fix: yt-dlp im temporären Verzeichnis ausführen (Streudatei „3“)
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 <noreply@anthropic.com>
This commit is contained in:
parent
60c7c10abb
commit
7e6ffa5899
3 changed files with 40 additions and 13 deletions
|
|
@ -12,13 +12,15 @@ def fake_yt_dlp():
|
||||||
"""Factory for a subprocess.run stand-in.
|
"""Factory for a subprocess.run stand-in.
|
||||||
|
|
||||||
It answers the info request with `info` and writes `vtt` where yt-dlp would
|
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):
|
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)
|
calls.append(command)
|
||||||
|
cwds.append(cwd)
|
||||||
if "--dump-single-json" in command:
|
if "--dump-single-json" in command:
|
||||||
return subprocess.CompletedProcess(command, 0, json.dumps(info), "")
|
return subprocess.CompletedProcess(command, 0, json.dumps(info), "")
|
||||||
if fail_download:
|
if fail_download:
|
||||||
|
|
@ -32,6 +34,7 @@ def fake_yt_dlp():
|
||||||
return subprocess.CompletedProcess(command, 0, "", "")
|
return subprocess.CompletedProcess(command, 0, "", "")
|
||||||
|
|
||||||
run.calls = calls
|
run.calls = calls
|
||||||
|
run.cwds = cwds
|
||||||
return run
|
return run
|
||||||
|
|
||||||
return make
|
return make
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"""Tests for yt_untertitel.py; yt-dlp is always replaced by fakes (no network)."""
|
"""Tests for yt_untertitel.py; yt-dlp is always replaced by fakes (no network)."""
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
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
|
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):
|
def test_main_prefers_manual_and_defaults_to_video_id(fake_yt_dlp, tmp_path, monkeypatch):
|
||||||
info = dict(AUTO_ONLY, subtitles={"de": []})
|
info = dict(AUTO_ONLY, subtitles={"de": []})
|
||||||
run = fake_yt_dlp(info)
|
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 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")
|
return subprocess.CompletedProcess(command, 1, "", "ERROR: Video unavailable")
|
||||||
|
|
||||||
monkeypatch.setattr(yt.subprocess, "run", run)
|
monkeypatch.setattr(yt.subprocess, "run", run)
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,26 @@ import argparse
|
||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
YT_DLP = "yt-dlp"
|
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):
|
def fetch_info(url):
|
||||||
"""Return the video metadata from yt-dlp, without downloading anything."""
|
"""Return the video metadata from yt-dlp, without downloading anything."""
|
||||||
result = subprocess.run(
|
result = run_yt_dlp(["--dump-single-json", "--skip-download", "--no-playlist", url])
|
||||||
[YT_DLP, "--dump-single-json", "--skip-download", "--no-playlist", url],
|
|
||||||
capture_output=True, text=True,
|
|
||||||
)
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
raise SystemExit(f"yt-dlp konnte das Video nicht abrufen:\n{result.stderr.strip()}")
|
raise SystemExit(f"yt-dlp konnte das Video nicht abrufen:\n{result.stderr.strip()}")
|
||||||
return json.loads(result.stdout)
|
return json.loads(result.stdout)
|
||||||
|
|
@ -70,14 +80,14 @@ def no_track_message(info, lang):
|
||||||
|
|
||||||
def download(url, track, is_auto, target_base):
|
def download(url, track, is_auto, target_base):
|
||||||
"""Download one subtitle track; yt-dlp writes <target_base>.<track>.vtt."""
|
"""Download one subtitle track; yt-dlp writes <target_base>.<track>.vtt."""
|
||||||
command = [
|
result = run_yt_dlp([
|
||||||
YT_DLP, "--skip-download", "--no-playlist", "--force-overwrites",
|
"--skip-download", "--no-playlist", "--force-overwrites",
|
||||||
"--write-auto-subs" if is_auto else "--write-subs",
|
"--write-auto-subs" if is_auto else "--write-subs",
|
||||||
"--sub-langs", track, "--sub-format", "vtt",
|
"--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,
|
url,
|
||||||
]
|
])
|
||||||
result = subprocess.run(command, capture_output=True, text=True)
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
raise SystemExit(f"Download fehlgeschlagen:\n{result.stderr.strip()}")
|
raise SystemExit(f"Download fehlgeschlagen:\n{result.stderr.strip()}")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue