Target State¶
NONE of the features in this document are implemented. This document describes the planned future state of Tuvima Library. It exists to preserve design decisions and prevent accidental overlap with what is already built.
Playback and Streaming¶
Four in-browser players covering every media type in the library.
EPUB Reader¶
Route: /read/{assetId}
- Paginated CSS multi-column view
- Chapter sidebar populated from the EPUB table of contents
- Font size and family adjustment
- Dark and light reading modes
- Resume from last position
- Keyboard navigation, mobile swipe gestures
Content served via GET /read/{assetId}/chapter/{index} — EPUB chapter HTML/XHTML with embedded images and CSS.
Comic Viewer¶
Route: /read/{assetId} (comic media type)
- Full-page image display
- Page-turn navigation via click, swipe, or keyboard
- Page thumbnail sidebar
- Zoom and pan for high-resolution panels
- LTR/RTL toggle for manga
- Prefetch of next 2–3 pages
Content served via GET /comic/{assetId}/page/{pageNum} — individual images extracted from CBZ/CBR archives.
Audiobook Player¶
Rendered as a persistent bottom bar in MainLayout.razor — survives page navigation. A PlaybackStateService (scoped per circuit, in Services/Playback/) manages the active audio session and exposes play/pause/seek to any component.
- Play/pause, skip ±30 seconds
- Playback speed (0.5x–3x)
- Chapter list with direct navigation
- Sleep timer
- Progress bar with chapter position markers
Video Player¶
Route: /watch/{assetId}
- HTML5 video with HLS.js for adaptive bitrate streaming
- Subtitle track selection — SRT, VTT, ASS with on-the-fly WebVTT conversion
- Chapter markers on the scrub bar
- Playback speed adjustment
- Picture-in-Picture
- Keyboard shortcuts: Space (play/pause), F (fullscreen), M (mute), arrow keys (seek)
- "Mark as watched" triggered automatically at 90% completion threshold
Content served via:
- GET /stream/{assetId}/subtitles/{trackIndex} — subtitle extraction from MKV, converted to WebVTT
- GET /stream/{assetId}/chapters — chapter metadata from MKV/M4B
Progress Tracking¶
All four players share a common progress API:
PUT /progress/{assetId}— upserts UserState with progress percentage, last-accessed timestamp, and media-specific extended data (page number, chapter index, video timestamp)GET /progress/{assetId}— retrieves current position for resume
Progress updates are sent at configurable intervals (default: every 30 seconds, or on chapter/page change).
Authentication and Multi-User¶
Local Authentication¶
A profile selection grid on the login page shows all configured profiles (avatar + name). Selecting a profile prompts for PIN (minimum 4 digits) or password (minimum 8 characters).
Sessions are managed via secure HTTP-only cookies (or JWT for API access). A "remember me" option persists sessions for a configurable period (default: 30 days).
All user-scoped data is bound to a profileId:
- Playback and reading progress (UserState)
- Reading and watching history
- Navigation configuration
- UI theme preferences
PIN and password management is available in the Users settings tab.
Shared Journey Detection¶
When two or more profiles access the same asset within a 5-minute window, the Engine tags the session as a "Shared Journey." Solo sessions are tagged "Solo Journey." The Dashboard surfaces which journeys were shared.
Parental Controls¶
Content maturity ratings sourced from TMDB (P1657 via Wikidata) or applied manually. Profile-level maturity filter (Kids, Teen, Adult). Access to mature-rated content requires a PIN.
OIDC (Phase 2)¶
Google, Facebook, and custom OIDC provider support. Deferred until local auth is stable.
Transcoding Pipeline¶
FFmpeg Integration¶
FFmpegService wraps FFmpeg/FFprobe with:
- Auto-detection of installation paths
- Hardware capability detection: NVENC (NVIDIA), QuickSync (Intel), VAAPI (Linux AMD/Intel)
- Metadata extraction replacing the current stub (resolution, duration, codec, frame rate)
- Embedded subtitle extraction (SRT, ASS, VTT)
- Chapter extraction from MKV/MP4
On-the-Fly Transcoding¶
GET /stream/{assetId}/transcode produces HLS output (segmented .m3u8 + .ts chunks). The client sends codec capabilities as query parameters; the Engine selects a transcode profile accordingly.
Quality profiles: Original, 1080p, 720p, 480p. Hardware-accelerated encoding when available; software fallback otherwise. Active sessions are tracked per user; temporary segments are cleaned up when the session ends.
Shadow Transcoder¶
A background service that pre-creates mobile-optimised copies of video files so streaming is instant without on-the-fly transcoding.
- Runs on a configurable schedule (default: daily at 3:00 AM via
TranscodeJobcron) - Scans the library for video assets without a mobile-optimised variant
- Creates lower-bitrate variants stored at
{LibraryRoot}/.tuvima-shadow/{assetId}/{quality}.mp4 - Respects hardware limits (default: 1 concurrent transcode)
- Progress reported via
TranscodeProgressSignalR event - Shadow copies are deleted automatically when the source asset is removed
Configuration in config/transcoding.json:
{
"quality_profiles": [
{ "name": "mobile", "resolution": "720p", "codec": "h264", "bitrate": "2M" },
{ "name": "tablet", "resolution": "1080p", "codec": "h264", "bitrate": "5M" }
],
"schedule": { "enabled": true, "cron": "0 3 * * *" },
"hardware_preference": "auto",
"max_concurrent": 1,
"shadow_storage_limit_gb": 500
}
Music Domain Model¶
Music uses the same Hub/Work/Edition/MediaAsset hierarchy as all other media types, with these mappings:
| Library concept | Music concept |
|---|---|
| Hub | Album |
| Work | Track |
| Person (role: Artist) | Artist |
Track number maps to Work.SequenceIndex. Disc number is stored as the canonical value disc_number.
MusicProcessor¶
Reads tag formats: ID3v2 (MP3), Vorbis comments (FLAC/OGG), MP4 atoms (M4A/AAC).
Extracted fields: title, artist, album, track number, disc number, genre, year, album art.
Magic byte detection:
- MP3: FF FB or 49 44 33
- FLAC: 66 4C 61 43
- OGG: 4F 67 67 53
- M4A: ftyp box
Providers¶
- MusicBrainz — zero-key, config-driven. Search by artist + album or MBID. Field weights: artist 0.9, album 0.85, year 0.9, genre 0.7.
- Spotify — requires a free API key. Contributes artist headshots, album art, genre, and popularity score. Metadata only — no streaming.
Player¶
The audiobook player component (AudioPlayer.razor) is generalised to handle both audiobooks and music. Album view with track list and play queue.
Interoperability¶
OPDS 1.2 Catalog¶
Route: /opds/
Atom XML feeds for: root catalogue, search (OpenSearch), categories by media type / author / series, recently added. OPDS Page Streaming Extension for comic viewer apps.
Compatible with Moon Reader, KOReader, Calibre, Thorium Reader, and any other OPDS client.
Authentication via API key in URL parameter or HTTP Basic mapped to an Engine API key.
Audiobookshelf-Compatible API¶
A subset of the Audiobookshelf API surface, allowing existing Audiobookshelf mobile apps to connect without modification:
GET /api/librariesGET /api/items/{id}GET /api/me/listening-sessions
Webhook System¶
Configuration in config/webhooks.json: list of endpoint URLs, event subscriptions, and HMAC secrets.
Events: FileIngested, MetadataHydrated, TranscodeCompleted, PersonEnriched.
Delivery: HTTP POST with X-Tuvima-Signature HMAC-SHA256 header.
Use cases include Discord/Telegram notifications for new content and automation triggers.
Import Wizard¶
Guided import from existing media managers:
- Plex — reads
com.plexapp.plugins.library.db, maps sections to Library Folders, imports watched status - Calibre — reads
metadata.db, imports books with existing metadata intact - Jellyfin — reads NFO sidecar files alongside media, maps fields to Library claims
PWA¶
- Web app manifest + service worker for installable experience
- Offline-cached shell (the app loads without the Engine; content requires the Engine)
- Push notifications for new content via Intercom bridge to the browser Push API
Browse and Discovery Pages¶
HubDetail¶
Route: /hub/{id}
Hero artwork with dominant color extraction. Hub metadata (name, year, franchise, Wikidata QID link). Works list grouped by media type. Person credits with headshots. Social Pivot links. "Hydrate from Wikidata" button.
WorkDetail¶
Route: /work/{id}
Work metadata (title, author, year, series position). Edition list with format labels and file sizes. Claim history panel. Play/Read/Listen button.
PersonDetail¶
Route: /person/{id}
Headshot, biography, occupation. Social links (Instagram, TikTok, Mastodon, website — using Actionable URI Schemes). Works grouped by role (author, narrator, director, cast member). Pseudonym relationships where applicable.
New Home Sections¶
The Home page adds three sections above the existing Universe swimlanes:
- Continue Journey — most recently accessed, incomplete items (queries UserState)
- Recently Added — horizontal scroll of newest Hubs (via
GET /hubs/recent?limit=20) - Smart Collections — "In Progress", "New This Week", "Unread" (auto-generated from metadata, pre-computed)
Faceted filtering is added to the Home page: filter by year range, media type, and author.
Navigation Additions¶
- Breadcrumb trail: Home → Hub → Work
- Click-through from swimlane tiles to HubDetail
- Click-through from search results to WorkDetail
- "Next in series" link on WorkDetail (uses
Work.SequenceIndex)
Statistics Page¶
Route: /statistics
Library statistics and personal reading/watching history charts.
API Additions Required¶
| Method | Route | Purpose |
|---|---|---|
| GET | /hubs/recent?limit=20 |
Recently added Hubs |
| GET | /journey/continue?profileId={id}&limit=10 |
Resume items |
| GET | /hubs/{id}/works |
Works for a Hub with full canonical values |
| GET | /works/{id}/editions |
Editions for a Work with file metadata |
| GET | /persons/{id} |
Person detail with social links and linked works |
| GET | /collections |
Smart and user-created collections |
| GET | /collections/{id}/items |
Items in a collection |