Files
unshackle/tests/orchestration/test_merge_video.py
imSp4rky ba69bc7d61 feat(dl): add --merge-video to merge video language variants
Group selected videos by (resolution, range, codec) and mux each group into one MKV; only language is collapsed, so ranges/codecs/resolutions stay in separate files. Adds --merge-video flag + muxing.merge_video config (global and per-service), docs, and tests.
2026-06-17 13:31:21 -06:00

130 lines
4.6 KiB
Python

"""Tests for ``--merge-video`` track grouping.
``group_videos_by_variant`` (``unshackle/commands/dl.py``) decides which selected video
tracks share one MKV when merge mode is on. The rule: group by ``(resolution, range,
codec)`` so only language varies within a file; resolutions, ranges and codecs stay
separate. With ``merge=False`` every track is its own group (one file per track).
These lock the pure grouping unit; the surrounding mux loop is Click-command orchestration.
"""
from __future__ import annotations
from unshackle.commands.dl import group_videos_by_variant
from unshackle.core.tracks import Video
def make_video(
track_id: str,
*,
range_: Video.Range,
height: int,
codec: Video.Codec,
language: str = "en",
) -> Video:
return Video(
id_=track_id,
url=f"https://example.test/{track_id}.m3u8",
language=language,
codec=codec,
range_=range_,
width=int(height * 16 / 9),
height=height,
bitrate=1_000_000,
)
HEVC = Video.Codec.HEVC
AVC = Video.Codec.AVC
SDR = Video.Range.SDR
HDR10 = Video.Range.HDR10
DV = Video.Range.DV
def test_merge_collapses_language_only() -> None:
"""Same (height, range, codec), different language → one group."""
videos = [
make_video("en", range_=SDR, height=1080, codec=HEVC, language="en"),
make_video("fr", range_=SDR, height=1080, codec=HEVC, language="fr"),
]
groups = group_videos_by_variant(videos, merge=True)
assert len(groups) == 1
assert [v.id for v in groups[0]] == ["en", "fr"]
def test_merge_splits_on_codec() -> None:
"""H264 vs H265 of the same resolution+range → separate groups."""
videos = [
make_video("hevc", range_=SDR, height=1080, codec=HEVC),
make_video("avc", range_=SDR, height=1080, codec=AVC),
]
groups = group_videos_by_variant(videos, merge=True)
assert len(groups) == 2
assert all(len(g) == 1 for g in groups)
def test_merge_splits_on_range() -> None:
"""SDR vs HDR10 of the same resolution+codec → separate groups."""
videos = [
make_video("sdr", range_=SDR, height=1080, codec=HEVC),
make_video("hdr10", range_=HDR10, height=1080, codec=HEVC),
]
groups = group_videos_by_variant(videos, merge=True)
assert len(groups) == 2
def test_merge_splits_on_resolution() -> None:
"""1080p vs 2160p of the same range+codec → separate groups."""
videos = [
make_video("1080", range_=SDR, height=1080, codec=HEVC),
make_video("2160", range_=SDR, height=2160, codec=HEVC),
]
groups = group_videos_by_variant(videos, merge=True)
assert len(groups) == 2
def test_merge_multi_range_yields_one_group_per_range() -> None:
"""Regression guard: -r HYBRID,DV,HDR10,SDR must never collapse into one file.
HYBRID is resolved upstream into a DV deliverable plus the requested standalone
ranges; here the four selected single-range tracks must stay in four groups.
"""
videos = [
make_video("sdr", range_=SDR, height=2160, codec=HEVC),
make_video("hdr10", range_=HDR10, height=2160, codec=HEVC),
make_video("dv", range_=DV, height=2160, codec=HEVC),
make_video("dv-hybrid", range_=DV, height=1080, codec=HEVC), # different height
]
groups = group_videos_by_variant(videos, merge=True)
assert len(groups) == 4
def test_no_merge_yields_one_group_per_track() -> None:
"""merge=False reproduces today's per-track behaviour exactly."""
videos = [
make_video("en", range_=SDR, height=1080, codec=HEVC, language="en"),
make_video("fr", range_=SDR, height=1080, codec=HEVC, language="fr"),
make_video("avc", range_=SDR, height=1080, codec=AVC),
]
groups = group_videos_by_variant(videos, merge=False)
assert len(groups) == 3
assert all(len(g) == 1 for g in groups)
def test_merge_preserves_first_seen_order() -> None:
"""Group order follows first-seen track order, for stable output filenames."""
videos = [
make_video("hevc-en", range_=SDR, height=1080, codec=HEVC, language="en"),
make_video("avc-en", range_=SDR, height=1080, codec=AVC, language="en"),
make_video("hevc-fr", range_=SDR, height=1080, codec=HEVC, language="fr"),
]
groups = group_videos_by_variant(videos, merge=True)
# HEVC group seen first (and gathers both languages), AVC group second.
assert [v.id for v in groups[0]] == ["hevc-en", "hevc-fr"]
assert [v.id for v in groups[1]] == ["avc-en"]
def test_empty_input_returns_empty() -> None:
assert group_videos_by_variant([], merge=True) == []
assert group_videos_by_variant([], merge=False) == []