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.
This commit is contained in:
@@ -21,6 +21,9 @@ 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:
|
||||
@@ -59,17 +62,17 @@ def _bare(**overrides) -> ParsedRelease:
|
||||
class TestQuality:
|
||||
def test_fills_when_none(self):
|
||||
p = _bare()
|
||||
enrich_from_probe(p, _info_with_video(width=1920, height=1080))
|
||||
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))
|
||||
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())
|
||||
enrich_from_probe(p, MediaInfo(), _KB)
|
||||
assert p.quality is None
|
||||
|
||||
|
||||
@@ -81,27 +84,27 @@ class TestQuality:
|
||||
class TestVideoCodec:
|
||||
def test_hevc_to_x265(self):
|
||||
p = _bare()
|
||||
enrich_from_probe(p, _info_with_video(codec="hevc"))
|
||||
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"))
|
||||
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"))
|
||||
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"))
|
||||
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())
|
||||
enrich_from_probe(p, MediaInfo(), _KB)
|
||||
assert p.codec is None
|
||||
|
||||
|
||||
@@ -119,7 +122,7 @@ class TestAudio:
|
||||
]
|
||||
)
|
||||
p = _bare()
|
||||
enrich_from_probe(p, info)
|
||||
enrich_from_probe(p, info, _KB)
|
||||
assert p.audio_codec == "EAC3"
|
||||
assert p.audio_channels == "5.1"
|
||||
|
||||
@@ -131,32 +134,32 @@ class TestAudio:
|
||||
]
|
||||
)
|
||||
p = _bare()
|
||||
enrich_from_probe(p, info)
|
||||
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)
|
||||
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)
|
||||
enrich_from_probe(p, info, _KB)
|
||||
assert p.audio_codec == "NEWCODEC"
|
||||
|
||||
def test_no_audio_tracks(self):
|
||||
p = _bare()
|
||||
enrich_from_probe(p, MediaInfo())
|
||||
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)
|
||||
enrich_from_probe(p, info, _KB)
|
||||
assert p.audio_codec == "DTS-HD.MA"
|
||||
assert p.audio_channels == "7.1"
|
||||
|
||||
@@ -175,7 +178,7 @@ class TestLanguages:
|
||||
]
|
||||
)
|
||||
p = _bare()
|
||||
enrich_from_probe(p, info)
|
||||
enrich_from_probe(p, info, _KB)
|
||||
assert p.languages == ["eng", "fre"]
|
||||
|
||||
def test_skips_und(self):
|
||||
@@ -186,7 +189,7 @@ class TestLanguages:
|
||||
]
|
||||
)
|
||||
p = _bare()
|
||||
enrich_from_probe(p, info)
|
||||
enrich_from_probe(p, info, _KB)
|
||||
assert p.languages == ["eng"]
|
||||
|
||||
def test_dedup_against_existing_case_insensitive(self):
|
||||
@@ -201,13 +204,13 @@ class TestLanguages:
|
||||
)
|
||||
p = _bare()
|
||||
p.languages = ["ENG"]
|
||||
enrich_from_probe(p, info)
|
||||
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())
|
||||
enrich_from_probe(p, MediaInfo(), _KB)
|
||||
assert p.languages == []
|
||||
|
||||
|
||||
@@ -224,7 +227,7 @@ class TestTechString:
|
||||
def test_rebuilt_from_filled_quality_and_codec(self):
|
||||
p = _bare()
|
||||
enrich_from_probe(
|
||||
p, _info_with_video(width=1920, height=1080, codec="hevc")
|
||||
p, _info_with_video(width=1920, height=1080, codec="hevc"), _KB
|
||||
)
|
||||
assert p.quality == "1080p"
|
||||
assert p.codec == "x265"
|
||||
@@ -234,7 +237,7 @@ class TestTechString:
|
||||
# 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")
|
||||
p, _info_with_video(width=1920, height=1080, codec="hevc"), _KB
|
||||
)
|
||||
assert p.tech_string == "1080p.BluRay.x265"
|
||||
|
||||
@@ -242,10 +245,10 @@ class TestTechString:
|
||||
# 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())
|
||||
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())
|
||||
enrich_from_probe(p, MediaInfo(), _KB)
|
||||
assert p.tech_string == ""
|
||||
|
||||
Reference in New Issue
Block a user