fix: TikTok-Extraktion im Docker-Container (Pi-hole DNS + Windows-FFMPEG_LOCATION)

This commit is contained in:
Andre
2026-04-16 18:34:58 +02:00
parent 895e2e588f
commit 66c3fc8f7a
4 changed files with 48 additions and 16 deletions
+21 -1
View File
@@ -157,9 +157,29 @@ Der Extraktions-Endpoint streamt drei Event-Typen:
- **`result`** -- Fertiges Rezept als JSON (`ExtractResponse`) - **`result`** -- Fertiges Rezept als JSON (`ExtractResponse`)
- **`error`** -- Fehlermeldung - **`error`** -- Fehlermeldung
## Bekannte Probleme & Loesungen
### TikTok: Verbindung verweigert im Docker-Container (Pi-hole)
Wenn Pi-hole als DNS-Server im Heimnetzwerk laeuft, kann der Docker-Container TikTok-Domains nicht aufloesen — Pi-hole gibt eine Sinkhole-IP zurueck, die Verbindung wird sofort verweigert. Ausserhalb des Containers funktioniert TikTok normal, weil der Pi-Host einen anderen DNS-Pfad nutzt.
**Loesung**: In `docker-compose.yml` einen oeffentlichen DNS-Server eintragen:
```yaml
services:
rezeptor:
dns:
- 1.1.1.1
- 8.8.8.8
```
Danach `docker compose up -d` (kein Rebuild noetig).
---
## Technische Hinweise ## Technische Hinweise
- **yt-dlp** wird immer als Subprocess aufgerufen (`asyncio.create_subprocess_exec`), nie ueber die Python-API, um den Event-Loop nicht zu blockieren - **yt-dlp** wird immer als Subprocess aufgerufen (`subprocess.run` via `loop.run_in_executor()`), nie ueber die Python-API, um den Event-Loop nicht zu blockieren
- **Whisper** hat ein Dateigroessen-Limit von 25 MB -- zu lange Videos fuehren zu einem Fehler - **Whisper** hat ein Dateigroessen-Limit von 25 MB -- zu lange Videos fuehren zu einem Fehler
- **recipe-scrapers** laedt HTML selbststaendig -- es wird die URL uebergeben, nicht vorgeladenes HTML - **recipe-scrapers** laedt HTML selbststaendig -- es wird die URL uebergeben, nicht vorgeladenes HTML
- **Bild-Upload** nach Tandoor erfolgt per `PATCH` mit Multipart -- der `Content-Type`-Header wird nicht manuell gesetzt - **Bild-Upload** nach Tandoor erfolgt per `PATCH` mit Multipart -- der `Content-Type`-Header wird nicht manuell gesetzt
-1
View File
@@ -19,7 +19,6 @@ class Settings(BaseSettings):
app_port: int = 8000 app_port: int = 8000
tmp_dir: str = "tmp" tmp_dir: str = "tmp"
ffmpeg_location: str = "" # Optional: full path to ffmpeg binary dir (for Windows local dev) ffmpeg_location: str = "" # Optional: full path to ffmpeg binary dir (for Windows local dev)
@field_validator("tmp_dir", "ffmpeg_location", mode="before") @field_validator("tmp_dir", "ffmpeg_location", mode="before")
@classmethod @classmethod
def _resolve_project_relative_paths(cls, value: str) -> str: def _resolve_project_relative_paths(cls, value: str) -> str:
+24 -13
View File
@@ -118,10 +118,13 @@ async def extract_video(url: str) -> AsyncGenerator[StatusEvent | Recipe, None]:
log.warning("Could not delete tmp file %s: %s", audio_path, e) log.warning("Could not delete tmp file %s: %s", audio_path, e)
def _ffmpeg_args() -> list[str]: def _ytdlp_extra_args() -> list[str]:
"""Return --ffmpeg-location args if configured, else empty list.""" """Return optional yt-dlp flags from settings (ffmpeg, proxy, cookies)."""
loc = get_settings().ffmpeg_location settings = get_settings()
return ["--ffmpeg-location", loc] if loc else [] args: list[str] = []
if settings.ffmpeg_location and Path(settings.ffmpeg_location).exists():
args += ["--ffmpeg-location", settings.ffmpeg_location]
return args
async def _run_yt_dlp(*args: str, timeout: int) -> tuple[bytes, bytes, int]: async def _run_yt_dlp(*args: str, timeout: int) -> tuple[bytes, bytes, int]:
@@ -135,7 +138,7 @@ async def _run_yt_dlp(*args: str, timeout: int) -> tuple[bytes, bytes, int]:
async def _get_metadata(url: str) -> dict: async def _get_metadata(url: str) -> dict:
"""Run yt-dlp --dump-json to get metadata without downloading.""" """Run yt-dlp --dump-json to get metadata without downloading."""
stdout, stderr, returncode = await _run_yt_dlp( stdout, stderr, returncode = await _run_yt_dlp(
"yt-dlp", "--dump-json", "--no-playlist", *_ffmpeg_args(), url, timeout=60 "yt-dlp", "--dump-json", "--no-playlist", *_ytdlp_extra_args(), url, timeout=60
) )
if returncode != 0: if returncode != 0:
err = stderr.decode(errors="replace") err = stderr.decode(errors="replace")
@@ -159,7 +162,7 @@ async def _download_audio(url: str, output_path: Path) -> None:
"--audio-format", "mp3", "--audio-format", "mp3",
"--audio-quality", "5", "--audio-quality", "5",
"--max-filesize", MAX_FILESIZE, "--max-filesize", MAX_FILESIZE,
*_ffmpeg_args(), *_ytdlp_extra_args(),
"--output", str(output_path).replace(".mp3", ".%(ext)s"), "--output", str(output_path).replace(".mp3", ".%(ext)s"),
url, url,
timeout=300, timeout=300,
@@ -197,10 +200,18 @@ def _get_thumbnail(metadata: dict) -> str | None:
def _check_tiktok_error(stderr: str, url: str) -> None: def _check_tiktok_error(stderr: str, url: str) -> None:
"""Raise a clear error for TikTok login/cookie issues.""" """Raise a clear error for TikTok connectivity/auth issues."""
if "tiktok" in url.lower(): if "tiktok" not in url.lower():
if any(phrase in stderr.lower() for phrase in ["login", "cookie", "captcha", "403"]): return
raise RuntimeError( err_lower = stderr.lower()
"TikTok erfordert Cookies für dieses Video. " if any(p in err_lower for p in ["connection refused", "failed to establish", "unable to download webpage"]):
"Bitte exportiere deine Browser-Cookies und konfiguriere yt-dlp entsprechend." raise RuntimeError(
) "TikTok ist vom Server nicht erreichbar (Verbindung verweigert). "
"Möglicherweise blockiert die installierte yt-dlp-Version TikTok. "
"Bitte Docker-Image neu bauen: docker compose up -d --build"
)
if any(p in err_lower for p in ["login", "cookie", "captcha", "403"]):
raise RuntimeError(
"TikTok erfordert Cookies für dieses Video. "
"Exportiere Browser-Cookies und setze YTDLP_COOKIES_FILE=/pfad/cookies.txt in der .env."
)
+3 -1
View File
@@ -15,7 +15,6 @@ services:
- TMP_DIR - TMP_DIR
- APP_HOST - APP_HOST
- APP_PORT - APP_PORT
- FFMPEG_LOCATION
- APP_UID - APP_UID
- APP_GID - APP_GID
ports: ports:
@@ -25,3 +24,6 @@ services:
- ${REZEPTOR_DATA_DIR:-./data}:/app/data - ${REZEPTOR_DATA_DIR:-./data}:/app/data
mem_limit: 512m mem_limit: 512m
memswap_limit: 512m # No swap – protect SD card memswap_limit: 512m # No swap – protect SD card
dns:
- 1.1.1.1
- 8.8.8.8