feat(library): add rescan_show orchestrator + walker (Step 4)
Step 4 of specs/dot_alfred.md — rebuild a TVShow aggregate from disk
by reusing the existing release pipeline (inspect_release) on every
video file in a show folder, then persist via the .alfred repository.
- alfred/application/library/walker.py — pure structural walk
(season folders detected via \bS\d{1,2}\b regex, video files
filtered against kb.video_extensions, no recursion).
- alfred/application/library/rescan.py — orchestrator that ingests
each season folder, infers PACK vs EPISODIC from on-disk file
count + parser output, and assembles via TVShowBuilder. Episode
paths stored relative to show_root. Logs + skips corrupt input
(no season parsed, mixed season numbers, unparseable episodes).
- Season now inherits MediaWithTracks: PACK seasons carry
season-level audio_tracks / subtitle_tracks; EPISODIC seasons
leave them empty (tracks live per-episode). SeasonBuilder gains
set_audio_tracks / set_subtitle_tracks; bridge writes/reads them
in the PACK branch via shared _synth_* helpers.
Out of scope, tracked as tech debt: adjacent .srt capture, multi-
episode (episode_end), TMDB-driven PACK detection (the current
heuristic '1 file == PACK' is a placeholder until ShowTracker lands).
18 new tests (11 walker + 7 rescan integration) on tmp_path with
the Foundation layout. Full suite: 1149 passed.
This commit is contained in:
@@ -17,6 +17,50 @@ callers).
|
||||
|
||||
### Added
|
||||
|
||||
- **`rescan_show` orchestrator
|
||||
(`alfred/application/library/rescan.py`).** Step 4 of the
|
||||
`specs/dot_alfred.md` plan. Walks an Alfred-managed show folder,
|
||||
runs the existing `inspect_release` pipeline on every video file it
|
||||
finds, and assembles a frozen `TVShow` aggregate persisted via the
|
||||
injected `TVShowRepository`. Reuses the release parser + ffprobe
|
||||
path verbatim — no duplicated parse/probe logic at the library
|
||||
layer. PACK vs EPISODIC inferred per season folder from the
|
||||
on-disk file count + parser output: a single video whose name
|
||||
carries no `Exx` token becomes a PACK season (tracks lifted to the
|
||||
season-level `audio_tracks` / `subtitle_tracks`), anything else
|
||||
becomes EPISODIC (one `Episode` per file). Episode paths are
|
||||
stored relative to the show root for portability. Files that fail
|
||||
to parse a season/episode number, or seasons with mixed numbers,
|
||||
are logged and skipped — the orchestrator never raises. Embedded
|
||||
subtitle tracks are captured from `ffprobe`; adjacent `.srt`
|
||||
files, multi-episode entries (`S01E01E02`), and TMDB-driven PACK
|
||||
detection are tracked as tech debt for a dedicated subtitles /
|
||||
ShowTracker session. 7 integration tests on `tmp_path` with the
|
||||
Foundation layout (S01 EPISODIC + S02 PACK) cover the round-trip
|
||||
through the real `.alfred` repository.
|
||||
- **Show tree walker (`alfred/application/library/walker.py`).**
|
||||
Step 4a foundation. `walk_show(show_root, scanner, kb)` returns a
|
||||
`ShowTree(show_root, season_folders=tuple[SeasonFolder, ...])` —
|
||||
pure structural snapshot, no parsing, no probing. Season folders
|
||||
are detected by a `\bS\d{1,2}\b` token anywhere in the directory
|
||||
name (release-style naming, no Plex `Season 01` / `Specials`
|
||||
conventions). Video files are filtered against
|
||||
`kb.video_extensions`; no recursion into sub-sub-folders. 11 unit
|
||||
tests on `tmp_path` cover detection (case-insensitive, in-word
|
||||
rejection), filtering (subs, NFO, sample files), and edge cases
|
||||
(empty / missing show root).
|
||||
- **Season-level audio/subtitle tracks
|
||||
(`alfred/domain/tv_shows/entities.py`,
|
||||
`alfred/domain/tv_shows/builders.py`).** `Season` now inherits
|
||||
from `MediaWithTracks` and carries `audio_tracks` /
|
||||
`subtitle_tracks` tuples (empty by default). Populated only in
|
||||
PACK mode (the single release covering the whole season); empty in
|
||||
EPISODIC mode where tracks live per-episode. `SeasonBuilder`
|
||||
gains `set_audio_tracks()` / `set_subtitle_tracks()` and forwards
|
||||
them through `from_existing()`. The bridge writes / reads them in
|
||||
the PACK branch via shared `_synth_audio_tracks` /
|
||||
`_synth_subtitle_tracks` helpers used for episodes too.
|
||||
|
||||
- **`DotAlfredTVShowRepository` — filesystem-backed implementation of
|
||||
the `TVShowRepository` port
|
||||
(`alfred/infrastructure/persistence/dot_alfred/repository.py`).**
|
||||
|
||||
Reference in New Issue
Block a user