Files
alfred/tests/application/test_enrich_from_probe.py
T
francwa 0246f85ef8 refactor(release): move codec mappings from code to YAML knowledge
The three module-level dicts in enrich_from_probe (ffprobe codec name
to scene token, channel count to layout) were exactly the kind of
domain lookup table CLAUDE.md says belongs in YAML, not in Python.
Move them to alfred/knowledge/release/probe_mappings.yaml, load
through a new ReleaseKnowledge.probe_mappings port field, and add a
kb parameter to enrich_from_probe so the consumer reads the maps via
the same injection pattern as everything else.

- New knowledge file: alfred/knowledge/release/probe_mappings.yaml
- New loader: load_probe_mappings() in infrastructure/knowledge/release.py
  (normalizes channel-count keys back to int).
- Port: ReleaseKnowledge gains probe_mappings: dict.
- Adapter: YamlReleaseKnowledge populates it at __init__.
- Consumer: enrich_from_probe(parsed, info, kb) reads the three sub-maps
  from kb.probe_mappings; unknown codecs still fall back to uppercase
  raw value, same behaviour as before.
- Call sites updated: inspect_release passes kb through; the testing
  script gets its kb wiring (it was already broken since the
  ReleaseKnowledge refactor); all 22 enrich_from_probe call sites in
  tests/application/test_enrich_from_probe.py pass _KB.
2026-05-21 07:37:42 +02:00

255 lines
8.9 KiB
Python

