diff --git a/assets/kiku-integration-poster.jpg b/assets/kiku-integration-poster.jpg index 9f23cd0..4f9e263 100644 Binary files a/assets/kiku-integration-poster.jpg and b/assets/kiku-integration-poster.jpg differ diff --git a/assets/kiku-integration.gif b/assets/kiku-integration.gif new file mode 100644 index 0000000..c92f3de Binary files /dev/null and b/assets/kiku-integration.gif differ diff --git a/assets/kiku-integration.mkv b/assets/kiku-integration.mkv new file mode 100644 index 0000000..97d0f90 Binary files /dev/null and b/assets/kiku-integration.mkv differ diff --git a/assets/kiku-integration.mp4 b/assets/kiku-integration.mp4 new file mode 100644 index 0000000..a3692f1 Binary files /dev/null and b/assets/kiku-integration.mp4 differ diff --git a/assets/kiku-integration.webm b/assets/kiku-integration.webm index bb995f0..4bcc2a2 100644 Binary files a/assets/kiku-integration.webm and b/assets/kiku-integration.webm differ diff --git a/assets/minecard-poster.jpg b/assets/minecard-poster.jpg new file mode 100644 index 0000000..c338624 Binary files /dev/null and b/assets/minecard-poster.jpg differ diff --git a/assets/minecard.gif b/assets/minecard.gif index a288825..4d45c78 100644 Binary files a/assets/minecard.gif and b/assets/minecard.gif differ diff --git a/assets/minecard.jpg b/assets/minecard.jpg new file mode 100644 index 0000000..0734e8b Binary files /dev/null and b/assets/minecard.jpg differ diff --git a/assets/minecard.mkv b/assets/minecard.mkv index 6b1cff6..027697f 100644 Binary files a/assets/minecard.mkv and b/assets/minecard.mkv differ diff --git a/assets/minecard.mp4 b/assets/minecard.mp4 index 3017ec2..2885b02 100644 Binary files a/assets/minecard.mp4 and b/assets/minecard.mp4 differ diff --git a/assets/minecard.webm b/assets/minecard.webm index ae930fe..4aa05d8 100644 Binary files a/assets/minecard.webm and b/assets/minecard.webm differ diff --git a/docs/architecture.md b/docs/architecture.md index f1ed391..3d968d9 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -4,7 +4,7 @@ SubMiner is split into three cooperating runtimes: - Electron desktop app (`src/`) for overlay/UI/runtime orchestration. - Launcher CLI (`launcher/`) for mpv/app command workflows. -- mpv Lua plugin (`plugin/subminer/main.lua` + module files) for player-side controls and IPC handoff. +- mpv Lua plugin (`plugin/subminer/init.lua` + module files) for player-side controls and IPC handoff. Within the desktop app, `src/main.ts` is a composition root that wires small runtime/domain modules plus core services. @@ -26,7 +26,9 @@ launcher/ # Standalone CLI launcher wrapper and mpv helpers config/ # Launcher config parsers + CLI parser builder main.ts # Launcher entrypoint and command dispatch plugin/ - subminer/ # mpv plugin modules + main entrypoint + subminer/ # Modular mpv plugin (init · main · bootstrap · lifecycle · process + # state · messages · hover · ui · options · environment · log + # binary · aniskip · aniskip_match) src/ main-entry.ts # Background-mode bootstrap wrapper before loading main.js main.ts # Entry point — delegates to runtime composers/domain modules @@ -66,24 +68,26 @@ src/ renderer/ # Overlay renderer (modularized UI/runtime) handlers/ # Keyboard/mouse interaction modules modals/ # Jimaku/Kiku/subsync/runtime-options/session-help modals - positioning/ # Invisible-layer layout + offset controllers + positioning/ # Subtitle position controller (drag-to-reposition) window-trackers/ # Backend-specific tracker implementations (Hyprland, Sway, X11, macOS) jimaku/ # Jimaku API integration helpers subsync/ # Subtitle sync (alass/ffsubsync) helpers subtitle/ # Subtitle processing utilities tokenizers/ # Tokenizer implementations + anki-integration/ # AnkiConnect proxy server + note-update enrichment workflow token-mergers/ # Token merge strategies translators/ # AI translation providers ``` ### Service Layer (`src/core/services/`) -- **Overlay/window runtime:** `overlay-manager.ts`, `overlay-window.ts`, `overlay-visibility.ts`, `overlay-bridge.ts`, `overlay-runtime-init.ts`, `overlay-content-measurement.ts`, `overlay-drop.ts` +- **Overlay/window runtime:** `overlay-manager.ts`, `overlay-window.ts`, `overlay-visibility.ts`, `overlay-bridge.ts`, `overlay-runtime-init.ts`, `overlay-content-measurement.ts` - **Shortcuts/input:** `shortcut.ts`, `overlay-shortcut.ts`, `overlay-shortcut-handler.ts`, `shortcut-fallback.ts`, `numeric-shortcut.ts` - **MPV runtime:** `mpv.ts`, `mpv-transport.ts`, `mpv-protocol.ts`, `mpv-properties.ts`, `mpv-render-metrics.ts` - **Mining + Anki/Jimaku runtime:** `mining.ts`, `field-grouping.ts`, `field-grouping-overlay.ts`, `anki-jimaku.ts`, `anki-jimaku-ipc.ts` -- **Subtitle/token pipeline:** `subtitle-processing-controller.ts`, `subtitle-position.ts`, `subtitle-ws.ts`, `tokenizer.ts` + `tokenizer/*` stage modules +- **Subtitle/token pipeline:** `subtitle-processing-controller.ts`, `subtitle-position.ts`, `subtitle-ws.ts`, `tokenizer.ts` + `tokenizer/*` stage modules (including `parser-enrichment-worker-runtime.ts` for async MeCab enrichment and `yomitan-parser-runtime.ts`) - **Integrations:** `jimaku.ts`, `subsync.ts`, `subsync-runner.ts`, `texthooker.ts`, `jellyfin.ts`, `jellyfin-remote.ts`, `discord-presence.ts`, `yomitan-extension-loader.ts`, `yomitan-settings.ts` +- **Anki integration:** `anki-integration.ts`, `anki-integration/anki-connect-proxy.ts` (local proxy for push-based auto-enrichment), `anki-integration/note-update-workflow.ts` - **Config/runtime controls:** `config-hot-reload.ts`, `runtime-options-ipc.ts`, `cli-command.ts`, `startup.ts` - **Domain submodules:** `anilist/*` (token/update queue/updater), `immersion-tracker/*` (storage/session/metadata/query/reducer) @@ -95,14 +99,15 @@ The renderer keeps `renderer.ts` focused on orchestration. UI behavior is delega src/renderer/ renderer.ts # Entrypoint/orchestration only context.ts # Shared runtime context contract - state.ts # Centralized renderer mutable state + state.ts # Centralized renderer mutable state (visible overlay only) error-recovery.ts # Global renderer error boundary + recovery actions overlay-content-measurement.ts # Reports rendered bounds to main process subtitle-render.ts # Primary/secondary subtitle rendering + style application positioning.ts # Facade export for positioning controller + yomitan-popup.ts # Yomitan popup iframe detection utilities positioning/ - controller.ts # Position controller orchestration - position-state.ts # Position state helpers + controller.ts # Subtitle drag-position controller + position-state.ts # Position state helpers (yPercent) handlers/ keyboard.ts # Keybindings, chord handling, modal key routing mouse.ts # Hover/drag behavior, selection + observer wiring @@ -120,7 +125,7 @@ src/renderer/ ### Launcher + Plugin Runtimes - `launcher/main.ts` dispatches commands through `launcher/commands/*` and shared config readers in `launcher/config/*`. It handles mpv startup, app passthrough, Jellyfin helper commands, and playback handoff. -- `plugin/subminer/main.lua` runs inside mpv and loads module files for overlay toggles, hover-token messages, and AniSkip intro-skip UX. +- `plugin/subminer/init.lua` runs inside mpv and loads modular Lua files: `main.lua` (orchestration), `bootstrap.lua` (startup), `lifecycle.lua` (connect/disconnect), `process.lua` (process management), `state.lua` (shared state), `messages.lua` (IPC), `hover.lua` (hover-token highlight rendering), `ui.lua` (OSD rendering), `options.lua` (config), `environment.lua` (detection), `log.lua` (logging), `binary.lua` (path resolution), `aniskip.lua` + `aniskip_match.lua` (intro-skip UX). ## Flow Diagram @@ -138,7 +143,7 @@ flowchart LR subgraph ExtRt["External Runtimes"] Launcher["launcher/
CLI dispatch"]:::extrt - Plugin["subminer/main.lua
mpv plugin"]:::extrt + Plugin["subminer/init.lua
mpv plugin"]:::extrt end subgraph Ext["External Systems"] @@ -161,8 +166,9 @@ flowchart LR subgraph Svc["Services — src/core/services/"] Mpv["MPV Stack
transport · protocol
properties · metrics"]:::svc - Overlay["Overlay Manager
window · visibility · bridge"]:::svc + OverlaySvc["Overlay Manager
window · visibility · bridge
mpv-sub-visibility"]:::svc Mining["Mining & Subtitles
mining · field-grouping
subtitle-ws · tokenizer"]:::svc + AnkiProxy["Anki Integration
anki-connect-proxy
note-update-workflow"]:::svc Integrations["Integrations
jimaku · subsync
texthooker · yomitan"]:::svc Tracking["Tracking
anilist · jellyfin
immersion · discord"]:::svc Config["Config & Runtime
hot-reload
runtime-options"]:::svc @@ -171,7 +177,7 @@ flowchart LR Bridge(["preload.ts
Electron IPC"]):::bridge subgraph Rend["Renderer — src/renderer/"] - Overlay["Main overlay window
primary + secondary subtitles"]:::rend + OverlayWin["Main overlay window
primary + secondary subtitles"]:::rend UI["subtitle-render
positioning
handlers · modals"]:::rend end @@ -182,16 +188,16 @@ flowchart LR Comp --> Svc mpvExt <-->|"JSON socket"| Mpv - AnkiExt <-->|"HTTP"| Mining + AnkiExt <-->|"HTTP"| AnkiProxy JimakuExt <-->|"HTTP"| Integrations - TrackerExt <-->|"platform"| Overlay + TrackerExt <-->|"platform"| OverlaySvc AnilistExt <-->|"HTTP"| Tracking JellyfinExt <-->|"HTTP"| Tracking DiscordExt <-->|"RPC"| Integrations - Overlay & Mining --> Bridge - Bridge --> Overlay - Overlay --> UI + OverlaySvc & Mining --> Bridge + Bridge --> OverlayWin + OverlayWin --> UI style Comp fill:#363a4f,stroke:#494d64,color:#cad3f5 style Svc fill:#363a4f,stroke:#494d64,color:#cad3f5 @@ -259,10 +265,10 @@ For domains migrated to reducer-style transitions (for example AniList token/que - **Module-level init:** Before `app.ready`, the composition root registers protocols, sets platform flags, constructs all services, and wires dependency injection. `runAndApplyStartupState()` parses CLI args and detects the compositor backend. - **Startup:** If `--generate-config` is passed, it writes the template and exits. Otherwise `app-lifecycle.ts` acquires the single-instance lock and registers Electron lifecycle hooks. - **Critical-path init:** Once `app.whenReady()` fires, `composeAppReadyRuntime()` runs strict config reload, resolves keybindings, creates the `MpvIpcClient` (which immediately connects and subscribes to 26 properties), and initializes the `RuntimeOptionsManager`, `SubtitleTimingTracker`, and `ImmersionTrackerService`. -- **Overlay runtime:** `initializeOverlayRuntime()` creates the primary overlay window (interactive Yomitan lookups and subtitle rendering) and registers global shortcuts and bounds tracking via the active window tracker. -- **Background warmups:** Non-critical services are launched asynchronously: MeCab tokenizer check, Yomitan extension load, JLPT + frequency dictionary prewarm, optional Jellyfin remote session, Discord presence service, and AniList token refresh. Warmup coverage is configurable through `startupWarmups` (including low-power mode that defers all but Yomitan). +- **Overlay runtime:** `initializeOverlayRuntime()` creates the primary overlay window (interactive Yomitan lookups and subtitle rendering), registers global shortcuts, and sets up bounds tracking via the active window tracker. mpv subtitle suppression is handled by a dedicated `overlay-mpv-sub-visibility` service. +- **Background warmups:** Non-critical services are launched asynchronously: MeCab tokenizer check (with async worker thread), Yomitan extension load, JLPT + frequency dictionary prewarm, optional Jellyfin remote session, Discord presence service, AniList token refresh, and optional AnkiConnect proxy server. Warmup coverage is configurable through `startupWarmups` (including low-power mode that defers all but Yomitan). - **Runtime:** Event-driven. mpv property changes, IPC messages, CLI commands, overlay shortcuts, and hot-reload notifications route through runtime handlers/composers. Subtitle text flows through `SubtitlePipeline` (normalize → tokenize → merge), and results are sent to the main overlay renderer and modal surfaces. -- **Shutdown:** `onWillQuitCleanup` destroys tray + config watcher, unregisters shortcuts, stops WebSocket + texthooker servers, closes the mpv socket + flushes OSD log, stops the window tracker, closes the Yomitan parser window, flushes the immersion tracker (SQLite), stops Jellyfin/Discord services, and cleans Anki/AniList state. +- **Shutdown:** `onWillQuitCleanup` destroys tray + config watcher, unregisters shortcuts, stops WebSocket + texthooker servers, closes the mpv socket + flushes OSD log, stops the window tracker, closes the Yomitan parser window, flushes the immersion tracker (SQLite), stops Jellyfin/Discord services, stops the AnkiConnect proxy server, and cleans Anki/AniList state. ```mermaid flowchart LR @@ -303,13 +309,14 @@ flowchart LR subgraph WarmupGroup[" "] direction TB - W1["MeCab"]:::warmup + W1["MeCab
+ worker thread"]:::warmup W2["Yomitan"]:::warmup W3["JLPT + freq
dictionaries"]:::warmup W4["Jellyfin"]:::warmup W5["Discord"]:::warmup W6["AniList"]:::warmup - W1 ~~~ W2 ~~~ W3 ~~~ W4 ~~~ W5 ~~~ W6 + W7["AnkiConnect
proxy"]:::warmup + W1 ~~~ W2 ~~~ W3 ~~~ W4 ~~~ W5 ~~~ W6 ~~~ W7 end Warmups --> WarmupGroup @@ -333,7 +340,7 @@ flowchart LR Quit --> T1["Tray · config watcher
global shortcuts"]:::shutdown Quit --> T2["WebSocket · texthooker
mpv socket · OSD log"]:::shutdown Quit --> T3["Window tracker
Yomitan parser"]:::shutdown - Quit --> T4["Immersion tracker
Jellyfin · Discord
Anki · AniList"]:::shutdown + Quit --> T4["Immersion tracker
Jellyfin · Discord
Anki proxy · AniList"]:::shutdown style Loop fill:#363a4f,stroke:#494d64,color:#cad3f5 ``` diff --git a/docs/public/assets/anki-card.svg b/docs/public/assets/anki-card.svg index bf9d4aa..7bd78fd 100644 --- a/docs/public/assets/anki-card.svg +++ b/docs/public/assets/anki-card.svg @@ -1,15 +1,40 @@ - + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/assets/highlight.svg b/docs/public/assets/highlight.svg index 98edbaa..fa933af 100644 --- a/docs/public/assets/highlight.svg +++ b/docs/public/assets/highlight.svg @@ -1,13 +1,39 @@ - + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + +1 + + + + N2 + + diff --git a/docs/public/assets/keyboard.svg b/docs/public/assets/keyboard.svg index 8148d13..bdaf25c 100644 --- a/docs/public/assets/keyboard.svg +++ b/docs/public/assets/keyboard.svg @@ -1,21 +1,31 @@ - + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + M + + + + + + diff --git a/docs/public/assets/kiku-integration-poster.jpg b/docs/public/assets/kiku-integration-poster.jpg index 9f23cd0..4f9e263 100644 Binary files a/docs/public/assets/kiku-integration-poster.jpg and b/docs/public/assets/kiku-integration-poster.jpg differ diff --git a/docs/public/assets/kiku-integration.gif b/docs/public/assets/kiku-integration.gif new file mode 100644 index 0000000..c92f3de Binary files /dev/null and b/docs/public/assets/kiku-integration.gif differ diff --git a/docs/public/assets/kiku-integration.mkv b/docs/public/assets/kiku-integration.mkv new file mode 100644 index 0000000..97d0f90 Binary files /dev/null and b/docs/public/assets/kiku-integration.mkv differ diff --git a/docs/public/assets/kiku-integration.mp4 b/docs/public/assets/kiku-integration.mp4 new file mode 100644 index 0000000..a3692f1 Binary files /dev/null and b/docs/public/assets/kiku-integration.mp4 differ diff --git a/docs/public/assets/kiku-integration.webm b/docs/public/assets/kiku-integration.webm index bb995f0..4bcc2a2 100644 Binary files a/docs/public/assets/kiku-integration.webm and b/docs/public/assets/kiku-integration.webm differ diff --git a/docs/public/assets/minecard-poster.jpg b/docs/public/assets/minecard-poster.jpg index a1e8139..c338624 100644 Binary files a/docs/public/assets/minecard-poster.jpg and b/docs/public/assets/minecard-poster.jpg differ diff --git a/docs/public/assets/minecard.gif b/docs/public/assets/minecard.gif index a288825..4d45c78 100644 Binary files a/docs/public/assets/minecard.gif and b/docs/public/assets/minecard.gif differ diff --git a/docs/public/assets/minecard.mkv b/docs/public/assets/minecard.mkv index 6b1cff6..027697f 100644 Binary files a/docs/public/assets/minecard.mkv and b/docs/public/assets/minecard.mkv differ diff --git a/docs/public/assets/minecard.mp4 b/docs/public/assets/minecard.mp4 index 3017ec2..2885b02 100644 Binary files a/docs/public/assets/minecard.mp4 and b/docs/public/assets/minecard.mp4 differ diff --git a/docs/public/assets/minecard.webm b/docs/public/assets/minecard.webm index ae930fe..4aa05d8 100644 Binary files a/docs/public/assets/minecard.webm and b/docs/public/assets/minecard.webm differ diff --git a/docs/public/assets/subtitle-download.svg b/docs/public/assets/subtitle-download.svg index 76f7dc9..f795ea2 100644 --- a/docs/public/assets/subtitle-download.svg +++ b/docs/public/assets/subtitle-download.svg @@ -1,16 +1,35 @@ - + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/assets/texthooker.svg b/docs/public/assets/texthooker.svg index 4da1998..bf305bf 100644 --- a/docs/public/assets/texthooker.svg +++ b/docs/public/assets/texthooker.svg @@ -1,19 +1,46 @@ - + + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WS diff --git a/docs/public/assets/tokenization.svg b/docs/public/assets/tokenization.svg index b74b8db..d9450d0 100644 --- a/docs/public/assets/tokenization.svg +++ b/docs/public/assets/tokenization.svg @@ -1,16 +1,34 @@ - - - + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + 42d diff --git a/scripts/mkv-to-readme-video.sh b/scripts/mkv-to-readme-video.sh index 9f017d0..2f24908 100755 --- a/scripts/mkv-to-readme-video.sh +++ b/scripts/mkv-to-readme-video.sh @@ -12,12 +12,13 @@ Description: - .mp4 (H.264 + AAC, prefers NVIDIA GPU if available) - .webm (AV1/VP9 + Opus, prefers NVIDIA GPU if available) - .gif (palette-optimised, 15 fps) + - -poster.jpg (single frame for video poster fallback) Options: -f, --force Overwrite existing output files Encoding profile: - - Crop: 1920x1080 at x=760 y=180 + - Crop: 1920x1080 at x=760 y=200 - MP4: H.264 + AAC - WebM: AV1/VP9 + Opus at 30 fps USAGE @@ -28,11 +29,11 @@ input="" while [[ $# -gt 0 ]]; do case "$1" in - -h|--help) + -h | --help) usage exit 0 ;; - -f|--force) + -f | --force) force=1 ;; -*) @@ -74,6 +75,7 @@ base="${filename%.*}" mp4_out="$dir/$base.mp4" webm_out="$dir/$base.webm" gif_out="$dir/$base.gif" +poster_out="$dir/$base-poster.jpg" overwrite_flag="-n" if [[ "$force" -eq 1 ]]; then @@ -81,7 +83,7 @@ if [[ "$force" -eq 1 ]]; then fi if [[ "$force" -eq 0 ]]; then - for output in "$mp4_out" "$webm_out" "$gif_out"; do + for output in "$mp4_out" "$webm_out" "$gif_out" "$poster_out"; do if [[ -e "$output" ]]; then echo "Error: output exists: $output (use --force to overwrite)" >&2 exit 1 @@ -94,7 +96,7 @@ has_encoder() { ffmpeg -hide_banner -encoders 2> /dev/null | grep -qE "[[:space:]]${encoder}[[:space:]]" } -crop_vf="crop=1920:1080:760:180" +crop_vf="crop=1920:1080:760:205" webm_vf="${crop_vf},fps=30" gif_vf="${crop_vf},fps=15,scale=960:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse=dither=bayer:bayer_scale=3" @@ -162,7 +164,15 @@ ffmpeg "$overwrite_flag" -i "$input" \ -vf "$gif_vf" \ "$gif_out" +echo "Generating poster: $poster_out" +ffmpeg "$overwrite_flag" -ss 00:00:05 -i "$input" \ + -vf "$crop_vf" \ + -vframes 1 \ + -q:v 2 \ + "$poster_out" + echo "Done." echo "MP4: $mp4_out" echo "WebM: $webm_out" echo "GIF: $gif_out" +echo "Poster: $poster_out"