From 4fd9d3f400a1ac4043d796c438fcd7ee25f906b6 Mon Sep 17 00:00:00 2001 From: Peter Meier Date: Mon, 22 Dec 2025 11:06:14 +0100 Subject: [PATCH] Add audio format option to download form and update API for audio downloads --- src/components/DownloadForm.astro | 1 + src/i18n/de.json | 1 + src/i18n/en.json | 1 + src/pages/api/download.ts | 44 +++++++++++++++++++++++++++---- 4 files changed, 42 insertions(+), 5 deletions(-) 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(