Commit Graph

10 Commits

Author SHA1 Message Date
felixfoertsch 495a40a6c6 restructure pipeline column headers with backward/skip/forward slots, rename ready → auto-approve
Build and Push Docker Image / build (push) Successful in 1m52s
- columnshell: three-row header (title, subtitle, backward · skip · forward grid)
- inbox: auto-processing checkbox moves into subtitle, page top-right toggle removed
- review: add ← back to inbox, rename approve-all-ready to approve auto
- queue: clear button relabeled ← back to inbox (matches existing behaviour)
- done: add ← back to review (reopen-all), clear moves to middle skip slot
- seriescard: action row mirrors movie card, now at the top
-  ready badge →  auto-approve with tooltip, review subtitle uses "auto · need decisions"
- new endpoints: /api/review/unsort-all, /api/review/reopen-all (+ tests)
2026-04-19 13:16:29 +02:00
felixfoertsch 8e1deb39d5 analyzer tests: narrow orig_lang_source type to MediaItem union
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 10:20:22 +02:00
felixfoertsch 455796ebb6 analyzer: only count commentary-driven removes as heuristic
snapshot decisions after decideAction, before deduplicateAudioByLanguage;
diff to identify tracks that flipped keep→remove due to the commentary
regex — language-driven removes no longer falsely upgrade auto_class to
auto_heuristic even when the title coincidentally matches

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 10:19:22 +02:00
felixfoertsch c595ad3792 analyzer: classify each plan as auto/auto_heuristic/manual
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 10:15:01 +02:00
felixfoertsch 4057b692ba audio: single EAC3 transcode target, prefer direct-play over lossless default
Build and Push Docker Image / build (push) Successful in 47s
two simplifications to how we pick and transcode the one-per-language
audio track, motivated by seeing inconsistent DTS → FLAC vs DTS →
EAC3 outputs in the wild:

transcode target:
- drop the FLAC path entirely. every incompatible source now targets
  EAC3 regardless of container or lossless/lossy status
- FLAC for movie audio is bad value: ~2-3× the file size vs EAC3, no
  Atmos spatial metadata (TrueHD Atmos → FLAC silently loses Atmos),
  no AVR passthrough on Apple TV
- one target = no more container-conditional surprises

winner within a language group (betterAudio):
- new priority: highest channels → Apple-compatible → default → index
- old order put 'default' on top which forced a DTS-HD MA transcode
  even when an AC3 track at equal channels was right next to it.
  flipping means AC3 beats DTS-HD MA at the same channel count — pure
  copy instead of a lossless-then-re-encode round trip
- channel count still dominates, so 7.1 TrueHD still beats 5.1 AC3
  (and gets transcoded, which is the right call for real surround)

tests: new case for DTS-HD MA default + AC3 non-default at 5.1 → AC3
wins, job_type=copy. new case for 7.1 TrueHD beats 5.1 AC3 default.
every other existing test still holds.
2026-04-14 10:23:49 +02:00
felixfoertsch 6698af020d analyzer: keep only one audio track per language, drop commentary/AD
Build and Push Docker Image / build (push) Successful in 42s
a release with 2× english (main + director's commentary, or a surround
track plus an audio-description track) was keeping both. the user only
wants one per language. rules, in priority order:

- always drop commentary / audio-description / visually-impaired /
  karaoke / sign-language tracks (matched by title regex + the
  is_hearing_impaired flag)
- within each kept-language group, pick one winner by:
  1. default disposition (main track the muxer chose)
  2. highest channel count
  3. apple-compatible codec (skip a transcode pass)
  4. lowest stream_index for stability

