Subtitle mining tool for Emby / Jellyfin / Plex. A Rust (axum + tokio) backend watches media-server playback sessions and AnkiConnect, then enriches Anki cards with audio, screenshots, and sentence/translation context. A Svelte frontend provides the player-side UI and configuration.
src/— Rust backendapi.rs— axum HTTP/WebSocket routes, enrichment pipeline (perform_enrichment)config.rs—Configand all persisted settings (serde). Stored as JSON in SQLite (app_config)anki.rs— AnkiConnect client + the poller/AnkiBeacon push loop (run_anki_poller)mining.rs— SQLite persistence (config, media history, mined notes)session.rs— media-server session tracking, history entries, subtitle loadingmedia.rs— ffmpeg audio/screenshot/AVIF generationsubtitle.rs— subtitle parsing + sentence-to-line matchingmedia_server/— per-server adapters:mediabrowser.rs(Emby/Jellyfin),plex.rs, shared types inmod.rs
frontend/src/— Svelte applib/ConfigPage.svelte— settings UI (tabbed: Server / Anki & Media / Frontend)lib/EnrichDialog.svelte— the card enrichment dialoglib/stores.js,lib/api.js— client state and backend calls
api_reference/— third-party media-server OpenAPI specs (see below)
When working with the media-server adapters in src/media_server/, consult the
upstream OpenAPI specifications in api_reference/ rather than
guessing endpoint shapes or field names:
api_reference/emby_openapi.json— Embyapi_reference/jellyfin-openapi-stable.json— Jellyfinapi_reference/plex_openapi.json— Plex
These are large; search them for the specific endpoint/field instead of reading them whole.
- Backend:
cargo check/cargo build/cargo test(run from repo root) - Frontend:
npm run build(ornpm run dev) fromfrontend/
- Config is persisted via SQLite, not a flat file;
Config::save_tois legacy. Themining.auto_approvefield is a migrated client-local setting and is stripped server-side. - Series tagging (
anki.series_tag_enabled/anki.series_tag_parent): when enabled, enrichment adds a per-series tag inperform_enrichment. With a non-empty parent it isparent::Series_Name; with an empty parent it is justSeries_Name. The series name comes from the media server's metadata (NowPlaying.series_name), plumbed throughNowPlayingState/HistoryEntry(persisted in themedia_historytable) andMediaContext; movies fall back to the bare title.