Files
alfred/alfred/domain/subtitles/value_objects.py
T
francwa e45465d52d feat: split resolve_destination, persona-driven prompts, qBittorrent relocation
Destination resolution
- Replace the single ResolveDestinationUseCase with four dedicated
  functions, one per release type:
    resolve_season_destination    (pack season, folder move)
    resolve_episode_destination   (single episode, file move)
    resolve_movie_destination     (movie, file move)
    resolve_series_destination    (multi-season pack, folder move)
- Each returns a dedicated DTO carrying only the fields relevant to
  that release type — no more polymorphic ResolvedDestination with
  half the fields unused depending on the case.
- Looser series folder matching: exact computed-name match is reused
  silently; any deviation (different group, multiple candidates) now
  prompts the user with all options including the computed name.

Agent tools
- Four new tools wrapping the use cases above; old resolve_destination
  removed from the registry.
- New move_to_destination tool: create_folder + move, chained — used
  after a resolve_* call to perform the actual relocation.
- Low-level filesystem_operations module (create_folder, move via mv)
  for instant same-FS renames (ZFS).

Prompt & persona
- New PromptBuilder (alfred/agent/prompt.py) replacing prompts.py:
  identity + personality block, situational expressions, memory
  schema, episodic/STM/config context, tool catalogue.
- Per-user expression system: knowledge/users/common.yaml +
  {username}.yaml are merged at runtime; one phrase per situation
  (greeting/success/error/...) is sampled into the system prompt.

qBittorrent integration
- Credentials now come from settings (qbittorrent_url/username/password)
  instead of hardcoded defaults.
- New client methods: find_by_name, set_location, recheck — the trio
  needed to update a torrent's save path and re-verify after a move.
- Host→container path translation settings (qbittorrent_host_path /
  qbittorrent_container_path) for docker-mounted setups.

Subtitles
- Identifier: strip parenthesized qualifiers (simplified, brazil…) at
  tokenization; new _tokenize_suffix used for the episode_subfolder
  pattern so episode-stem tokens no longer pollute language detection.
- Placer: extract _build_dest_name so it can be reused by the new
  dry_run path in ManageSubtitlesUseCase.
- Knowledge: add yue, ell, ind, msa, rus, vie, heb, tam, tel, tha,
  hin, ukr; add 'fre' to fra; add 'simplified'/'traditional' to zho.

Misc
- LTM workspace: add 'trash' folder slot.
- Default LLM provider switched to deepseek.
- testing/debug_release.py: CLI to parse a release, hit TMDB, and
  dry-run the destination resolution end-to-end.
2026-05-14 05:01:59 +02:00

92 lines
2.6 KiB
Python

"""Subtitle domain value objects."""
from dataclasses import dataclass, field
from enum import Enum
class ScanStrategy(Enum):
"""How to locate subtitle files for a given release."""
ADJACENT = "adjacent" # .srt next to the video
FLAT = "flat" # Subs/*.srt
EPISODE_SUBFOLDER = "episode_subfolder" # Subs/{episode_name}/*.srt
EMBEDDED = "embedded" # tracks inside the video container
class TypeDetectionMethod(Enum):
"""How to differentiate standard / SDH / forced when tokens are ambiguous."""
TOKEN_IN_NAME = "token_in_name"
SIZE_AND_COUNT = "size_and_count"
FFPROBE_METADATA = "ffprobe_metadata"
class SubtitleType(Enum):
STANDARD = "standard"
SDH = "sdh"
FORCED = "forced"
UNKNOWN = "unknown"
@dataclass(frozen=True)
class SubtitleFormat:
"""A known subtitle file format."""
id: str
extensions: list[str]
description: str = ""
def matches_extension(self, ext: str) -> bool:
return ext.lower() in [e.lower() for e in self.extensions]
@dataclass(frozen=True)
class SubtitleLanguage:
"""A known subtitle language with its recognition tokens."""
code: str # ISO 639-1
tokens: list[str] # lowercase
def matches_token(self, token: str) -> bool:
return token.lower() in self.tokens
@dataclass(frozen=True)
class SubtitlePattern:
"""
A known structural pattern for how a release group organises subtitle files.
Patterns are loaded from alfred/knowledge/patterns/*.yaml and are
independent of any specific release group — multiple groups can share
the same pattern.
"""
id: str
description: str
scan_strategy: ScanStrategy
root_folder: str | None # e.g. "Subs", None for adjacent/embedded
type_detection: TypeDetectionMethod
version: str = "1.0"
@dataclass(frozen=True)
class SubtitleMatchingRules:
"""
Effective rules after scope resolution (global → group → show → season → episode).
Only stores actual values — None means "inherited, not overridden at this level".
"""
preferred_languages: list[str] = field(default_factory=list) # ISO 639-1 codes
preferred_formats: list[str] = field(default_factory=list) # format ids
allowed_types: list[str] = field(default_factory=list) # SubtitleType ids
format_priority: list[str] = field(default_factory=list) # ordered format ids
min_confidence: float = 0.7
@dataclass(frozen=True)
class RuleScope:
"""At which level a rule set applies."""
level: str # "global" | "release_group" | "movie" | "show" | "season" | "episode"
identifier: str | None = None # imdb_id, group name, "S01", "S01E03"…