tests cover: commentary dropped even when it matches OG, AD flag
dropped, default beats non-default, higher channels beat default-less
candidates of equal type, Apple-compat tiebreak, per-language dedupe
runs independently, and single-stream files stay noop.
2026-04-14 10:07:02 +02:00
felixfoertsch 6d8a8fa6d6 drop the subtitle-languages setting, it never influenced extraction
Build and Push Docker Image / build (push) Successful in 53s
analyzer removes every subtitle unconditionally (see case 'Subtitle' in
decideAction) and the pipeline extracts all of them to sidecars — the config
was purely informational and only subtitles.ts echoed it back as
'keepLanguages' for a subtitle-manager ui that doesn't exist yet. we'll
revive language preferences inside that manager when it ships.

removes: the settings card + ui state, POST /api/settings/subtitle-languages,
the config default, the SUBTITLE_LANGUAGES env mapping, AnalyzerConfig's
subtitleLanguages field, RescanConfig's subtitleLanguages field, every
caller site (scan.ts / execute.ts / review.ts), and the keepLanguages
surface in subtitles.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 15:26:48 +02:00
felixfoertsch 6fcaeca82c write canonical iso3 language metadata, tighten is_noop, store full jellyfin data
Build and Push Docker Image / build (push) Failing after 16s
ffmpeg now writes -metadata:s:a:i language=<iso3> on every kept audio track so
files end up with canonical 3-letter tags (en → eng, ger → deu, null → und).
analyzer passes stream.profile (not title) to transcodeTarget so lossless
dts-hd ma in mkv correctly targets flac. is_noop also checks og-is-default and
canonical-language so pipeline-would-change-it cases stop showing as done.

normalizeLanguage gains 2→3 mapping, and mapStream no longer normalizes at
ingest so the raw jellyfin tag survives for the canonical check.

per-item scan work runs in a single db.transaction for large sqlite speedups,
extracted into server/services/rescan.ts so execute.ts can reuse it.

on successful job, execute calls jellyfin /Items/{id}/Refresh, waits for
DateLastRefreshed to change, refetches the item, and upserts it through the
same pipeline; plan flips to done iff the fresh streams satisfy is_noop.

schema wiped + rewritten to carry jellyfin_raw, external_raw, profile,
bit_depth, date_last_refreshed, runtime_ticks, original_title, last_executed_at
— so future scans aren't required to stay correct. user must drop data/*.db.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:56:19 +02:00
felixfoertsch 874f04b7a5 wire scheduler into queue, add retry, dev-reset cleanup, biome 2.4 migrate
- execute: actually call isInScheduleWindow/waitForWindow/sleepBetweenJobs in runSequential (they were dead code); emit queue_status SSE events (running/paused/sleeping/idle) so the pipeline's existing QueueStatus listener lights up
- review: POST /:id/retry resets an errored plan to approved, wipes old done/error jobs, rebuilds command from current decisions, queues fresh job
- scan: dev-mode DELETE now also wipes jobs + subtitle_files (previously orphaned after every dev reset)
- biome: migrate config to 2.4 schema, autoformat 68 files (strings + indentation), relax opinionated a11y/hooks-deps/index-key rules that don't fit this codebase
- routeTree.gen.ts regenerated after /nodes removal
2026-04-13 07:41:19 +02:00
felixfoertsch f11861658e add bun:test coverage for analyzer + ffmpeg + validate, emit ffmpeg progress sse
- analyzer.test.ts: audio keep rules (OG + configured langs, unknown OG, undetermined lang, iso alias), ordering (OG first, reorder noop), subtitle forced-remove, transcode targets
- ffmpeg.test.ts: shellQuote, sortKeptStreams canonical order, buildCommand tmp+mv, type-relative maps (0:a:N), disposition, buildPipelineCommand sub extraction + transcode bitrate, predictExtractedFiles dedup
- validate.test.ts: parseId bounds + isOneOf narrowing
- execute: parse ffmpeg Duration + time, emit job_progress SSE events throttled at 500ms so ProcessingColumn progress bar fills in (it already listened)
- package: switch test script from placeholder echo to 'bun test'
2026-04-13 07:35:24 +02:00