refactor(subtitles): rename SubtitleCandidate → SubtitleScanResult
The old name conflated 'might become a placed subtitle' with 'what a scan pass produced'. The class is the output of a scan/identify pass — language/format may still be None while classification is in progress, confidence reflects classifier certainty, raw_tokens holds filename fragments under analysis. SubtitleScanResult says that directly. Pure rename + refreshed docstring; no behavior change. Touches the domain entity, the matcher/identifier/utils services, the manage_subtitles use case, the placer, the metadata store, the shared-media cross-ref comment, and 7 test modules.
This commit is contained in:
@@ -40,7 +40,7 @@ from alfred.application.filesystem.manage_subtitles import (
|
||||
_to_imdb_id,
|
||||
_to_unresolved_dto,
|
||||
)
|
||||
from alfred.domain.subtitles.entities import MediaSubtitleMetadata, SubtitleCandidate
|
||||
from alfred.domain.subtitles.entities import MediaSubtitleMetadata, SubtitleScanResult
|
||||
from alfred.application.subtitles.placer import PlacedTrack, PlaceResult
|
||||
from alfred.domain.subtitles.value_objects import (
|
||||
ScanStrategy,
|
||||
@@ -63,8 +63,8 @@ def _track(
|
||||
is_embedded: bool = False,
|
||||
raw_tokens: list[str] | None = None,
|
||||
file_size_kb: float | None = None,
|
||||
) -> SubtitleCandidate:
|
||||
return SubtitleCandidate(
|
||||
) -> SubtitleScanResult:
|
||||
return SubtitleScanResult(
|
||||
language=lang,
|
||||
format=fmt,
|
||||
subtitle_type=stype,
|
||||
|
||||
@@ -21,7 +21,7 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from alfred.domain.subtitles.entities import SubtitleCandidate
|
||||
from alfred.domain.subtitles.entities import SubtitleScanResult
|
||||
from alfred.application.subtitles.placer import (
|
||||
PlacedTrack,
|
||||
PlaceResult,
|
||||
@@ -46,8 +46,8 @@ def _track(
|
||||
fmt=SRT,
|
||||
stype=SubtitleType.STANDARD,
|
||||
is_embedded: bool = False,
|
||||
) -> SubtitleCandidate:
|
||||
return SubtitleCandidate(
|
||||
) -> SubtitleScanResult:
|
||||
return SubtitleScanResult(
|
||||
language=lang,
|
||||
format=fmt,
|
||||
subtitle_type=stype,
|
||||
|
||||
@@ -23,7 +23,7 @@ from unittest.mock import patch
|
||||
import pytest
|
||||
|
||||
from alfred.domain.shared.ports import FileEntry
|
||||
from alfred.domain.subtitles.entities import SubtitleCandidate
|
||||
from alfred.domain.subtitles.entities import SubtitleScanResult
|
||||
from alfred.domain.subtitles.services.identifier import (
|
||||
SubtitleIdentifier,
|
||||
_count_entries,
|
||||
@@ -310,8 +310,8 @@ class TestSizeDisambiguation:
|
||||
detection=TypeDetectionMethod.SIZE_AND_COUNT,
|
||||
)
|
||||
|
||||
def _track(self, lang_code: str, entries: int) -> SubtitleCandidate:
|
||||
return SubtitleCandidate(
|
||||
def _track(self, lang_code: str, entries: int) -> SubtitleScanResult:
|
||||
return SubtitleScanResult(
|
||||
language=SubtitleLanguage(code=lang_code, tokens=[lang_code]),
|
||||
format=None,
|
||||
subtitle_type=SubtitleType.UNKNOWN,
|
||||
|
||||
@@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from alfred.domain.subtitles.entities import SubtitleCandidate
|
||||
from alfred.domain.subtitles.entities import SubtitleScanResult
|
||||
from alfred.domain.subtitles.services.matcher import SubtitleMatcher
|
||||
from alfred.domain.subtitles.value_objects import (
|
||||
SubtitleFormat,
|
||||
@@ -40,8 +40,8 @@ def _track(
|
||||
stype: SubtitleType = SubtitleType.STANDARD,
|
||||
confidence: float = 1.0,
|
||||
is_embedded: bool = False,
|
||||
) -> SubtitleCandidate:
|
||||
return SubtitleCandidate(
|
||||
) -> SubtitleScanResult:
|
||||
return SubtitleScanResult(
|
||||
language=lang,
|
||||
format=fmt,
|
||||
subtitle_type=stype,
|
||||
|
||||
@@ -5,9 +5,9 @@ uncovered:
|
||||
|
||||
- ``TestSubtitleFormat`` — extension matching (case-insensitive).
|
||||
- ``TestSubtitleLanguage`` — token matching (case-insensitive).
|
||||
- ``TestSubtitleCandidateDestName`` — ``destination_name`` property:
|
||||
- ``TestSubtitleScanResultDestName`` — ``destination_name`` property:
|
||||
standard / SDH / forced naming, error on missing language or format.
|
||||
- ``TestSubtitleCandidateRepr`` — debug repr for embedded vs external.
|
||||
- ``TestSubtitleScanResultRepr`` — debug repr for embedded vs external.
|
||||
- ``TestMediaSubtitleMetadata`` — ``all_tracks`` / ``total_count`` /
|
||||
``unresolved_tracks``.
|
||||
- ``TestAvailableSubtitles`` — utility dedup by (lang, type).
|
||||
@@ -24,7 +24,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from alfred.domain.subtitles.aggregates import SubtitleRuleSet
|
||||
from alfred.domain.subtitles.entities import MediaSubtitleMetadata, SubtitleCandidate
|
||||
from alfred.domain.subtitles.entities import MediaSubtitleMetadata, SubtitleScanResult
|
||||
from alfred.domain.subtitles.services.utils import available_subtitles
|
||||
from alfred.domain.subtitles.value_objects import (
|
||||
RuleScope,
|
||||
@@ -74,7 +74,7 @@ class TestSubtitleLanguage:
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# SubtitleCandidate #
|
||||
# SubtitleScanResult #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
@@ -82,50 +82,50 @@ SRT = SubtitleFormat(id="srt", extensions=[".srt"])
|
||||
FRA = SubtitleLanguage(code="fra", tokens=["fr", "fre"])
|
||||
|
||||
|
||||
class TestSubtitleCandidateDestName:
|
||||
class TestSubtitleScanResultDestName:
|
||||
def test_standard(self):
|
||||
t = SubtitleCandidate(
|
||||
t = SubtitleScanResult(
|
||||
language=FRA, format=SRT, subtitle_type=SubtitleType.STANDARD
|
||||
)
|
||||
assert t.destination_name == "fra.srt"
|
||||
|
||||
def test_sdh(self):
|
||||
t = SubtitleCandidate(language=FRA, format=SRT, subtitle_type=SubtitleType.SDH)
|
||||
t = SubtitleScanResult(language=FRA, format=SRT, subtitle_type=SubtitleType.SDH)
|
||||
assert t.destination_name == "fra.sdh.srt"
|
||||
|
||||
def test_forced(self):
|
||||
t = SubtitleCandidate(
|
||||
t = SubtitleScanResult(
|
||||
language=FRA, format=SRT, subtitle_type=SubtitleType.FORCED
|
||||
)
|
||||
assert t.destination_name == "fra.forced.srt"
|
||||
|
||||
def test_unknown_treated_as_standard(self):
|
||||
t = SubtitleCandidate(
|
||||
t = SubtitleScanResult(
|
||||
language=FRA, format=SRT, subtitle_type=SubtitleType.UNKNOWN
|
||||
)
|
||||
# UNKNOWN doesn't add a suffix → same as standard.
|
||||
assert t.destination_name == "fra.srt"
|
||||
|
||||
def test_missing_language_raises(self):
|
||||
t = SubtitleCandidate(language=None, format=SRT)
|
||||
t = SubtitleScanResult(language=None, format=SRT)
|
||||
with pytest.raises(ValueError, match="language or format missing"):
|
||||
t.destination_name
|
||||
|
||||
def test_missing_format_raises(self):
|
||||
t = SubtitleCandidate(language=FRA, format=None)
|
||||
t = SubtitleScanResult(language=FRA, format=None)
|
||||
with pytest.raises(ValueError, match="language or format missing"):
|
||||
t.destination_name
|
||||
|
||||
def test_extension_dot_stripped(self):
|
||||
# Format extension is ".srt" — leading dot must not be duplicated.
|
||||
t = SubtitleCandidate(language=FRA, format=SRT)
|
||||
t = SubtitleScanResult(language=FRA, format=SRT)
|
||||
assert t.destination_name.endswith(".srt")
|
||||
assert ".." not in t.destination_name
|
||||
|
||||
|
||||
class TestSubtitleCandidateRepr:
|
||||
class TestSubtitleScanResultRepr:
|
||||
def test_embedded_repr(self):
|
||||
t = SubtitleCandidate(
|
||||
t = SubtitleScanResult(
|
||||
language=FRA, format=None, is_embedded=True, confidence=1.0
|
||||
)
|
||||
r = repr(t)
|
||||
@@ -135,14 +135,14 @@ class TestSubtitleCandidateRepr:
|
||||
def test_external_repr_uses_filename(self, tmp_path):
|
||||
f = tmp_path / "fr.srt"
|
||||
f.write_text("")
|
||||
t = SubtitleCandidate(language=FRA, format=SRT, file_path=f, confidence=0.85)
|
||||
t = SubtitleScanResult(language=FRA, format=SRT, file_path=f, confidence=0.85)
|
||||
r = repr(t)
|
||||
assert "fra" in r
|
||||
assert "fr.srt" in r
|
||||
assert "0.85" in r
|
||||
|
||||
def test_unresolved_repr(self):
|
||||
t = SubtitleCandidate(language=None, format=None)
|
||||
t = SubtitleScanResult(language=None, format=None)
|
||||
r = repr(t)
|
||||
assert "?" in r
|
||||
|
||||
@@ -160,8 +160,8 @@ class TestMediaSubtitleMetadata:
|
||||
assert m.unresolved_tracks == []
|
||||
|
||||
def test_aggregates_embedded_and_external(self):
|
||||
e = SubtitleCandidate(language=FRA, format=None, is_embedded=True)
|
||||
x = SubtitleCandidate(language=FRA, format=SRT, file_path=Path("/x.srt"))
|
||||
e = SubtitleScanResult(language=FRA, format=None, is_embedded=True)
|
||||
x = SubtitleScanResult(language=FRA, format=SRT, file_path=Path("/x.srt"))
|
||||
m = MediaSubtitleMetadata(
|
||||
media_id=None,
|
||||
media_type="movie",
|
||||
@@ -174,13 +174,13 @@ class TestMediaSubtitleMetadata:
|
||||
def test_unresolved_tracks_only_external_with_none_lang(self):
|
||||
# An embedded with None language must NOT appear in unresolved_tracks
|
||||
# (the property only iterates external_tracks).
|
||||
embedded_unknown = SubtitleCandidate(
|
||||
embedded_unknown = SubtitleScanResult(
|
||||
language=None, format=None, is_embedded=True
|
||||
)
|
||||
external_known = SubtitleCandidate(
|
||||
external_known = SubtitleScanResult(
|
||||
language=FRA, format=SRT, file_path=Path("/a.srt")
|
||||
)
|
||||
external_unknown = SubtitleCandidate(
|
||||
external_unknown = SubtitleScanResult(
|
||||
language=None, format=SRT, file_path=Path("/b.srt")
|
||||
)
|
||||
m = MediaSubtitleMetadata(
|
||||
@@ -201,14 +201,14 @@ class TestAvailableSubtitles:
|
||||
def test_dedup_by_lang_and_type(self):
|
||||
ENG = SubtitleLanguage(code="eng", tokens=["en"])
|
||||
tracks = [
|
||||
SubtitleCandidate(
|
||||
SubtitleScanResult(
|
||||
language=FRA, format=SRT, subtitle_type=SubtitleType.STANDARD
|
||||
),
|
||||
SubtitleCandidate(
|
||||
SubtitleScanResult(
|
||||
language=FRA, format=SRT, subtitle_type=SubtitleType.STANDARD
|
||||
),
|
||||
SubtitleCandidate(language=FRA, format=SRT, subtitle_type=SubtitleType.SDH),
|
||||
SubtitleCandidate(
|
||||
SubtitleScanResult(language=FRA, format=SRT, subtitle_type=SubtitleType.SDH),
|
||||
SubtitleScanResult(
|
||||
language=ENG, format=SRT, subtitle_type=SubtitleType.STANDARD
|
||||
),
|
||||
]
|
||||
@@ -222,10 +222,10 @@ class TestAvailableSubtitles:
|
||||
|
||||
def test_none_language_treated_as_key(self):
|
||||
# Tracks with no language form a single None-keyed bucket.
|
||||
t1 = SubtitleCandidate(
|
||||
t1 = SubtitleScanResult(
|
||||
language=None, format=SRT, subtitle_type=SubtitleType.UNKNOWN
|
||||
)
|
||||
t2 = SubtitleCandidate(
|
||||
t2 = SubtitleScanResult(
|
||||
language=None, format=SRT, subtitle_type=SubtitleType.UNKNOWN
|
||||
)
|
||||
result = available_subtitles([t1, t2])
|
||||
|
||||
@@ -16,7 +16,7 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from alfred.domain.subtitles.entities import SubtitleCandidate
|
||||
from alfred.domain.subtitles.entities import SubtitleScanResult
|
||||
from alfred.application.subtitles.placer import PlacedTrack
|
||||
from alfred.domain.subtitles.value_objects import (
|
||||
SubtitleFormat,
|
||||
@@ -32,8 +32,8 @@ ENG = SubtitleLanguage(code="eng", tokens=["en"])
|
||||
|
||||
def _track(
|
||||
lang=FRA, *, embedded: bool = False, confidence: float = 0.92
|
||||
) -> SubtitleCandidate:
|
||||
return SubtitleCandidate(
|
||||
) -> SubtitleScanResult:
|
||||
return SubtitleScanResult(
|
||||
language=lang,
|
||||
format=SRT,
|
||||
subtitle_type=SubtitleType.STANDARD,
|
||||
|
||||
Reference in New Issue
Block a user