From 7e6ffa58995806edd0697cc6f25878ab695c857f Mon Sep 17 00:00:00 2001 From: dschlueter Date: Sat, 10 Oct 2026 20:12:08 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20yt-dlp=20im=20tempor=C3=A4ren=20Verzeich?= =?UTF-8?q?nis=20ausf=C3=BChren=20(Streudatei=20=E2=80=9E3=E2=80=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit yt-dlp ruft zur Prüfung „ffmpeg -bsfs“ auf; das System-ffmpeg 6.1.1 (Ubuntu) legt dabei eine leere Datei „3“ im Arbeitsverzeichnis an. yt-dlp läuft jetzt in einem temporären Verzeichnis mit absolutem Zielpfad, die Datei verschwindet mit dem Verzeichnis. Co-Authored-By: Claude Opus 5.5 --- tests/conftest.py | 9 ++++++--- tests/test_yt_untertitel.py | 16 +++++++++++++++- yt_untertitel.py | 28 +++++++++++++++++++--------- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 522ed2b..31250c6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,13 +12,15 @@ def fake_yt_dlp(): """Factory for a subprocess.run stand-in. It answers the info request with `info` and writes `vtt` where yt-dlp would - write the subtitle file. `run.calls` records every command. + write the subtitle file. `run.calls` records every command, `run.cwds` the + working directories. """ def make(info, vtt="WEBVTT\n", write=True, fail_download=False): - calls = [] + calls, cwds = [], [] - def run(command, capture_output, text): + def run(command, capture_output, text, cwd=None): calls.append(command) + cwds.append(cwd) if "--dump-single-json" in command: return subprocess.CompletedProcess(command, 0, json.dumps(info), "") if fail_download: @@ -32,6 +34,7 @@ def fake_yt_dlp(): return subprocess.CompletedProcess(command, 0, "", "") run.calls = calls + run.cwds = cwds return run return make diff --git a/tests/test_yt_untertitel.py b/tests/test_yt_untertitel.py index 1088043..e422ae7 100644 --- a/tests/test_yt_untertitel.py +++ b/tests/test_yt_untertitel.py @@ -1,6 +1,7 @@ """Tests for yt_untertitel.py; yt-dlp is always replaced by fakes (no network).""" import subprocess import sys +from pathlib import Path import pytest @@ -67,6 +68,19 @@ def test_main_downloads_auto_original(fake_yt_dlp, tmp_path, monkeypatch, capsys assert "vtt2txt.py -i" in out +def test_yt_dlp_runs_in_temporary_directory(fake_yt_dlp, tmp_path, monkeypatch): + # ffmpeg -bsfs, called by yt-dlp, leaves a file "3" in its working directory + run = fake_yt_dlp(AUTO_ONLY) + monkeypatch.setattr(yt.subprocess, "run", run) + monkeypatch.chdir(tmp_path) + run_main(monkeypatch, "-u", URL, "-n", "Rede") + + assert all(cwd is not None and Path(cwd) != tmp_path for cwd in run.cwds) + template = run.calls[1][run.calls[1].index("-o") + 1] + assert template == f"{tmp_path / 'Rede'}.%(ext)s" + assert (tmp_path / "Rede.de-orig.vtt").is_file() + + def test_main_prefers_manual_and_defaults_to_video_id(fake_yt_dlp, tmp_path, monkeypatch): info = dict(AUTO_ONLY, subtitles={"de": []}) run = fake_yt_dlp(info) @@ -84,7 +98,7 @@ def test_main_no_matching_track(fake_yt_dlp, tmp_path, monkeypatch): def test_main_info_error(tmp_path, monkeypatch): - def run(command, capture_output, text): + def run(command, capture_output, text, cwd=None): return subprocess.CompletedProcess(command, 1, "", "ERROR: Video unavailable") monkeypatch.setattr(yt.subprocess, "run", run) diff --git a/yt_untertitel.py b/yt_untertitel.py index d695ac9..14a6c0a 100755 --- a/yt_untertitel.py +++ b/yt_untertitel.py @@ -6,16 +6,26 @@ import argparse import json import shutil import subprocess +import tempfile YT_DLP = "yt-dlp" +def run_yt_dlp(arguments): + """Run yt-dlp in a temporary working directory. + + yt-dlp calls "ffmpeg -bsfs" to check ffmpeg; some ffmpeg builds (Ubuntu + 6.1.1) then leave an empty file "3" in the working directory. + """ + with tempfile.TemporaryDirectory() as tmp: + return subprocess.run( + [YT_DLP, *arguments], capture_output=True, text=True, cwd=tmp + ) + + def fetch_info(url): """Return the video metadata from yt-dlp, without downloading anything.""" - result = subprocess.run( - [YT_DLP, "--dump-single-json", "--skip-download", "--no-playlist", url], - capture_output=True, text=True, - ) + result = run_yt_dlp(["--dump-single-json", "--skip-download", "--no-playlist", url]) if result.returncode != 0: raise SystemExit(f"yt-dlp konnte das Video nicht abrufen:\n{result.stderr.strip()}") return json.loads(result.stdout) @@ -70,14 +80,14 @@ def no_track_message(info, lang): def download(url, track, is_auto, target_base): """Download one subtitle track; yt-dlp writes ..vtt.""" - command = [ - YT_DLP, "--skip-download", "--no-playlist", "--force-overwrites", + result = run_yt_dlp([ + "--skip-download", "--no-playlist", "--force-overwrites", "--write-auto-subs" if is_auto else "--write-subs", "--sub-langs", track, "--sub-format", "vtt", - "-o", f"{target_base}.%(ext)s", + # Absolute, because yt-dlp runs in a temporary directory + "-o", f"{Path(target_base).resolve()}.%(ext)s", url, - ] - result = subprocess.run(command, capture_output=True, text=True) + ]) if result.returncode != 0: raise SystemExit(f"Download fehlgeschlagen:\n{result.stderr.strip()}")