Roadmap¶
What's landed, what's open, what's speculative. Keep this in sync with code changes - if a feature lands, move it from Open to a release-note entry; if a new gap surfaces, add it under Open.
Stage 2 — video (mostly landed)¶
Landed¶
- Single library root:
maneki serve <root>walks one directory recursively and auto-mounts whichever kinds it finds — no<root>/audio/or<root>/videos/subdir convention. Empty roots mount neither kind; mixed roots mount both. maneki info/list/inspect: cross-cutting top-level commands that take any library root -infofor file counts per kind,list(aliasls) for a full inventory walk,inspectfor a file's tags / cover (audio) or ffprobe streams + container info (video). Output is rich-formatted.- Base scan + raw stream: every video file under the library root is discovered recursively; flat list endpoint; HTTP Range stream (with RFC 7233 suffix-range support).
- Subtitle sidecars:
.srtdiscovery + on-the-fly conversion to WebVTT. - HLS pipeline (on-demand segments): synthesised VOD manifest with
#EXT-X-ENDLISTupfront from ffprobe duration; per-segment MPEG-TS transcode on first request, cached on disk. Seek-anywhere works without waiting for a linear transcode to catch up. Foreground concurrency capped at 3 so rapid seeks don't wedge the player; partial .ts files are unlinked on ffmpeg cancel so a cancelled seek can't poison the next request. HLS cache survives server restarts and is wiped automatically whenHLS_CACHE_VERSIONchanges (versioned marker file). - Posters + thumbnails:
/posterreturns a 16:9-padded contact-sheet PNG (3×3 timestamped frame grid + header strip with codec / resolution / duration / size);/thumbnailreturns a single mid-video JPEG. Both cached under<root>/.maneki/posters/using sha256-derived stems so deeply-nested paths can't blowNAME_MAX. Cache misses return202 Acceptedwith a tiny inline-SVG placeholder so the SPA paints instantly; the SPA polls/api/thumbnails/readyand swaps the placeholder once the real PNG / JPEG lands. Contact-sheet generation collapses 9 frame extracts into one ffmpeg invocation. - Embedded subtitle streams:
.mkv/.mp4text subtitle tracks (subrip, ass, mov_text) are surfaced through/subtitlesalongside any sidecar.srt. Extraction runs in a single ffmpeg invocation per video (one-mapoutput per stream) so a 45-track .mkv opens in ~2 s instead of stalling on 45 parallel ffmpegs that each re-read the source. Cached under<root>/.maneki/subs/<sha256(id)[:32]>/embed-<N>.vtt. The SPA registers only the default + English / English-SDH variants on player mount to keep the HLS critical path clean; the rest of the captions menu still lists them. Image-based codecs (PGS, DVD VobSub, DVB) are filtered out since they'd need OCR. --no-cover-imagesopt-out: skip the 9-frame contact-sheet phase entirely./posterfalls back to the row thumbnail. Useful on slow disks or huge libraries.--prewarm-cache(renamed from--prewarm-images): runs subtitle probe + thumbnail + contact-sheet generation at startup through a bounded worker pool; background work yields to foreground player requests via a sharedTranscodeBudget.- SQLite-backed scan index: video scan rows persist in
<root>/.maneki/index.db(same file as audio, separatevideostable, namespaced meta keys,CREATE TABLE IF NOT EXISTSso the two apps coexist). Warm rescans only ffprobe files whose(mtime, size_bytes)changed. Single-transaction batch upsert at end of scan keeps a 1300-file rescan under ~6 s. - Watcher hot-reload: watchdog
Observerrooted at the library directory drives a 5-second debounced rescan whenever a video file appears / disappears / moves. In-place edits (re-encode, retag) detect via fingerprint mismatch and invalidate the cached poster + thumbnail for that id so the next browse / open regenerates. - O(1) video lookup: every per-video endpoint reads from the in-memory
video_cachedict instead of re-walking the filesystem. - Stable, collision-free video ids:
<readable-slug>-<8-hex-sha256>so paths that flatten to the same slug (tv/ch01.mkvvstv-ch01.mkvat the root) still get distinct ids. - Orphan cache cleanup: on every startup we sweep the poster, HLS, and subtitle caches and remove entries whose source video id is no longer in the library. Catches rename / move / delete patterns without manual intervention.
- SPA video tab: vertical AUDIO / VIDEO rail on the left edge switches between modes; the rail self-hides when only one kind is mounted. Video mode collapses the audio sidebar, video.js v8 player with HLS source + WebVTT subtitle tracks. Browser fullscreen via the HTML5 Fullscreen API in plain Chrome / Safari (URL bar + tab strip stripped via
navigationUI: "hide"); CSS-pin fallback in Tauri / Electron WKWebView;fis gated onplayer.hasStarted()so pre-playback presses don't fall through to the poster<img>and open it as a new tab. - Theater mode:
tshortcut (also in the command palette) hides the videos list + splitter so the player pane spans the full video area. Topbar + mode rail stay visible. - Player meta strip: shows duration, source resolution (
1080p/4K/WxHpulled fromvideoWidth×videoHeightonloadedmetadata), file size, subtitle count. - Folder browser: SPA drives off
GET /video/api/browse?path=...- click-in directory navigator with sticky breadcrumbs at the top of the pane, folder rows above file rows, descendant video counts per folder. Empty subdirs are hidden. Path traversal guarded server-side. - Resizable video / player splitter: 6 px draggable divider between the video list pane and the player. Clamped so the player always keeps room. Per-session only.
- Kind-aware search: in video mode the topbar search swaps the folder browser for a flat results pane of filename-substring matches across the whole library; debounced 200 ms; capped at 200 results.
- House auth:
POST /auth/loginreturns bearer; SPA same-origin login form (URL field hidden when SPA is co-hosted with the server). - SPA served at
/: no/ui/prefix; API routes registered first so the StaticFiles mount at root doesn't shadow them.
Open¶
- Smaller-default caption size that doesn't break the menu: video.js's auto-scaled 100% baseline is shouty in fullscreen, but every attempt to ship a smaller default (CSS override,
setValues({fontPercent}), localStorage seed pre-init) either blocks the menu's user-picks or doesn't actually apply. Today users have to pick a size once via the menu (persisted across reloads). - Long filename handling: real release / encode filenames are very long (e.g.
Some.Movie.2019.1080p.BluRay.x264-GROUP.mkv). Row layout currently overflows / truncates. Need a parsing pass to extract a clean display title (year, source, codec stripped off, available via hover), tooltip with full filename, and wrap / ellipsis rules per density. - 2160p (4K) / heavy-codec hardware transcode: libx264 software re-encode at veryfast / crf 23 is too slow for 4K HEVC sources — segments arrive slower than playback. Add a hardware-accelerated transcode path (VideoToolbox on macOS, NVENC on NVIDIA, QSV on Intel) and a quality-tier ladder so the client can opt down to 1080p when the network or CPU can't keep up.
- HLS cache size cap + LRU eviction: orphan cleanup (renamed / removed source files) runs at every startup, but there's no cap on the cache for currently-live videos. Add
--hls-cache-gb Nwith LRU eviction once the cap is hit. - Audio-track picker: same shape as subtitles — ffprobe streams, surface a chooser in the player chrome.
- Scrub-bar hover previews: extend the contact-sheet logic into denser per-segment thumbnails for hover-on-scrub previews. Cheap re-use of existing ffmpeg infra.
- Theater-mode overlap with topbar (cosmetic): some viewport sizes show the player title strip rendering tighter than expected next to the topbar. Investigation pending.
Stage 3 — Maneki-native protocol & compat facades¶
The Subsonic API is the only audio protocol today; the video API is Maneki-native but unstable. Stage 3 is to design Maneki's own clean protocol for both kinds, then offer Subsonic / Jellyfin compat layers on top for existing clients.
- Protocol design: pick the shape (REST vs gRPC vs JSON-RPC; flat vs typed). Library, playback, search, ratings, resume. One protocol for audio + video so the SPA only speaks one dialect.
- Audio Subsonic compat facade: keep
/audio/rest/*working against Maneki-native data so existing Subsonic clients (Symfonium, Amperfy, play:Sub, Feishin) keep working. - Jellyfin / Plex compat: deferred until the native protocol is stable. Useful for Infuse / Streamio / VLC / mobile-app integration.
Audio polish (continued)¶
- AcoustID auto-enable: today you have to pass
--acoustid-keyper run. Read from~/.config/maneki/maneki.toml([acoustid].api_key) and apply automatically when an album has tagless tracks. - Album merge tool: when the same album exists with different tags as two folders, an interactive merge.
--dry-runwith rich diff: show exactly what tags would change, what files would move.
Speculative¶
Things that would be interesting if anyone ever asked, but not pursued speculatively:
- BPM / key analysis (needs
librosa, big dep weight). - AI-generated playlists with audio-feature similarity (current
maneki audio playlist genis tag-based; an audio-feature pass would need fingerprinting /librosa). - Multi-user serve (right now: single-user).
- Sonos / Chromecast / DLNA output (AirPlay covers the Apple-ecosystem case).
- Cross-fade between tracks.
- Listening rooms / sync-play across clients.
- Voice control.
- Live TV / DVR tuner support.
- Photo libraries as a third kind alongside audio + video.