- analyzer: files with non-harmonized audio titles (e.g. "Chinese - Dolby
Digital - 5.1" instead of "ZHO - EAC3 · 5.1") are no longer marked as
desired-state noop. Only fires when a title already exists — null titles
are left alone to avoid processing every file just to add titles.
- export trackTitle from ffmpeg.ts so analyzer can compute expected title
- settings: unchecking auto-processing now calls stopProcessInbox() to
abort the running inbox processor
- 3 new analyzer tests for title mismatch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single-item "← Back to inbox" (reopen, unapprove) now re-probes the file
via ffprobe and re-upserts media_items + media_streams. Covers cases where
the original scan errored or the file was replaced on disk. Bulk operations
(unsort-all, reopen-all) skip the rescan to avoid hammering ffprobe.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All three processInbox callers (manual button, auto-processing toggle,
post-scan auto-process) now go through startProcessInbox() which manages
the shared abort controller. Previously only the manual button set the
abort controller, so Stop Sorting had no effect when processing was
triggered from the settings toggle or after scan completion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ColumnShell: new sort row below header border with sortOptions/sortValue/onSortChange
- inbox: ↓↑ scan time, ↓↑ name (dropdown moved from button row to sort row)
- review: classification (default), ↓↑ scan time, ↓↑ name
- queue/done: ↓↑ added, ↓↑ name (client-side sort on already-fetched arrays)
- auto-process checkbox stays visible during inbox processing, progress shows below it
- backend: unified GroupSort type replaces InboxSort, review bucket accepts sort param
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- sort state lifted to PipelinePage so loadGroups includes the sort param
on every reload (scan SSE events no longer reset the sort)
- sort dropdown moved from subtitle to ColumnShell middle slot (left of
Process Inbox button)
- ColumnShell.skip renamed to middle, accepts ReactNode or ColumnAction
- per-item "Process →" button on inbox movie cards and series cards:
POST /:id/process resolves language + reanalyzes + sorts a single item
- dashboard stat pills refresh during scan (every 25 items)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On resume, the scan filters out files already marked 'scanned' in the DB
so only unscanned files are probed. Also clears the scan_running flag on
DB init so a container killed mid-scan doesn't permanently block new scans.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The one-shot backfill for the inbox rollout ran on every startup,
setting sorted=1 on rows with sorted=0 AND auto_class IS NULL —
which is exactly the state of freshly scanned items. After the
drop-jellyfin migration wipes all tables, the backfill is obsolete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- delete server/services/jellyfin.ts, webhook.ts, mqtt.ts and their tests
- strip jellyfin/mqtt imports and startup calls from index.tsx and settings.ts
- remove /jellyfin, /mqtt, /mqtt/status, /mqtt/test, /jellyfin/webhook-plugin endpoints from settings router
- clean ENV_MAP and isEnvConfigured of jellyfin/mqtt keys
- add db/index.ts migrations for series_key, duration_seconds, scan_status, scan_error, last_scanned_at (new columns absent on older dev DBs)
- move idx_media_items_series_key out of SCHEMA into migrate() so it runs after the column is added
- fix all test fixtures: drop jellyfin_id/series_jellyfin_id column refs, update MediaItem/MediaStream object literals to match current types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove Jellyfin web links, MqttBadge, MqttSection, jellyfinUrl prop chain,
and all jellyfin_id/seriesJellyfinId references from frontend types and
components. Replace series_jellyfin_id with series_key, remove jellyfinUrl
from PipelineData, drop language_display from MediaStream.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
remove resolveSeriesTvdb from LanguageResolverConfig, rename jellyfinFallback
to probeFallback, replace Jellyfin-based TVDB resolution with series_name
title search over Sonarr library, update tests accordingly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move language normalization out of jellyfin.ts into its own module so
non-Jellyfin services (ffmpeg, radarr, sonarr, analyzer) no longer
depend on the Jellyfin service file. jellyfin.ts re-exports
normalizeLanguage for backward compatibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
all with title hover text for discoverability. saves horizontal space
so buttons never overflow their card boundaries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
the button floats top-left on hover without displacing the action row
buttons (rescan, approve series) which stay in fixed positions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
previously stop only killed the running ffmpeg process — the loop
immediately picked up the next pending job. now the abort signal
breaks the loop between jobs, so remaining items stay in the queue
column as pending jobs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
the Dead Zone case: OG is Japanese mono, English 5.1 exists but is removed
by config (audio_languages=[]). the previous check only looked at kept
streams, so it never fired. now compares OG channels against all non-OG
audio in the file — if a superior dub exists, flag for review regardless
of whether it's currently configured to be kept.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
every "← Back to inbox" button (review, queue, done, per-item unapprove,
per-item reopen) now does the same thing: reset plan to pending/unsorted,
clear auto_class, delete non-running jobs. previously unapprove left
sorted=1 (sent to review instead of inbox) and each column had its own
SQL. now consistent: back to inbox always means "needs re-processing."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
when the original language track has fewer channels than a kept non-OG
track (e.g. Japanese mono vs English 5.1), classify as auto_heuristic
instead of auto so the user can decide whether to prefer the dub.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
upsertJellyfinItem no longer runs analyzeItem or creates stream_decisions.
it inserts a minimal review_plans stub (pending, unsorted). all analysis
happens in processInbox. this means after scan, ALL items land in the
inbox — the "needs action" count equals the inbox count until processing
classifies them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
scan emits pipeline_changed every 25 items, rescan endpoints emit on
completion. pipeline page listens and throttle-reloads all column data
(1s debounce) so inbox fills progressively without manual navigation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
the oven/bun:1-slim tag resolves to a new digest frequently, busting
the apt-get ffmpeg layer (5+ minutes). switching to debian:bookworm-slim
keeps that layer stable. bun is copied as a single binary from the
build stage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
jellyfin on the left, languages + radarr + sonarr + schedule on the right.
auto-process toggle removed (available from inbox column). danger zone
stays full width at the bottom.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
processing runs in background, items appear in columns progressively via
SSE-triggered reloads. stop button aborts mid-run, remaining items stay in
inbox. remove skip/skip-all from inbox. fix column header height jitter by
giving subtitle slot a fixed height.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
the resolver was calling sonarrLang with episode-level TVDB IDs first,
missing the library, triggering an HTTP round-trip per episode. now checks
the library and resolves the correct series TVDB before calling sonarrLang.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Per-item rescan now resets plan to inbox (sorted=0, auto_class=NULL),
clears external_raw, deletes pending jobs, then lets processInbox
handle language resolution + analysis when auto_processing is on
- Add getSeriesEpisodes() to jellyfin service for bulk episode discovery
- Add POST /api/review/rescan-series endpoint accepting seriesJellyfinId
+ optional seasonNumber — discovers new episodes, resets all matching
items to inbox, refreshes streams from Jellyfin, auto-processes if on
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>