From a95b187edc2b795aa201027e47566ab0563aeb2e Mon Sep 17 00:00:00 2001 From: lrnselfreliance <33263708+lrnselfreliance@users.noreply.github.com> Date: Sun, 11 Oct 2026 16:20:44 -0600 Subject: [PATCH] A yt-dlp format file is not the primary file when merged media is present (#817) `get_primary_file` returned the first video/audio file in whatever order it was given, so a group holding both `.mp4` and a leftover video-only `.f313.webm` could make the leftover the primary file. The Video then pointed at a file without audio, and a download of it was deferred with "Download did not contain audio stream" on every retry. Skip yt-dlp per-format files (`.fNNN` stems) when a merged media file is in the group. A group of only format files still gets one as its primary. Co-authored-by: Claude Opus 5.5 --- wrolpi/files/lib.py | 13 ++++++++++--- wrolpi/files/test/test_lib.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/wrolpi/files/lib.py b/wrolpi/files/lib.py index 7a9aca64..68dc8d37 100644 --- a/wrolpi/files/lib.py +++ b/wrolpi/files/lib.py @@ -694,6 +694,10 @@ def _paths_to_files_dict(group: List[pathlib.Path]) -> List[dict]: return files +# The stem of a yt-dlp per-format file, which is merged into the final media file. Example: The Title.f313.webm +YTDLP_FORMAT_STEM = re.compile(r'\.f\d{2,4}$') + + def get_primary_file(files: Union[Tuple[pathlib.Path], Iterable[pathlib.Path]]) -> pathlib.Path: """Given a list of files, return the file that we can model or index. @@ -720,13 +724,16 @@ def get_primary_file(files: Union[Tuple[pathlib.Path], Iterable[pathlib.Path]]) # EPUB has high priority. mimetypes = {i[1] for i in file_mimetypes} has_epub = any(i.startswith('application/epub') for i in mimetypes) + # A yt-dlp per-format file (video-only or audio-only) is primary only when no merged media file is present. + has_merged_media = any(m.startswith(('video/', 'audio/')) and not YTDLP_FORMAT_STEM.search(f.stem) + for f, m in file_mimetypes) for file, mimetype in file_mimetypes: # These are the files that can be modeled. if is_singlefile_file(file): return file - if mimetype.startswith('video/'): - return file - if mimetype.startswith('audio/'): + if mimetype.startswith(('video/', 'audio/')): + if has_merged_media and YTDLP_FORMAT_STEM.search(file.stem): + continue return file if mimetype.startswith('application/epub'): return file diff --git a/wrolpi/files/test/test_lib.py b/wrolpi/files/test/test_lib.py index 7072803e..b8028fc0 100644 --- a/wrolpi/files/test/test_lib.py +++ b/wrolpi/files/test/test_lib.py @@ -1,3 +1,4 @@ +import itertools import json import pathlib import shutil @@ -995,6 +996,22 @@ def test_get_primary_file(test_directory, video_file, srt_file3, example_epub, e assert lib.get_primary_file([foo, bar]) +def test_get_primary_file_skips_ytdlp_format_files(test_directory): + """A yt-dlp per-format file (`.f313.webm`) is never the primary file when the merged media is present.""" + merged = test_directory / 'a video.mp4' + video_only = test_directory / 'a video.f313.webm' + audio_only = test_directory / 'a video.f251.webm' + for path in (merged, video_only): + shutil.copy(PROJECT_DIR / 'test/big_buck_bunny_720p_1mb.mp4', path) + shutil.copy(PROJECT_DIR / 'test/big_buck_bunny.mp3', audio_only) + + for files in itertools.permutations([merged, video_only, audio_only]): + assert lib.get_primary_file(list(files)) == merged + + # Only format files remain (an unmerged download); one must still be the primary. + assert lib.get_primary_file([video_only, audio_only]) in (video_only, audio_only) + + @pytest.mark.asyncio async def test_refresh_files_no_groups(async_client, test_session, test_directory, make_files_structure, zip_file_factory, refresh_files):