Parallel to sync_show. Calls TMDBClient.get_movie_info,
combines the TmdbMovieInfo with the on-disk MovieRelease loaded
via DotAlfredMovieReleaseRepository.load_by_tmdb_id, and upserts
into DotAlfredMovieLibraryIndex.
Policy mirrors sync_show with two adaptations specific to movies:
* placeholder signature is name == metadata.path (auto-heal writes
them equal — the schema requires name to be non-empty so we can't
use name == "" as the spec originally suggested),
* when the per-movie sidecar is gone but the index entry remains,
sync warns and returns the existing entry unchanged (no upsert
possible without a release: index.upsert requires folder/imdb_id
from the MovieRelease itself).
Raises MovieNotFoundInLibrary when neither index nor sidecar
carry tmdb_id.
Mirror rescan_show for the movies library. Locates the main video via
find_video_file, runs inspect_release once (movies are one-folder-one-
main-file by convention), and writes a v2 MovieRelease sidecar via
DotAlfredMovieReleaseRepository.
Signature
rescan_movie(
movie_dir,
*,
tmdb_id: TmdbId,
imdb_id: ImdbId | None = None,
movie_repo: DotAlfredMovieReleaseRepository,
prober,
kb,
) -> MovieRelease
Behavior
* added_at = datetime.now(UTC) — the v2 sidecar records when the
release was last reconciled with disk, not filesystem mtime (which
drifts across moves and hard-links). Phase 3 made this field
required on MovieRelease.
* No TMDB call. Index auto-heals from the new sidecar on next read.
* MovieRescanFailed raised when no video is found inside movie_dir
(only explicit failure mode; all other adapter errors degrade
gracefully into empty / partial fields).
* file_path is recorded relative to movie_dir so the sidecar stays
portable across library moves.
Tests
tests/application/movies/test_rescan.py: 8 scenarios on the real v2
movie repo + real KB + stubbed prober. Covers track flattening,
sidecar round-trip, prober returning None, video in subfolder,
explicit no-video failure, imdb_id optional.
Full suite: 1233 passed / 10 skipped / 4 xfailed.