diff --git a/src/components/DownloadForm.astro b/src/components/DownloadForm.astro
index 56092a2..b643ed6 100644
--- a/src/components/DownloadForm.astro
+++ b/src/components/DownloadForm.astro
@@ -38,6 +38,7 @@ const streamOnly = import.meta.env.STREAM_ONLY === "true";
+
diff --git a/src/i18n/de.json b/src/i18n/de.json
index 65a05fc..341a4dc 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -20,6 +20,7 @@
"urlPlaceholder": "https://www.youtube.com/watch?v=...",
"formatLabel": "Format",
"formatMp4": "MP4 (empfohlen - beste Kompatibilität)",
+ "formatAudio": "Nur Audio (MP3/M4A)",
"formatBest": "Bestes verfügbares Format",
"downloadInProgress": "Download läuft...",
"downloadSuccessful": "Download erfolgreich!",
diff --git a/src/i18n/en.json b/src/i18n/en.json
index e9b5cde..c8318f0 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -20,6 +20,7 @@
"urlPlaceholder": "https://www.youtube.com/watch?v=...",
"formatLabel": "Format",
"formatMp4": "MP4 (recommended - best compatibility)",
+ "formatAudio": "Audio only (MP3/M4A)",
"formatBest": "Best available format",
"downloadInProgress": "Download in progress...",
"downloadSuccessful": "Download successful!",
diff --git a/src/pages/api/download.ts b/src/pages/api/download.ts
index 196a1e3..582e32a 100644
--- a/src/pages/api/download.ts
+++ b/src/pages/api/download.ts
@@ -1,7 +1,7 @@
import type { APIRoute } from "astro";
import { getSession, isLoginEnabled } from "../../lib/session";
import { tApi } from "../../lib/i18n";
-import { mkdir, unlink, readFile } from "node:fs/promises";
+import { mkdir, unlink, readFile, readdir } from "node:fs/promises";
import { existsSync } from "node:fs";
import path from "node:path";
import { tmpdir } from "node:os";
@@ -59,13 +59,24 @@ export const POST: APIRoute = async ({ request }) => {
// Zuerst Video-Informationen abrufen, um den Dateinamen zu erhalten
const videoInfo = await ytDlpWrap.getVideoInfo(url);
const title = videoInfo.title || "Video";
- const ext = format === "mp4" ? "mp4" : videoInfo.ext || "mp4";
+
+ // Dateiendung bestimmen
+ let ext = "mp4";
+ if (format === "audio") {
+ ext = "mp3"; // yt-dlp wird zu MP3 konvertieren
+ } else if (format === "mp4") {
+ ext = "mp4";
+ } else {
+ ext = videoInfo.ext || "mp4";
+ }
const filename = `${title}.${ext}`;
// Format-String für yt-dlp
const formatString =
format === "mp4"
? "bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo[ext=mp4]+bestaudio/best[ext=mp4]/best"
+ : format === "audio"
+ ? "bestaudio[ext=m4a]/bestaudio[ext=mp3]/bestaudio"
: "best";
if (streamOnly) {
@@ -88,6 +99,9 @@ export const POST: APIRoute = async ({ request }) => {
if (format === "mp4") {
execArgs.push("--merge-output-format", "mp4");
+ } else if (format === "audio") {
+ // Für Audio: Konvertiere zu MP3
+ execArgs.push("--extract-audio", "--audio-format", "mp3");
}
ytDlpWrap
@@ -113,11 +127,17 @@ export const POST: APIRoute = async ({ request }) => {
});
// Stream als Response zurückgeben
+ let contentType = "application/octet-stream";
+ if (format === "mp4") {
+ contentType = "video/mp4";
+ } else if (format === "audio") {
+ contentType = "audio/mpeg"; // MP3
+ }
+
return new Response(fileBuffer, {
status: 200,
headers: {
- "Content-Type":
- format === "mp4" ? "video/mp4" : "application/octet-stream",
+ "Content-Type": contentType,
"Content-Disposition": `attachment; filename="${encodeURIComponent(
filename
)}"`,
@@ -172,6 +192,9 @@ export const POST: APIRoute = async ({ request }) => {
if (format === "mp4") {
execArgs.push("--merge-output-format", "mp4");
+ } else if (format === "audio") {
+ // Für Audio: Konvertiere zu MP3
+ execArgs.push("--extract-audio", "--audio-format", "mp3");
}
ytDlpWrap
@@ -197,10 +220,21 @@ export const POST: APIRoute = async ({ request }) => {
});
// Dateiname aus dem tatsächlichen Output-Pfad extrahieren
- const actualFilename = path.join(downloadDir, `${title}.${ext}`);
+ // Für Audio: yt-dlp erstellt .mp3 Datei
+ const actualExt = format === "audio" ? "mp3" : ext;
+ const actualFilename = path.join(downloadDir, `${title}.${actualExt}`);
let finalFilename = filename;
if (existsSync(actualFilename)) {
finalFilename = path.basename(actualFilename);
+ } else if (format === "audio") {
+ // Fallback: Suche nach .mp3 Datei mit möglicherweise anderem Namen
+ const files = await readdir(downloadDir);
+ const mp3File = files.find(
+ (f: string) => f.endsWith(".mp3") && f.includes(title)
+ );
+ if (mp3File) {
+ finalFilename = mp3File;
+ }
}
return new Response(