e79ca462b8
enrich_from_probe fills None fields on ParsedRelease (quality, source, codec, audio_*, languages) but left tech_string at its parser-time value — so the filename builders (movie_folder_name, episode_filename, …) saw stale tech tokens even after a successful probe. Re-derive tech_string the same way the parser does — quality.source.codec joined by dots, skipping None — at the end of enrich_from_probe. Token- level values still win because enrich only fills None fields. Four new tests in TestTechString cover: enrichment rebuilds it, existing source survives, no-info input leaves it untouched, fully empty parsed produces ''.
90 lines
2.7 KiB
Python
90 lines
2.7 KiB
Python
"""enrich_from_probe — fill missing ParsedRelease fields from MediaInfo."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from alfred.domain.release.value_objects import ParsedRelease
|
|
from alfred.domain.shared.media import MediaInfo
|
|
|
|
# Map ffprobe codec names to scene-style codec tokens
|
|
_VIDEO_CODEC_MAP = {
|
|
"hevc": "x265",
|
|
"h264": "x264",
|
|
"h265": "x265",
|
|
"av1": "AV1",
|
|
"vp9": "VP9",
|
|
"mpeg4": "XviD",
|
|
}
|
|
|
|
# Map ffprobe audio codec names to scene-style tokens
|
|
_AUDIO_CODEC_MAP = {
|
|
"eac3": "EAC3",
|
|
"ac3": "AC3",
|
|
"dts": "DTS",
|
|
"truehd": "TrueHD",
|
|
"aac": "AAC",
|
|
"flac": "FLAC",
|
|
"opus": "OPUS",
|
|
"mp3": "MP3",
|
|
"pcm_s16l": "PCM",
|
|
"pcm_s24l": "PCM",
|
|
}
|
|
|
|
# Map channel count to standard layout string
|
|
_CHANNEL_MAP = {
|
|
8: "7.1",
|
|
6: "5.1",
|
|
2: "2.0",
|
|
1: "1.0",
|
|
}
|
|
|
|
|
|
def enrich_from_probe(parsed: ParsedRelease, info: MediaInfo) -> None:
|
|
"""
|
|
Fill None fields in parsed using data from ffprobe MediaInfo.
|
|
|
|
Only overwrites fields that are currently None — token-level values
|
|
from the release name always take priority.
|
|
Mutates parsed in place.
|
|
"""
|
|
if parsed.quality is None and info.resolution:
|
|
parsed.quality = info.resolution
|
|
|
|
if parsed.codec is None and info.video_codec:
|
|
parsed.codec = _VIDEO_CODEC_MAP.get(
|
|
info.video_codec.lower(), info.video_codec.upper()
|
|
)
|
|
|
|
if parsed.bit_depth is None and info.video_codec:
|
|
# ffprobe exposes bit depth via pix_fmt — not in MediaInfo yet, skip for now
|
|
pass
|
|
|
|
# Audio — use the default track, fallback to first
|
|
default_track = next((t for t in info.audio_tracks if t.is_default), None)
|
|
track = default_track or (info.audio_tracks[0] if info.audio_tracks else None)
|
|
|
|
if track:
|
|
if parsed.audio_codec is None and track.codec:
|
|
parsed.audio_codec = _AUDIO_CODEC_MAP.get(
|
|
track.codec.lower(), track.codec.upper()
|
|
)
|
|
|
|
if parsed.audio_channels is None and track.channels:
|
|
parsed.audio_channels = _CHANNEL_MAP.get(
|
|
track.channels, f"{track.channels}ch"
|
|
)
|
|
|
|
# Languages — merge ffprobe languages with token-level ones
|
|
# "und" = undetermined, not useful
|
|
if info.audio_languages:
|
|
existing = set(parsed.languages)
|
|
for lang in info.audio_languages:
|
|
if lang.lower() != "und" and lang.upper() not in existing:
|
|
parsed.languages.append(lang)
|
|
|
|
# Re-derive tech_string so filename builders see the enriched
|
|
# quality/source/codec. Built the same way as in the parser pipeline:
|
|
# the non-None parts joined by dots, in order.
|
|
parsed.tech_string = ".".join(
|
|
p for p in (parsed.quality, parsed.source, parsed.codec) if p
|
|
)
|