Add server-side AI subtitle translation backed by any OpenAI-compatible
chat endpoint (OpenAI, Groq, a local Ollama/llama.cpp server). A viewer
picks a source track and target language in the player; the server runs a
bounded, resumable job pipeline that translates SRT/VTT cues in batches and
streams them back over the realtime websocket so playback pauses, fills in
cues near the playhead, and resumes. The finished track is persisted as an
ordinary downloaded subtitle, so it reaches every client through the
existing subtitle pipeline with no client changes.
- Job lifecycle persisted in subtitle_ai_jobs (migration 168): enqueue with
idempotency, bounded concurrency, progress/heartbeat, cancellation, and
crash recovery.
- New realtime events (subtitle_ready + subtitle_translation_*) with a
per-session notifier; the player renders a synthetic "live" track fed by
websocket cues. Timestamps never leave the server, so timing can't drift.
- Admin settings card for endpoint / model / concurrency.
Player + lifecycle hardening (from the code review of this feature):
- Hand off from the live track to the persisted track on completion
(selected by downloaded-subtitle id) and on the subtitle_ready broadcast,
so the saved track survives a reload and a mid-stream socket drop.
- Never persist the synthetic live-track sentinel index as a subtitle
preference; restore the prior selection on failure; only auto-resume
playback if the viewer was actually playing.
- Resume promptly when the playhead is past the last cue; rebuild the live
track on a new job; O(batch) live-cue ingestion instead of O(n^2).
Reliability:
- Root translation jobs in the application context so shutdown cancels them.
- Heartbeat-based stale-job reaper (safe across multiple instances) replaces
the table-wide startup reset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The shared dev DB already recorded version 166 (166_trending_blend_collection_type
from another branch), so the integer-version migration runner silently skipped our
166 and the table was never created — the trending section errored out empty.
167 is the next free version.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Login identifiers were compared case-sensitively, so "John" and "john"
were distinct accounts and a user could not log in unless they matched the
exact casing used at registration.
Convert users.username and users.email to the citext type (migration 165).
citext compares case-insensitively while preserving the originally stored
casing for display, so the existing unique constraints become
case-insensitive and `WHERE username = $1` / `email = $1` lookups match
regardless of case with no change to the query code itself.
Also add auth.NormalizeUsername/NormalizeEmail (trim-only; case preserved),
applied at the repository chokepoints (Create, Update, GetByUsername,
GetByEmail) and before validation in the create paths, so surrounding
whitespace no longer defeats matching or creates lookalike accounts.
Verified non-destructively against the dev DB: mixed-case lookups resolve
to the same row, case-variant inserts are rejected by the unique
constraint, and the down migration cleanly reverts to text.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Use ADD COLUMN IF NOT EXISTS so re-running 162 on a database that already has
the column is a no-op.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add request domain, repository, service, and reconcile task
- Add Radarr/Sonarr fulfillment adapters and TMDB discovery
- Expose user and admin request APIs with quota and approval rules
- Add web UI for browsing, requesting, and admin queue management
- Migration 139 introduces media_requests and related tables
- Map number words and ordinals (e.g. "Two"/"2nd") to digit tokens in both SQL normalize_search_text and the Go mirror so "Dune: Part Two" and "Dune Part 2" match
- Rebuild title_normalized generated column and title FTS GIN indexes (migration 138)
- Rename actor-oriented API, storage, and webhook fields to user/profile terminology
- Switch dev compose helpers to use docker-compose.yml
- Update frontend types and webhook sync settings for the new endpoints
- Wire introdb marker fetching into playback and Jellyfin compat
- Persist and expose recap/preview markers alongside intro and credits
- Add new playback/profile settings for recap and preview behavior