""" Container-compatibility rule-table tests for ``OpenCodecMaster._check_combo_compatibility``. QA finding: OTC-012 (table-driven combo rule evaluation). The rule table inside ``_check_combo_compatibility`` encodes 5 known container/codec combinations and their severities: 1. x265 + WebM -> INCOMPATIBLE 2. IAMF audio + (MKV|WebM, i.e. non-MP4) -> INCOMPATIBLE 3. Vorbis + MP4 -> WARNING 4. FLAC + WebM -> WARNING 5. VP9 + MP4 -> WARNING (IAMF + MP4 is the OK case for rule 2: not fired, no other rule fires, empty warnings list.) The rule table is defined as closures *inside* the method body, so we can't test it as a free function. Instead we mock the ``OpenCodecMaster`` instance: build it via ``__new__`` (skip the heavy ``__init__`` that constructs the whole GUI), set ``codec_combo`` / ``audio_combo`` / ``container_combo`` to ``MagicMock`` objects whose ``currentIndex()`` returns the index we want to test, and patch ``_log`` to capture warnings. Then we call ``_check_combo_compatibility`` directly and assert on the returned list. The 6 cases (one per rule plus the IAMF+MP4 happy case) exercise every predicate in the table. """ from __future__ import annotations from unittest.mock import MagicMock import pytest # ───────────────────────────────────────────────────────────────────────────── # Indices into the module-level VIDEO_CODECS / AUDIO_PROFILES / # CONTAINER_PROFILES lists (defined in scripts/open-transcode.v3.py). # ───────────────────────────────────────────────────────────────────────────── # VIDEO_CODECS[0] = "AV1 (SVT-AV1)" — ffmpeg_encoder="libsvtav1" # VIDEO_CODECS[1] = "VP9" — ffmpeg_encoder="libvpx-vp9" # VIDEO_CODECS[2] = "x265 (HEVC)" — ffmpeg_encoder="libx265" # # AUDIO_PROFILES[0] = "Opus (96k)" — ffmpeg_encoder_name="libopus" # AUDIO_PROFILES[3] = "Vorbis (128k)" — ffmpeg_encoder_name="libvorbis" # AUDIO_PROFILES[5] = "FLAC" — ffmpeg_encoder_name="flac" # AUDIO_PROFILES[6] = "IAMF (128k)" — ffmpeg_encoder_name="libiamf" # # CONTAINER_PROFILES[0] = "MKV" — ext="mkv" # CONTAINER_PROFILES[1] = "WebM" — ext="webm" # CONTAINER_PROFILES[2] = "MP4" — ext="mp4" def _make_master_for_compat(v3_module, codec_idx, audio_idx, container_idx): """Build a minimal ``OpenCodecMaster`` for compatibility-rule testing. Skips the real ``__init__`` (which constructs the whole QMainWindow UI tree) and sets only the three combo-box attributes that ``_check_combo_compatibility`` reads. The ``_log`` method is patched to capture warnings into ``master._logged`` so the test can also verify what was logged (not just what was returned). """ master = v3_module.OpenCodecMaster.__new__(v3_module.OpenCodecMaster) codec_combo = MagicMock() codec_combo.currentIndex.return_value = codec_idx audio_combo = MagicMock() audio_combo.currentIndex.return_value = audio_idx container_combo = MagicMock() container_combo.currentIndex.return_value = container_idx master.codec_combo = codec_combo master.audio_combo = audio_combo master.container_combo = container_combo logged: list[str] = [] master._log = lambda msg: logged.append(msg) master._logged = logged return master # ───────────────────────────────────────────────────────────────────────────── # Test cases — one per rule in the table, plus the IAMF+MP4 happy case. # ───────────────────────────────────────────────────────────────────────────── def test_hevc_in_webm_incompatible(v3_module): """Rule 1: x265 + WebM -> INCOMPATIBLE. HEVC (x265) cannot be muxed into WebM — the WebM container only supports VP8/VP9 video and Opus/Vorbis audio. """ master = _make_master_for_compat( v3_module, codec_idx=2, # x265 (HEVC) — ffmpeg_encoder="libx265" audio_idx=0, # Opus 96k (irrelevant for this rule) container_idx=1, # WebM — ext="webm" ) warnings = master._check_combo_compatibility() assert any(w.startswith("INCOMPATIBLE:") for w in warnings), ( f"Expected an INCOMPATIBLE warning for x265+WebM, got: {warnings}" ) hevc_warning = next(w for w in warnings if w.startswith("INCOMPATIBLE:")) assert "x265" in hevc_warning or "HEVC" in hevc_warning assert "WebM" in hevc_warning def test_iamf_in_mkv_incompatible(v3_module): """Rule 2: IAMF audio + MKV -> INCOMPATIBLE. IAMF (AOMedia Immersive Audio) requires the MP4 container — MKV and WebM cannot mux the IAMF codec. """ master = _make_master_for_compat( v3_module, codec_idx=0, # AV1 (irrelevant for this rule) audio_idx=6, # IAMF — ffmpeg_encoder_name="libiamf" container_idx=0, # MKV — ext="mkv" (non-MP4) ) warnings = master._check_combo_compatibility() assert any(w.startswith("INCOMPATIBLE:") for w in warnings), ( f"Expected an INCOMPATIBLE warning for IAMF+MKV, got: {warnings}" ) iamf_warning = next(w for w in warnings if w.startswith("INCOMPATIBLE:")) assert "IAMF" in iamf_warning assert "MP4" in iamf_warning # message tells user to switch to MP4 def test_iamf_in_mp4_ok(v3_module): """Rule 2 happy path: IAMF audio + MP4 -> no INCOMPATIBLE. With AV1 video + IAMF audio + MP4 container, none of the 5 rules fire (the only audio-triggered rule for MP4 is Vorbis-in-MP4; the only video-triggered rule for MP4 is VP9-in-MP4; AV1+IAMF+MP4 hits neither). The warnings list should be empty. """ master = _make_master_for_compat( v3_module, codec_idx=0, # AV1 (not VP9, not x265) audio_idx=6, # IAMF container_idx=2, # MP4 (so _is_iamf_non_mp4 does not fire) ) warnings = master._check_combo_compatibility() assert warnings == [], ( f"Expected no warnings for AV1+IAMF+MP4, got: {warnings}" ) def test_vorbis_in_mp4_warning(v3_module): """Rule 3: Vorbis + MP4 -> WARNING. Vorbis in MP4 has limited player support — it works in some players (e.g. VLC) but not in many hardware / mobile players. Opus or MKV/WebM is the recommended alternative. """ master = _make_master_for_compat( v3_module, codec_idx=0, # AV1 (not VP9, so VP9 rule doesn't fire too) audio_idx=3, # Vorbis — ffmpeg_encoder_name="libvorbis" container_idx=2, # MP4 — ext="mp4" ) warnings = master._check_combo_compatibility() assert any(w.startswith("WARNING:") for w in warnings), ( f"Expected a WARNING for Vorbis+MP4, got: {warnings}" ) assert not any(w.startswith("INCOMPATIBLE:") for w in warnings), ( f"Vorbis+MP4 is a soft warning, not a hard incompatibility: {warnings}" ) vorbis_warning = next(w for w in warnings if w.startswith("WARNING:")) assert "Vorbis" in vorbis_warning def test_flac_in_webm_warning(v3_module): """Rule 4: FLAC + WebM -> WARNING. FLAC in WebM is rarely supported by players — MKV is the recommended container for FLAC audio. """ master = _make_master_for_compat( v3_module, codec_idx=0, # AV1 (not x265, so HEVC rule doesn't fire too) audio_idx=5, # FLAC — ffmpeg_encoder_name="flac" container_idx=1, # WebM — ext="webm" ) warnings = master._check_combo_compatibility() assert any(w.startswith("WARNING:") for w in warnings), ( f"Expected a WARNING for FLAC+WebM, got: {warnings}" ) assert not any(w.startswith("INCOMPATIBLE:") for w in warnings), ( f"FLAC+WebM is a soft warning, not a hard incompatibility: {warnings}" ) flac_warning = next(w for w in warnings if w.startswith("WARNING:")) assert "FLAC" in flac_warning def test_vp9_in_mp4_warning(v3_module): """Rule 5: VP9 + MP4 -> WARNING. VP9 in MP4 has uneven player support — WebM is the canonical VP9 container. """ master = _make_master_for_compat( v3_module, codec_idx=1, # VP9 — ffmpeg_encoder="libvpx-vp9" audio_idx=0, # Opus (not Vorbis, so Vorbis rule doesn't fire too) container_idx=2, # MP4 — ext="mp4" ) warnings = master._check_combo_compatibility() assert any(w.startswith("WARNING:") for w in warnings), ( f"Expected a WARNING for VP9+MP4, got: {warnings}" ) assert not any(w.startswith("INCOMPATIBLE:") for w in warnings), ( f"VP9+MP4 is a soft warning, not a hard incompatibility: {warnings}" ) vp9_warning = next(w for w in warnings if w.startswith("WARNING:")) assert "VP9" in vp9_warning