Adds automatic image tagging that runs as a post-scan phase, sending
thumbnails to an OpenAI-compatible vision API and applying matching
tags from the user-defined tag vocabulary.
- New ai-tagger module with batch processing, failure tolerance, and
tag validation against existing vocabulary
- Admin settings page (Manage > AI Tagging) for endpoint, model, and
enable toggle with connection testing
- DB migration for ai_tagged_at tracking column and AI config seeds
- Re-tag All support to queue items for reprocessing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
media_key was a lossy shortening of item_key (libraryId:lastSegment) that
introduced a real collision bug: two TV episodes from different series with
the same filename would share the same media_key and each other's tags.
- DB migration converts existing media_tags rows from short format to full
item_key by joining against media_items; ambiguous/orphaned rows are dropped
- media_tags column renamed media_key → item_key
- Removed itemKeyToMediaKey() from scanner; reconcileAndPrune now passes
item_key directly to reKeyMediaItem
- DB reader functions (tv, movies, games) now expose item_key on returned
entities; frontend components use entity.item_key instead of constructing
the short libraryId:id form
- MixedView now constructs the full mixed_file: item_key format
- Tag API renamed mediaKey param → itemKey throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Computes a SHA-256 partial-content fingerprint (file size + first 64 KB) for
movies, TV episodes, and mixed files during scans. When a file is moved or
renamed within a library, the scan detects the fingerprint match, renames the
media_items row in-place, and updates media_tags.media_key to match — so tags
and NFO metadata survive the move transparently.
- src/lib/fingerprint.ts: new computeFingerprint() using sync FS reads
- src/lib/db.ts: fingerprint TEXT column + index migration
- src/lib/tags.ts: reKeyMediaItem() to update media_tags on rename
- src/lib/scanner.ts: replace clear+upsert with detectMoves/reconcileAndPrune
for movies, TV episodes, and mixed files; games retain clear+upsert (v1)
- TV scan restructured to a single filesystem pass (no double-scanning)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- API reads now serve from media_items cache instead of scanning the filesystem
on every request; scans (manual or scheduled) remain the write path
- NFO metadata is no longer parsed automatically during scans; title falls back
to folder/filename — metadata can be refreshed per-item via the kabob menu
- Mixed libraries are now indexed in media_items (new mixed_file item type)
with file_path stored; scanMixed walks recursively and upserts all files
- Added file_path column to media_items and migrated item_type CHECK constraint
to include mixed_file via safe table-recreation migration
- New POST /api/nfo-refresh endpoint reads the .nfo for a single item and
patches its DB row (supports movie, tv_series, tv_episode)
- Added "Refresh metadata" button to movie and TV series kabob menus
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `movies` type: per-movie folders with video files, poster/backdrop
images, and optional Jellyfin NFO metadata (title, year, plot, rating,
genres, runtime). Grid view with 2:3 poster art, detail modal with play
and two-click delete of the movie folder.
- Add `tv` type: Series -> Season -> Episode hierarchy with lazy loading at
each level. Reads tvshow.nfo and episodedetails NFO files for metadata.
Episode grid with video thumbnails, streams via existing video player.
Delete is limited to the entire series folder to avoid breaking Jellyfin.
- Add fast-xml-parser dependency for Kodi/Jellyfin NFO parsing (lib/nfo.ts)
- Migrate existing DB to expand the libraries CHECK constraint to include
the two new types; migration is idempotent and preserves existing data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>