Enhance Dockerfile and API for yt-dlp integration

- Install Deno as a JavaScript runtime for yt-dlp in the Dockerfile.
- Add configuration for yt-dlp to support cookies and JavaScript runtime selection.
- Update API to handle cookies for YouTube bot detection and allow specifying the JavaScript runtime.
- Introduce new environment variables for cookie management and JavaScript runtime configuration in README.
This commit is contained in:
Peter Meier
2025-12-22 12:58:09 +01:00
parent eb32dd1064
commit a8f822807d
3 changed files with 69 additions and 8 deletions

View File

@@ -3,15 +3,25 @@ FROM node:20-alpine
# Set default locale to German # Set default locale to German
ENV LOCALE=de ENV LOCALE=de
# yt-dlp und ffmpeg installieren # yt-dlp, ffmpeg und deno (JavaScript Runtime für YouTube) installieren
RUN apk add --no-cache \ RUN apk add --no-cache \
python3 \ python3 \
py3-pip \ py3-pip \
ffmpeg \ ffmpeg \
&& pip3 install --no-cache-dir --break-system-packages yt-dlp curl \
unzip \
&& pip3 install --no-cache-dir --break-system-packages yt-dlp \
&& curl -fsSL https://deno.land/install.sh | sh \
&& mv /root/.deno/bin/deno /usr/local/bin/deno \
&& chmod +x /usr/local/bin/deno
WORKDIR /app WORKDIR /app
# yt-dlp Konfiguration erstellen (für deno als JS-Runtime)
RUN mkdir -p /root/.config/yt-dlp && \
echo "--js-runtimes deno" > /root/.config/yt-dlp/config && \
echo "--no-warnings" >> /root/.config/yt-dlp/config
# Package-Dateien kopieren und Dependencies installieren # Package-Dateien kopieren und Dependencies installieren
COPY package*.json ./ COPY package*.json ./
RUN npm ci RUN npm ci

View File

