diff --git a/CHANGELOG.md b/CHANGELOG.md index a377e7fd..01de9398 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,13 @@ The bundled TTS model package (`pyproject.toml`) is versioned independently. contact — less wall-of-text, faster to act on. ### Fixed +- **Dubbing a URL no longer fails with `[Errno 22] Invalid argument` on Windows.** + yt-dlp stamps the downloaded file's modified-time with the video's upload + date; an out-of-range/invalid timestamp makes the `os.utime` call raise + `[Errno 22]` and aborts the whole URL ingest. OmniVoice downloads to a throwaway + file and never uses its mtime, so it now skips the stamp entirely + (`updatetime=False`). (#642) + - **Dubbing a YouTube link that 403s now retries with a different player client.** Some videos serve their formats signature-protected to the default player client, so the media download fails with `HTTP Error 403: Forbidden` diff --git a/backend/services/dub_pipeline.py b/backend/services/dub_pipeline.py index 49eb7eae..7edef069 100644 --- a/backend/services/dub_pipeline.py +++ b/backend/services/dub_pipeline.py @@ -534,6 +534,12 @@ def yt_download_sync( "quiet": True, "no_warnings": True, "restrictfilenames": True, + # Don't stamp the downloaded file's mtime with the video's upload date + # (#642): on Windows an out-of-range/invalid timestamp makes the os.utime + # call raise `[Errno 22] Invalid argument`, failing the whole ingest. We + # download to a throwaway `original.*` and never use its mtime, so skip + # it entirely (equivalent to yt-dlp's --no-mtime). + "updatetime": False, "socket_timeout": 30, # Resilience against YouTube CDN flakes: a single empty fragment # (commonly the very last one — "Did not get any data blocks") diff --git a/tests/test_dub_download_mtime.py b/tests/test_dub_download_mtime.py new file mode 100644 index 00000000..685bf721 --- /dev/null +++ b/tests/test_dub_download_mtime.py @@ -0,0 +1,46 @@ +"""yt-dlp must not stamp the download's mtime (#642). + +On Windows, yt-dlp stamping the temp file with the video's upload date can raise +`[Errno 22] Invalid argument` from os.utime on an out-of-range timestamp, failing +the whole URL ingest. We download to a throwaway file and never use its mtime, so +`updatetime` must be False. +""" +import os +import sys + +import pytest + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "backend")) + +from services import dub_pipeline # noqa: E402 + + +def test_download_opts_disable_mtime(tmp_path, monkeypatch): + import yt_dlp + + captured = {} + + class _FakeYDL: + def __init__(self, opts): + captured.update(opts) + + def __enter__(self): + return self + + def __exit__(self, *a): + return False + + def extract_info(self, url, download=True): + raise RuntimeError("stop after capturing opts") + + def prepare_filename(self, info): + return "unused" + + monkeypatch.setattr(yt_dlp, "YoutubeDL", _FakeYDL) + with pytest.raises(Exception): + dub_pipeline.yt_download_sync("https://youtu.be/abc", str(tmp_path)) + + assert captured.get("updatetime") is False, ( + "yt-dlp must set updatetime=False so a bad upload-date timestamp can't " + "raise [Errno 22] on Windows (#642)" + )