Commit Graph

230 Commits

Author SHA1 Message Date
felixfoertsch 56be720494 detect wrong audio titles as not-noop, stop processing on auto-process uncheck
Build and Push Docker Image / build (push) Successful in 1m30s
- 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>
2026-04-21 10:44:50 +02:00
felixfoertsch f31e84e186 rescan file on reopen/unapprove so returning to inbox refreshes stream metadata
Build and Push Docker Image / build (push) Successful in 1m43s
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>
2026-04-21 10:33:53 +02:00
felixfoertsch 78d569189f fix stop buttons: centralize processInbox launch through abort-aware startProcessInbox
Build and Push Docker Image / build (push) Successful in 1m18s
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>
2026-04-21 10:18:57 +02:00
felixfoertsch 6721b8caf3 reset stale running jobs on startup, prevent stuck processing after container restart
Build and Push Docker Image / build (push) Successful in 2m9s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 10:07:25 +02:00
felixfoertsch 7900f450a7 sort dropdown on every column, auto-process checkbox stays visible during sorting
Build and Push Docker Image / build (push) Successful in 1m10s
- 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>
2026-04-21 09:36:20 +02:00
felixfoertsch 789a9f7bfe fix inbox sort during scan, move dropdown to button row, per-item Process button
Build and Push Docker Image / build (push) Successful in 48s
- 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>
2026-04-21 09:18:52 +02:00
felixfoertsch 6325bdb3e9 resumable scan: skip already-scanned files, clear stale scan_running on startup
Build and Push Docker Image / build (push) Successful in 57s
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>
2026-04-21 09:06:48 +02:00
felixfoertsch 7fa2404d13 remove backfill that moved unclassified inbox items to review on restart
Build and Push Docker Image / build (push) Has been cancelled
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>
2026-04-21 09:05:18 +02:00
felixfoertsch 9d65dd12be pipeline ux: actionable errors, inbox sorting, danger stop buttons, overlapping checkmarks
Build and Push Docker Image / build (push) Successful in 2m10s
- clickable error count in header shows file names + error messages
- inbox sort dropdown (scan time / name, asc / desc)
- inbox movies no longer minimal (show available badges)
- stop buttons use solid danger style, descriptive labels (Stop Scan, Stop Job, Stop Sorting)
- double checkmarks overlap like WhatsApp read receipts
- processInbox logs start/completion to stdout for Docker visibility
- fix byTitle in language-resolver test, bump to 2026.04.21.1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 08:58:08 +02:00
felixfoertsch 203c1aa154 reorder settings: languages + schedule left, radarr/sonarr right, danger zone bottom
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 08:30:35 +02:00
felixfoertsch e0fe061b74 drop old jellyfin schema on upgrade, require rescan
Build and Push Docker Image / build (push) Successful in 45s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 07:04:42 +02:00
felixfoertsch 6f4d265385 bump version to 2026.04.21
Build and Push Docker Image / build (push) Successful in 1m32s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 06:42:22 +02:00
felixfoertsch 686434f5c3 remove jellyfin, mqtt, webhook services, fix tests, add schema migrations
- 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>
2026-04-21 06:31:54 +02:00
felixfoertsch 6b01de5f30 frontend: drop jellyfin/mqtt, use series_key
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>
2026-04-21 06:31:54 +02:00
felixfoertsch 5bea914b14 review.ts: replace jellyfin_id/series_jellyfin_id with file_path/series_key, remove jellyfin imports
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 06:31:54 +02:00
felixfoertsch 6022ed09b2 simplify language-resolver: drop jellyfin resolveSeriesTvdb callback
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>
2026-04-21 06:31:54 +02:00
felixfoertsch 8a95026728 rewrite scan: filesystem walk + ffprobe, no jellyfin
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch fbfd492e18 rewrite rescan: upsertScannedItem from probe data, no jellyfin
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch 9b04ed87d2 new schema: drop jellyfin columns, file_path as unique key
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch 444d2eb733 extract normalizeLanguage, guessOriginalLanguage to language-utils.ts
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>
2026-04-21 06:31:21 +02:00
felixfoertsch 7cb2714793 add discover.ts: recursive video file discovery with Bun.Glob, unit tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch 7d12241ccb add probe.ts: ffprobe JSON parser with probeFile, parseProbeOutput, unit tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch 96bf208a16 add path-parser: extract movie/episode metadata from Radarr/Sonarr file paths
Parses title, year, provider IDs (imdb/tmdb/tvdb), season, episode number,
and container from on-disk paths without requiring Jellyfin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch 4fe651f822 add sonarr title fallback for episode language resolution, bump version
Build and Push Docker Image / build (push) Successful in 2m34s
2026-04-20 21:12:14 +02:00
felixfoertsch c11ba795e6 compact icon buttons on cards: ↻ rescan, ✓ approve, ✓✓ approve all, ↑ above
Build and Push Docker Image / build (push) Successful in 1m17s
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>
2026-04-20 17:45:55 +02:00
felixfoertsch e0a43f1a54 approve-above button uses absolute positioning, no overflow
Build and Push Docker Image / build (push) Successful in 1m33s
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>
2026-04-20 17:43:03 +02:00
felixfoertsch 089cfe8640 fix series card action row overflow: allow button wrapping
Build and Push Docker Image / build (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 17:41:46 +02:00
felixfoertsch abdfa2a790 move inbox stop button to left side (backward slot)
Build and Push Docker Image / build (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 17:41:13 +02:00
felixfoertsch 65acc683b9 stop button aborts queue loop, remaining jobs stay pending in queue
Build and Push Docker Image / build (push) Successful in 6m48s
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>
2026-04-20 15:46:31 +02:00
felixfoertsch c985bb9f04 move auto-process queue checkbox from Processing to Queue column
Build and Push Docker Image / build (push) Successful in 1m24s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 14:25:15 +02:00
felixfoertsch df08065aa4 processing bar full width, stop button shows just "Stop"
Build and Push Docker Image / build (push) Successful in 1m35s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 14:20:59 +02:00
felixfoertsch 1e749e0188 OG quality check compares against all non-OG streams in file, not just kept
Build and Push Docker Image / build (push) Successful in 3m55s
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>
2026-04-20 14:00:10 +02:00
felixfoertsch e6d3c179fe bump version to 2026.04.20.9
Build and Push Docker Image / build (push) Successful in 1m19s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 12:08:30 +02:00
felixfoertsch fd1fb8c77b unify all back-to-inbox actions via shared sendToInbox logic
Build and Push Docker Image / build (push) Successful in 1m18s
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>
2026-04-20 12:03:39 +02:00
felixfoertsch f00360b128 bump version to 2026.04.20.8
Build and Push Docker Image / build (push) Successful in 3m45s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 11:47:05 +02:00
felixfoertsch c3ee64974c flag OG quality inferior to non-OG as auto_heuristic (needs review)
Build and Push Docker Image / build (push) Successful in 59s
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>
2026-04-20 11:36:35 +02:00
felixfoertsch 3341ceed14 remove analyzer from scan, scan is now pure jellyfin ingest
Build and Push Docker Image / build (push) Successful in 1m13s
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>
2026-04-20 11:31:30 +02:00
felixfoertsch fbb8bdf272 inbox cards show title only, no badges or stream details before processing
Build and Push Docker Image / build (push) Successful in 1m12s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 11:06:29 +02:00
felixfoertsch b7f6144a6a live pipeline updates during scan via SSE pipeline_changed events
Build and Push Docker Image / build (push) Successful in 38s
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>
2026-04-20 10:57:01 +02:00
felixfoertsch ce37aec647 use debian base for runtime stage so ffmpeg layer caches independently
Build and Push Docker Image / build (push) Successful in 8m49s
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>
2026-04-20 10:34:33 +02:00
felixfoertsch b7ee1f387b cancel superseded CI builds on new push
Build and Push Docker Image / build (push) Successful in 48s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:31:12 +02:00
felixfoertsch ebbb305a58 settings: two-column layout, remove auto-process toggle, languages on top right
Build and Push Docker Image / build (push) Successful in 7m8s
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>
2026-04-20 10:15:32 +02:00
felixfoertsch 2701441a1c background processing with stop, simplify pipeline header stats, remove inbox skip
Build and Push Docker Image / build (push) Successful in 1m40s
- processInbox runs in background, returns immediately, items appear in
  columns progressively via SSE-triggered reloads
- stop button aborts mid-run, processed items stay in their destination
- fix language resolver: resolve series TVDB before sonarrLang HTTP fallback
- remove skip/skip-all from inbox (not meaningful in process-based flow)
- pipeline header shows only: total, needs action, queued, errors
- fix column header height jitter with fixed subtitle slot height

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:06:44 +02:00
felixfoertsch 43d934ff68 background processing with stop, remove inbox skip, progressive column updates, fix header height
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>
2026-04-20 09:52:58 +02:00
felixfoertsch aafe5b2ec1 fix processInbox slowness: resolve series TVDB before sonarrLang HTTP fallback
Build and Push Docker Image / build (push) Successful in 1m9s
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>
2026-04-20 09:38:04 +02:00
felixfoertsch c251d22638 pipeline is root page, full viewport width, remove pipeline nav link
Build and Push Docker Image / build (push) Successful in 2m1s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:23:55 +02:00
felixfoertsch 5aa8041f52 remove done column 50-item cap, rename Auto Review to Process Inbox, fix duplicate navigate
Build and Push Docker Image / build (push) Successful in 3m18s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:14:48 +02:00
felixfoertsch 56a1aa3b65 add rescan buttons to SeriesCard (series + season), navigate back to pipeline after detail rescan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:13:37 +02:00
felixfoertsch ffa6d95d6f rescan endpoints: reset to inbox, add series/season rescan
- 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>
2026-04-20 09:09:26 +02:00
felixfoertsch babc6414e5 processInbox auto-loads sonarr/radarr libraries when no external config provided
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:06:47 +02:00