# `src/core/subtitles.py` External subtitle sidecars — attach, discover, keep in sync on rename. Curator does not mux/embed subtitles. Jellyfin plays an **external** subtitle file natively as long as it shares the video's filename stem, with an optional language code and flag suffix:: Title (rok) [tmdbid-N].mkv Title (rok) [tmdbid-N].cs.srt # language Title (rok) [tmdbid-N].cs.forced.srt # + a flag (forced / sdh / default) Subtitle presence is a **filesystem fact**, like NFO — nothing is cached in the pool index. What Curator does own: copying a picked file into place with the right name (`attach_subtitle`), and moving a subtitle along whenever its video gets renamed so it never silently detaches (`rename_subtitles_with`). Source: [`src/core/subtitles.py`](../../src/core/subtitles.py) ## Classes ### `SubtitleFile` *class · line 30* ```python class SubtitleFile: ``` One subtitle sidecar found next to a video. #### `SubtitleFile.label` *property · line 38* ```python label -> str ``` Human-readable `jazyk (flag)` for display, e.g. `cs (forced)`. ## Functions ### `subtitle_filename` *function · line 44* ```python subtitle_filename(video_stem: str, ext: str, language: Optional[str]=None, flag: Optional[str]=None) -> str ``` Canonical subtitle filename: `{video_stem}[.language][.flag]{ext}`. ### `_parse_subtitle_name` *function · line 57* ```python _parse_subtitle_name(video_stem: str, filename: str) -> Optional[tuple[Optional[str], Optional[str]]] ``` `(language, flag)` if `filename` is a subtitle sidecar of `video_stem`. ### `find_subtitles` *function · line 83* ```python find_subtitles(video_path: Path) -> list[SubtitleFile] ``` Every external subtitle sidecar sitting next to `video_path`, sorted. ### `attach_subtitle` *function · line 101* ```python attach_subtitle(video_path: Path, source: Path, *, language: Optional[str]=None, flag: Optional[str]=None, move: bool=False) -> Path ``` Copy (or move) `source` next to `video_path`, named to match it. An existing subtitle already at that exact name (e.g. replacing the Czech track) is overwritten; nothing else next to the video is touched. Raises: ValueError: `source`'s extension isn't a recognised subtitle format. ### `rename_subtitles_with` *function · line 128* ```python rename_subtitles_with(old_video_path: Path, new_video_path: Path) -> list[Path] ``` Move every subtitle sidecar of `old_video_path` to match `new_video_path`. Call this wherever a video gets renamed/moved so an attached subtitle never silently detaches (its stem would stop matching and Jellyfin would drop it). No-op when the video's path didn't actually change. Returns the new paths.