"""Tests for ``alfred.application.release.enrich_from_probe``.
The function mutates a ``ParsedRelease`` in place using ffprobe ``MediaInfo``.
Token-level values from the release name always win — only ``None`` fields
are filled.
Coverage:
- ``TestQuality`` — resolution fill-in (and no-overwrite).
- ``TestVideoCodec`` — codec map (hevc→x265, …) + uppercase fallback.
- ``TestAudio`` — default track preferred over first; codec & channel maps
with unknown-value fallbacks.
- ``TestLanguages`` — append-only merge; ``und`` skipped; case-insensitive
duplicate suppression.
Uses real ``ParsedRelease`` / ``MediaInfo`` instances — no mocking needed.
"""
from __future__ import annotations
from alfred.application.release.enrich_from_probe import enrich_from_probe
from alfred.domain.release.value_objects import ParsedRelease
from alfred.domain.shared.media import AudioTrack, MediaInfo, VideoTrack
from alfred.infrastructure.knowledge.release_kb import YamlReleaseKnowledge
_KB = YamlReleaseKnowledge()
def _info_with_video(*, width=None, height=None, codec=None, **rest) -> MediaInfo:
"""Helper: build a MediaInfo with a single video track (the common case)."""
return MediaInfo(
video_tracks=[VideoTrack(index=0, codec=codec, width=width, height=height)],
**rest,
)
def _bare(**overrides) -> ParsedRelease:
"""Build a minimal ParsedRelease with all enrichable fields = None."""
defaults = dict(
raw="X",
clean="X",
title="X",
title_sanitized="X",
year=None,
season=None,
episode=None,
episode_end=None,
quality=None,
source=None,
codec=None,
group="UNKNOWN",
)
defaults.update(overrides)
return ParsedRelease(**defaults)
# --------------------------------------------------------------------------- #
# Quality / resolution #
# --------------------------------------------------------------------------- #
class TestQuality:
def test_fills_when_none(self):
p = _bare()
enrich_from_probe(p, _info_with_video(width=1920, height=1080), _KB)
assert p.quality == "1080p"
def test_does_not_overwrite_existing(self):
p = _bare(quality="2160p")
enrich_from_probe(p, _info_with_video(width=1920, height=1080), _KB)
assert p.quality == "2160p"
def test_no_dims_leaves_none(self):
p = _bare()
enrich_from_probe(p, MediaInfo(), _KB)
assert p.quality is None
# --------------------------------------------------------------------------- #
# Video codec #
# --------------------------------------------------------------------------- #
class TestVideoCodec:
def test_hevc_to_x265(self):
p = _bare()
enrich_from_probe(p, _info_with_video(codec="hevc"), _KB)
assert p.codec == "x265"
def test_h264_to_x264(self):
p = _bare()
enrich_from_probe(p, _info_with_video(codec="h264"), _KB)
assert p.codec == "x264"
def test_unknown_codec_uppercased(self):
p = _bare()
enrich_from_probe(p, _info_with_video(codec="weird"), _KB)
assert p.codec == "WEIRD"
def test_does_not_overwrite_existing(self):
p = _bare(codec="HEVC")
enrich_from_probe(p, _info_with_video(codec="h264"), _KB)
assert p.codec == "HEVC"
def test_no_codec_leaves_none(self):
p = _bare()
enrich_from_probe(p, MediaInfo(), _KB)
assert p.codec is None
# --------------------------------------------------------------------------- #
# Audio #
# --------------------------------------------------------------------------- #
class TestAudio:
def test_uses_default_track(self):
info = MediaInfo(
audio_tracks=[
AudioTrack(0, "aac", 2, "stereo", "eng", is_default=False),
AudioTrack(1, "eac3", 6, "5.1", "eng", is_default=True),
]
)
p = _bare()
enrich_from_probe(p, info, _KB)
assert p.audio_codec == "EAC3"
assert p.audio_channels == "5.1"
def test_falls_back_to_first_track_when_no_default(self):
info = MediaInfo(
audio_tracks=[
AudioTrack(0, "ac3", 6, "5.1", "eng"),
AudioTrack(1, "aac", 2, "stereo", "fre"),
]
)
p = _bare()
enrich_from_probe(p, info, _KB)
assert p.audio_codec == "AC3"
assert p.audio_channels == "5.1"
def test_channel_count_unknown_falls_back(self):
info = MediaInfo(audio_tracks=[AudioTrack(0, "aac", 4, "quad", "eng")])
p = _bare()
enrich_from_probe(p, info, _KB)
assert p.audio_channels == "4ch"
def test_unknown_audio_codec_uppercased(self):
info = MediaInfo(audio_tracks=[AudioTrack(0, "newcodec", 2, "stereo", "eng")])
p = _bare()
enrich_from_probe(p, info, _KB)
assert p.audio_codec == "NEWCODEC"
def test_no_audio_tracks(self):
p = _bare()
enrich_from_probe(p, MediaInfo(), _KB)
assert p.audio_codec is None
assert p.audio_channels is None
def test_does_not_overwrite_existing_audio_fields(self):
info = MediaInfo(audio_tracks=[AudioTrack(0, "ac3", 6, "5.1", "eng")])
p = _bare(audio_codec="DTS-HD.MA", audio_channels="7.1")
enrich_from_probe(p, info, _KB)
assert p.audio_codec == "DTS-HD.MA"
assert p.audio_channels == "7.1"
# --------------------------------------------------------------------------- #
# Languages #
# --------------------------------------------------------------------------- #
class TestLanguages:
def test_appends_new(self):
info = MediaInfo(
audio_tracks=[
AudioTrack(0, "aac", 2, "stereo", "eng"),
AudioTrack(1, "aac", 2, "stereo", "fre"),
]
)
p = _bare()
enrich_from_probe(p, info, _KB)
assert p.languages == ["eng", "fre"]
def test_skips_und(self):
info = MediaInfo(
audio_tracks=[
AudioTrack(0, "aac", 2, "stereo", "und"),
AudioTrack(1, "aac", 2, "stereo", "eng"),
]
)
p = _bare()
enrich_from_probe(p, info, _KB)
assert p.languages == ["eng"]
def test_dedup_against_existing_case_insensitive(self):
# existing token-level languages are typically upper-case ("FRENCH", "ENG")
# The current logic compares track.lang.upper() against existing —
# so a track with "eng" is suppressed if "ENG" is already in languages.
info = MediaInfo(
audio_tracks=[
AudioTrack(0, "aac", 2, "stereo", "eng"),
AudioTrack(1, "aac", 2, "stereo", "fre"),
]
)
p = _bare()
p.languages = ["ENG"]
enrich_from_probe(p, info, _KB)
# "eng" → upper "ENG" already present → skipped. "fre" → "FRE" new → kept.
assert p.languages == ["ENG", "fre"]
def test_no_audio_tracks_leaves_languages_empty(self):
p = _bare()
enrich_from_probe(p, MediaInfo(), _KB)
assert p.languages == []
# --------------------------------------------------------------------------- #
# tech_string #
# --------------------------------------------------------------------------- #
class TestTechString:
"""tech_string is a derived property on ParsedRelease: it always
reflects the current quality/source/codec. Enrichment never writes
it directly — it stays in sync by construction."""
def test_rebuilt_from_filled_quality_and_codec(self):
p = _bare()
enrich_from_probe(
p, _info_with_video(width=1920, height=1080, codec="hevc"), _KB
)
assert p.quality == "1080p"
assert p.codec == "x265"
assert p.tech_string == "1080p.x265"
def test_keeps_existing_source_when_enriching(self):
# Token-level source must stay; probe fills only None fields.
p = _bare(source="BluRay")
enrich_from_probe(
p, _info_with_video(width=1920, height=1080, codec="hevc"), _KB
)
assert p.tech_string == "1080p.BluRay.x265"
def test_unchanged_when_no_enrichable_video_info(self):
# No video info → nothing to fill → derived tech_string stays as it was.
p = _bare(quality="2160p", source="WEB-DL", codec="x265")
assert p.tech_string == "2160p.WEB-DL.x265"
enrich_from_probe(p, MediaInfo(), _KB)
assert p.tech_string == "2160p.WEB-DL.x265"
def test_empty_when_nothing_known(self):
p = _bare()
enrich_from_probe(p, MediaInfo(), _KB)
assert p.tech_string == ""