Add audio format option to download form and update API for audio downloads
This commit is contained in:
@@ -38,6 +38,7 @@ const streamOnly = import.meta.env.STREAM_ONLY === "true";
|
|||||||
<option value="mp4" selected
|
<option value="mp4" selected
|
||||||
>{t(Astro, "downloadForm.formatMp4")}</option
|
>{t(Astro, "downloadForm.formatMp4")}</option
|
||||||
>
|
>
|
||||||
|
<option value="audio">{t(Astro, "downloadForm.formatAudio")}</option>
|
||||||
<option value="best">{t(Astro, "downloadForm.formatBest")}</option>
|
<option value="best">{t(Astro, "downloadForm.formatBest")}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"urlPlaceholder": "https://www.youtube.com/watch?v=...",
|
"urlPlaceholder": "https://www.youtube.com/watch?v=...",
|
||||||
"formatLabel": "Format",
|
"formatLabel": "Format",
|
||||||
"formatMp4": "MP4 (empfohlen - beste Kompatibilität)",
|
"formatMp4": "MP4 (empfohlen - beste Kompatibilität)",
|
||||||
|
"formatAudio": "Nur Audio (MP3/M4A)",
|
||||||
"formatBest": "Bestes verfügbares Format",
|
"formatBest": "Bestes verfügbares Format",
|
||||||
"downloadInProgress": "Download läuft...",
|
"downloadInProgress": "Download läuft...",
|
||||||
"downloadSuccessful": "Download erfolgreich!",
|
"downloadSuccessful": "Download erfolgreich!",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"urlPlaceholder": "https://www.youtube.com/watch?v=...",
|
"urlPlaceholder": "https://www.youtube.com/watch?v=...",
|
||||||
"formatLabel": "Format",
|
"formatLabel": "Format",
|
||||||
"formatMp4": "MP4 (recommended - best compatibility)",
|
"formatMp4": "MP4 (recommended - best compatibility)",
|
||||||
|
"formatAudio": "Audio only (MP3/M4A)",
|
||||||
"formatBest": "Best available format",
|
"formatBest": "Best available format",
|
||||||
"downloadInProgress": "Download in progress...",
|
"downloadInProgress": "Download in progress...",
|
||||||
"downloadSuccessful": "Download successful!",
|
"downloadSuccessful": "Download successful!",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { APIRoute } from "astro";
|
import type { APIRoute } from "astro";
|
||||||
import { getSession, isLoginEnabled } from "../../lib/session";
|
import { getSession, isLoginEnabled } from "../../lib/session";
|
||||||
import { tApi } from "../../lib/i18n";
|
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 { existsSync } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
@@ -59,13 +59,24 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
// Zuerst Video-Informationen abrufen, um den Dateinamen zu erhalten
|
// Zuerst Video-Informationen abrufen, um den Dateinamen zu erhalten
|
||||||
const videoInfo = await ytDlpWrap.getVideoInfo(url);
|
const videoInfo = await ytDlpWrap.getVideoInfo(url);
|
||||||
const title = videoInfo.title || "Video";
|
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}`;
|
const filename = `${title}.${ext}`;
|
||||||
|
|
||||||
// Format-String für yt-dlp
|
// Format-String für yt-dlp
|
||||||
const formatString =
|
const formatString =
|
||||||
format === "mp4"
|
format === "mp4"
|
||||||
? "bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo[ext=mp4]+bestaudio/best[ext=mp4]/best"
|
? "bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo[ext=mp4]+bestaudio/best[ext=mp4]/best"
|
||||||
|
: format === "audio"
|
||||||
|
? "bestaudio[ext=m4a]/bestaudio[ext=mp3]/bestaudio"
|
||||||
: "best";
|
: "best";
|
||||||
|
|
||||||
if (streamOnly) {
|
if (streamOnly) {
|
||||||
@@ -88,6 +99,9 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
if (format === "mp4") {
|
if (format === "mp4") {
|
||||||
execArgs.push("--merge-output-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
|
ytDlpWrap
|
||||||
@@ -113,11 +127,17 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Stream als Response zurückgeben
|
// 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, {
|
return new Response(fileBuffer, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type":
|
"Content-Type": contentType,
|
||||||
format === "mp4" ? "video/mp4" : "application/octet-stream",
|
|
||||||
"Content-Disposition": `attachment; filename="${encodeURIComponent(
|
"Content-Disposition": `attachment; filename="${encodeURIComponent(
|
||||||
filename
|
filename
|
||||||
)}"`,
|
)}"`,
|
||||||
@@ -172,6 +192,9 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
if (format === "mp4") {
|
if (format === "mp4") {
|
||||||
execArgs.push("--merge-output-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
|
ytDlpWrap
|
||||||
@@ -197,10 +220,21 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Dateiname aus dem tatsächlichen Output-Pfad extrahieren
|
// 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;
|
let finalFilename = filename;
|
||||||
if (existsSync(actualFilename)) {
|
if (existsSync(actualFilename)) {
|
||||||
finalFilename = path.basename(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(
|
return new Response(
|
||||||
|
|||||||
Reference in New Issue
Block a user