@@ -101,6 +101,9 @@ Siehe [BUILD-MACOS.md](./BUILD-MACOS.md) für detaillierte Anleitung.
- `SESSION_SECRET`: Secret für Session-Cookies (Standard: `default-secret-change-in-production`) - `SESSION_SECRET`: Secret für Session-Cookies (Standard: `default-secret-change-in-production`)
- `STREAM_ONLY`: Wenn auf `true` gesetzt, werden Dateien nicht physisch gespeichert, sondern direkt als Download-Stream angeboten (Standard: `false`) - `STREAM_ONLY`: Wenn auf `true` gesetzt, werden Dateien nicht physisch gespeichert, sondern direkt als Download-Stream angeboten (Standard: `false`)
- `LOCALE`: Sprache der Anwendung - `de` für Deutsch oder `en` für Englisch (Standard: `de`) - `LOCALE`: Sprache der Anwendung - `de` für Deutsch oder `en` für Englisch (Standard: `de`)
- `YT_DLP_COOKIES`: Pfad zu einer Cookie-Datei für yt-dlp (z.B. `./cookies.txt`). Wird verwendet, um YouTube Bot-Erkennung zu umgehen. Siehe [yt-dlp Cookie-Dokumentation](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp)
- `YT_DLP_COOKIES_FROM_BROWSER`: Browser-Name, aus dem Cookies geladen werden sollen (z.B. `chrome`, `firefox`, `edge`, `safari`). Alternative zu `YT_DLP_COOKIES`. Siehe [yt-dlp Cookie-Dokumentation](https://github.com/yt-dlp/yt-dlp/wiki/Extractors#exporting-youtube-cookies)
- `YT_DLP_JS_RUNTIME`: JavaScript Runtime für yt-dlp (Standard: `deno`). Andere Optionen: `node`, `d8`, etc.
**Beispiel `.env` Datei mit Login:** **Beispiel `.env` Datei mit Login:**
``` ```
@@ -111,6 +114,7 @@ DOWNLOAD_DIR=./downloaded
SESSION_SECRET=mein-geheimes-session-secret SESSION_SECRET=mein-geheimes-session-secret
STREAM_ONLY=false STREAM_ONLY=false
LOCALE=de LOCALE=de
YT_DLP_COOKIES_FROM_BROWSER=chrome
``` ```
**Beispiel `.env` Datei ohne Login:** **Beispiel `.env` Datei ohne Login:**
@@ -119,8 +123,15 @@ LOGIN=false
DOWNLOAD_DIR=./downloaded DOWNLOAD_DIR=./downloaded
STREAM_ONLY=false STREAM_ONLY=false
LOCALE=de LOCALE=de
YT_DLP_COOKIES_FROM_BROWSER=chrome
``` ```
**Hinweis zu Cookies:**
- Wenn YouTube Bot-Erkennung auftritt, müssen Cookies verwendet werden
- `YT_DLP_COOKIES_FROM_BROWSER` ist die einfachste Option: Gibt den Browser-Namen an (z.B. `chrome`, `firefox`, `edge`, `safari`)
- Alternativ kann `YT_DLP_COOKIES` mit dem Pfad zu einer Cookie-Datei verwendet werden
- Cookie-Dateien können mit Browser-Erweiterungen wie "Get cookies.txt LOCALLY" oder "cookies.txt" exportiert werden
**Hinweis zu `LOGIN`:** **Hinweis zu `LOGIN`:**
- Wenn `LOGIN=false`: Keine Login-Seite, alle Seiten sind öffentlich zugänglich. `LOGIN_USERNAME` und `LOGIN_PASSWORD` werden ignoriert. - Wenn `LOGIN=false`: Keine Login-Seite, alle Seiten sind öffentlich zugänglich. `LOGIN_USERNAME` und `LOGIN_PASSWORD` werden ignoriert.
- Wenn `LOGIN=true` oder nicht gesetzt: Login ist aktiviert. `LOGIN_USERNAME` und `LOGIN_PASSWORD` müssen gesetzt sein. - Wenn `LOGIN=true` oder nicht gesetzt: Login ist aktiviert. `LOGIN_USERNAME` und `LOGIN_PASSWORD` müssen gesetzt sein.

View File

@@ -56,8 +56,23 @@ export const POST: APIRoute = async ({ request }) => {
// Format aus Request Header (optional, Standard: MP4) // Format aus Request Header (optional, Standard: MP4)
const format = request.headers.get("x-format") || "mp4"; const format = request.headers.get("x-format") || "mp4";
// Cookie-Unterstützung für YouTube Bot-Erkennung
const cookiesFile = process.env.YT_DLP_COOKIES;
const cookiesFromBrowser = process.env.YT_DLP_COOKIES_FROM_BROWSER;
// JavaScript Runtime für yt-dlp (Standard: deno)
const jsRuntime = process.env.YT_DLP_JS_RUNTIME || "deno";
// Zuerst Video-Informationen abrufen, um den Dateinamen zu erhalten // Zuerst Video-Informationen abrufen, um den Dateinamen zu erhalten
const videoInfo = await ytDlpWrap.getVideoInfo(url); // getVideoInfo verwendet intern yt-dlp mit --dump-json
// Deno wird beim eigentlichen Download verwendet
const videoInfoOptions: string[] = [];
if (cookiesFile) {
videoInfoOptions.push("--cookies", cookiesFile);
} else if (cookiesFromBrowser) {
videoInfoOptions.push("--cookies-from-browser", cookiesFromBrowser);
}
const videoInfo = await ytDlpWrap.getVideoInfo(url, videoInfoOptions);
const title = videoInfo.title || "Video"; const title = videoInfo.title || "Video";
// Dateiendung bestimmen // Dateiendung bestimmen
@@ -72,12 +87,13 @@ export const POST: APIRoute = async ({ request }) => {
const filename = `${title}.${ext}`; const filename = `${title}.${ext}`;
// Format-String für yt-dlp // Format-String für yt-dlp
// Für "best": Kein Format-String = yt-dlp wählt automatisch bestes Format und merged
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" : format === "audio"
? "bestaudio[ext=m4a]/bestaudio[ext=mp3]/bestaudio" ? "bestaudio[ext=m4a]/bestaudio[ext=mp3]/bestaudio"
: "best"; : undefined; // undefined = yt-dlp wählt automatisch bestes Format
if (streamOnly) { if (streamOnly) {
// STREAM-MODUS: Datei temporär speichern, streamen, dann löschen // STREAM-MODUS: Datei temporär speichern, streamen, dann löschen
@@ -91,12 +107,24 @@ export const POST: APIRoute = async ({ request }) => {
url, url,
"-o", "-o",
tempFilePath, tempFilePath,
"-f",
formatString,
"--no-mtime", "--no-mtime",
"--no-playlist", "--no-playlist",
"--js-runtimes",
jsRuntime,
]; ];
// Cookie-Unterstützung hinzufügen
if (cookiesFile) {
execArgs.push("--cookies", cookiesFile);
} else if (cookiesFromBrowser) {
execArgs.push("--cookies-from-browser", cookiesFromBrowser);
}
// Format nur hinzufügen wenn definiert
if (formatString) {
execArgs.push("-f", formatString);
}
if (format === "mp4") { if (format === "mp4") {
execArgs.push("--merge-output-format", "mp4"); execArgs.push("--merge-output-format", "mp4");
} else if (format === "audio") { } else if (format === "audio") {
@@ -184,12 +212,24 @@ export const POST: APIRoute = async ({ request }) => {
url, url,
"-o", "-o",
outputPath, outputPath,
"-f",
formatString,
"--no-mtime", "--no-mtime",
"--no-playlist", "--no-playlist",
"--js-runtimes",
jsRuntime,
]; ];
// Cookie-Unterstützung hinzufügen
if (cookiesFile) {
execArgs.push("--cookies", cookiesFile);
} else if (cookiesFromBrowser) {
execArgs.push("--cookies-from-browser", cookiesFromBrowser);
}
// Format nur hinzufügen wenn definiert
if (formatString) {
execArgs.push("-f", formatString);
}
if (format === "mp4") { if (format === "mp4") {
execArgs.push("--merge-output-format", "mp4"); execArgs.push("--merge-output-format", "mp4");
} else if (format === "audio") { } else if (format === "audio") {