* fix(scanner): skip misplaced TV episodes in movie libraries
A movie-type library containing a TV show laid out as
"Show/Season NN/SxxExx.mkv" (e.g. a fan "supercuts" pack) created one
bogus movie item per episode, all titled after the season folder
("Season 01"). They surfaced on Recently Added and never matched.
Add naming.IsMisplacedSeriesFile, which detects a file with an SxxExx
name inside an explicit "Season NN"/"Specials" directory, and guard
createOrFindSkeleton so such files in a strict movie library are recorded
as a skipped root (reason series_in_movie_library) instead of becoming
items. The movie match-queue worker now dequeues files the skeleton step
deliberately skips instead of erroring on the empty content id.
The guard fires on the structural signal alone, regardless of any parsed
provider id, because a "Season NN" folder otherwise yields a bogus tmdb
id (the season number). Movies whose release filename merely contains an
SxxExx substring but live in a proper "Title (Year)/" folder are
unaffected (covered by the new test).
* fix(metadata): durably exclude misplaced-series files from movie queue
Address review findings on the misplaced-TV skip:
- The skipped file's content_id is never set, so every library sync
re-enqueued it just for the worker to skip and dequeue it again.
Exclude files beneath a series_in_movie_library skipped root in the
movie match queue predicates; deleting the skipped root row makes the
files eligible again. The worker drain remains to flush rows claimed
before the root was recorded.
- Extract the eligibility predicate (previously duplicated verbatim in
eight queries) into movieQueueFileEligibleCond.
- Derive skipped-root file_count from media_files under the root via a
new UpsertObservedFile instead of hardcoding 1: a 34-episode pack now
reports 34 instead of each per-file upsert overwriting the count.
This also fixes the pre-existing missing_folder_ids undercount.
- Fold the two near-identical Upsert+log blocks in createOrFindSkeleton
into a recordSkippedRoot helper, normalize libraryType once, and name
the reason strings as constants.
- Drop a no-op filepath.Base on already-split path segments in
IsMisplacedSeriesFile.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>