de02bdea06
- Refactor memory system (episodic/STM/LTM with components) - Implement complete subtitle domain (scanner, matcher, placer) - Add YAML workflow infrastructure - Externalize knowledge base (patterns, release groups) - Add comprehensive testing suite - Create manual testing CLIs"
91 lines
3.5 KiB
Python
91 lines
3.5 KiB
Python
"""Subtitle domain aggregates."""
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
from ..shared.value_objects import ImdbId
|
|
from .knowledge.base import SubtitleKnowledgeBase
|
|
from .value_objects import RuleScope, SubtitleMatchingRules
|
|
|
|
|
|
def DEFAULT_RULES() -> SubtitleMatchingRules:
|
|
"""Load default matching rules from subtitles.yaml (defaults section)."""
|
|
return SubtitleKnowledgeBase().default_rules()
|
|
|
|
|
|
@dataclass
|
|
class SubtitleRuleSet:
|
|
"""
|
|
Rules for subtitle selection at a given scope level, with inheritance.
|
|
|
|
Only delta fields are stored — None means "inherit from parent".
|
|
Resolution order: global → release_group → show/movie → season → episode.
|
|
|
|
A RuleSet can also be pinned to a specific media item (imdb_id),
|
|
bypassing the scope hierarchy for that item.
|
|
"""
|
|
|
|
scope: RuleScope
|
|
parent: "SubtitleRuleSet | None" = None
|
|
pinned_to: ImdbId | None = None
|
|
|
|
# Deltas — None = inherit
|
|
_languages: list[str] | None = field(default=None, repr=False)
|
|
_formats: list[str] | None = field(default=None, repr=False)
|
|
_types: list[str] | None = field(default=None, repr=False)
|
|
_format_priority: list[str] | None = field(default=None, repr=False)
|
|
_min_confidence: float | None = field(default=None, repr=False)
|
|
|
|
def resolve(self) -> SubtitleMatchingRules:
|
|
"""
|
|
Walk the parent chain and merge deltas into effective rules.
|
|
Falls back to DEFAULT_RULES at the top of the chain.
|
|
"""
|
|
base = self.parent.resolve() if self.parent else DEFAULT_RULES()
|
|
return SubtitleMatchingRules(
|
|
preferred_languages=self._languages or base.preferred_languages,
|
|
preferred_formats=self._formats or base.preferred_formats,
|
|
allowed_types=self._types or base.allowed_types,
|
|
format_priority=self._format_priority or base.format_priority,
|
|
min_confidence=self._min_confidence if self._min_confidence is not None else base.min_confidence,
|
|
)
|
|
|
|
def override(
|
|
self,
|
|
languages: list[str] | None = None,
|
|
formats: list[str] | None = None,
|
|
types: list[str] | None = None,
|
|
format_priority: list[str] | None = None,
|
|
min_confidence: float | None = None,
|
|
) -> None:
|
|
"""Set delta overrides at this scope level."""
|
|
if languages is not None:
|
|
self._languages = languages
|
|
if formats is not None:
|
|
self._formats = formats
|
|
if types is not None:
|
|
self._types = types
|
|
if format_priority is not None:
|
|
self._format_priority = format_priority
|
|
if min_confidence is not None:
|
|
self._min_confidence = min_confidence
|
|
|
|
def to_dict(self) -> dict:
|
|
"""Serialize deltas only (for persistence in rules.yaml)."""
|
|
delta: dict[str, Any] = {}
|
|
if self._languages is not None:
|
|
delta["languages"] = self._languages
|
|
if self._formats is not None:
|
|
delta["formats"] = self._formats
|
|
if self._types is not None:
|
|
delta["types"] = self._types
|
|
if self._format_priority is not None:
|
|
delta["format_priority"] = self._format_priority
|
|
if self._min_confidence is not None:
|
|
delta["min_confidence"] = self._min_confidence
|
|
return {"scope": {"level": self.scope.level, "identifier": self.scope.identifier}, "override": delta}
|
|
|
|
@classmethod
|
|
def global_default(cls) -> "SubtitleRuleSet":
|
|
return cls(scope=RuleScope(level="global"))
|