Overlay 2.0 (#12)
@@ -69,6 +69,7 @@ export default {
|
||||
{ text: 'Launcher Script', link: '/launcher-script' },
|
||||
{ text: 'Usage', link: '/usage' },
|
||||
{ text: 'Mining Workflow', link: '/mining-workflow' },
|
||||
// { text: 'Feature Demos', link: '/demos' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,13 +16,13 @@ make docs-preview # Preview built site at http://localhost:4173
|
||||
|
||||
- [Installation](/installation) — Requirements, Linux/macOS/Windows install, mpv plugin setup
|
||||
- [Usage](/usage) — `subminer` wrapper + subcommands (`jellyfin`, `yt`, `doctor`, `config`, `mpv`, `texthooker`, `app`), mpv plugin, keybindings
|
||||
- [Mining Workflow](/mining-workflow) — End-to-end sentence mining guide, overlay layers, card creation
|
||||
- [Mining Workflow](/mining-workflow) — End-to-end sentence mining guide, single overlay + modals, card creation
|
||||
|
||||
### Reference
|
||||
|
||||
- [Configuration](/configuration) — Full config file reference and option details
|
||||
- [Keyboard Shortcuts](/shortcuts) — All global, overlay, mining, and plugin chord shortcuts in one place
|
||||
- [Anki Integration](/anki-integration) — AnkiConnect setup, field mapping, media generation, field grouping
|
||||
- [Anki Integration](/anki-integration) — AnkiConnect setup, proxy/polling transport, field mapping, media generation, field grouping
|
||||
- [Jellyfin Integration](/jellyfin-integration) — Optional Jellyfin auth, cast discovery, remote control, and playback launch
|
||||
- [Immersion Tracking](/immersion-tracking) — SQLite schema, retention/rollup policies, query templates, and extension points
|
||||
- [Performance & Tuning](/troubleshooting#performance-and-resource-impact) — Resource usage and practical low-impact profile
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Anki Integration
|
||||
|
||||
SubMiner uses the [AnkiConnect](https://ankiweb.net/shared/info/2055492159) add-on to create and update Anki cards with sentence context, audio, and screenshots.
|
||||
This project is built primarily for [Kiku](https://kiku.youyoumu.my.id/) and [Lapis](https://github.com/donkuri/lapis) note types, including sentence-card and field-grouping behavior.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -10,9 +11,14 @@ SubMiner uses the [AnkiConnect](https://ankiweb.net/shared/info/2055492159) add-
|
||||
|
||||
AnkiConnect listens on `http://127.0.0.1:8765` by default. If you changed the port in AnkiConnect's settings, update `ankiConnect.url` in your SubMiner config.
|
||||
|
||||
## How Polling Works
|
||||
## Auto-Enrichment Transport
|
||||
|
||||
SubMiner polls AnkiConnect at a regular interval (default: 3 seconds, configurable via `ankiConnect.pollingRate`) to detect new cards. When it finds a card that was added since the last poll:
|
||||
SubMiner supports two auto-enrichment transport modes:
|
||||
|
||||
1. `proxy` (default): runs a local AnkiConnect-compatible proxy and enriches cards immediately after successful `addNote` / `addNotes` / `multi` responses.
|
||||
2. `polling`: polls AnkiConnect at `ankiConnect.pollingRate` (default: 3s).
|
||||
|
||||
In both modes, the enrichment workflow is the same:
|
||||
|
||||
1. Checks if a duplicate expression already exists (for field grouping).
|
||||
2. Updates the sentence field with the current subtitle.
|
||||
@@ -20,7 +26,83 @@ SubMiner polls AnkiConnect at a regular interval (default: 3 seconds, configurab
|
||||
4. Fills the translation field from the secondary subtitle or AI.
|
||||
5. Writes metadata to the miscInfo field.
|
||||
|
||||
Polling uses the query `"deck:<your-deck>" added:1` to find recently added cards. If no deck is configured, it searches all decks.
|
||||
Polling mode uses the query `"deck:<your-deck>" added:1` to find recently added cards. If no deck is configured, it searches all decks.
|
||||
|
||||
### Proxy Mode Setup (Yomitan / Texthooker)
|
||||
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"url": "http://127.0.0.1:8765", // real AnkiConnect
|
||||
"proxy": {
|
||||
"enabled": true,
|
||||
"host": "127.0.0.1",
|
||||
"port": 8766,
|
||||
"upstreamUrl": "http://127.0.0.1:8765"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then point Yomitan/clients to `http://127.0.0.1:8766` instead of `8765`.
|
||||
|
||||
When SubMiner loads the bundled Yomitan extension, it also attempts to update the **default Yomitan profile** (`profiles[0].options.anki.server`) to the active SubMiner endpoint:
|
||||
|
||||
- proxy URL when `ankiConnect.proxy.enabled` is `true`
|
||||
- direct `ankiConnect.url` when proxy mode is disabled
|
||||
|
||||
To avoid clobbering custom setups, this auto-update only changes the default profile when its current server is blank or the stock Yomitan default (`http://127.0.0.1:8765`).
|
||||
|
||||
For browser-based Yomitan or other external clients (for example Texthooker in a normal browser profile), set their Anki server to the same proxy URL separately: `http://127.0.0.1:8766` (or your configured `proxy.host` + `proxy.port`).
|
||||
|
||||
### Browser/Yomitan external setup (separate profile)
|
||||
|
||||
If you want SubMiner to use proxy mode without touching your main/default Yomitan profile, create or select a separate Yomitan profile just for SubMiner and set its Anki server to the proxy URL.
|
||||
|
||||
That profile isolation gives you both benefits:
|
||||
|
||||
- SubMiner can auto-enrich immediately via proxy.
|
||||
- Your default Yomitan profile keeps its existing Anki server setting.
|
||||
|
||||
In Yomitan, go to Settings → Profile and:
|
||||
|
||||
1. Create a profile for SubMiner (or choose one dedicated profile).
|
||||
2. Open Anki settings for that profile.
|
||||
3. Set server to `http://127.0.0.1:8766` (or your configured proxy URL).
|
||||
4. Save and make that profile active when using SubMiner.
|
||||
|
||||
This is only for non-bundled, external/browser Yomitan or other clients. The bundled profile auto-update logic only targets `profiles[0]` when it is blank or still default.
|
||||
|
||||
### Proxy Troubleshooting (quick checks)
|
||||
|
||||
If auto-enrichment appears to do nothing:
|
||||
|
||||
1. Confirm proxy listener is running while SubMiner is active:
|
||||
|
||||
```bash
|
||||
ss -ltnp | rg 8766
|
||||
```
|
||||
|
||||
2. Confirm requests can pass through the proxy:
|
||||
|
||||
```bash
|
||||
curl -sS http://127.0.0.1:8766 \
|
||||
-H 'content-type: application/json' \
|
||||
-d '{"action":"version","version":2}'
|
||||
```
|
||||
|
||||
3. Check both log sinks:
|
||||
|
||||
- Launcher/mpv-integrated log: `~/.cache/SubMiner/mp.log`
|
||||
- App runtime log: `~/.config/SubMiner/logs/SubMiner-YYYY-MM-DD.log`
|
||||
|
||||
4. Ensure config JSONC is valid and logging shape is correct:
|
||||
|
||||
```jsonc
|
||||
"logging": {
|
||||
"level": "debug"
|
||||
}
|
||||
```
|
||||
|
||||
`"logging": "debug"` is invalid for current schema and can break reload/start behavior.
|
||||
|
||||
## Field Mapping
|
||||
|
||||
@@ -186,17 +268,17 @@ When you mine the same word multiple times, SubMiner can merge the cards instead
|
||||
|
||||
**Disabled** (`"disabled"`): No duplicate detection. Each card is independent.
|
||||
|
||||
**Auto** (`"auto"`): When a duplicate expression is found, SubMiner merges the new card into the existing one automatically. Both sentences, audio clips, and images are preserved. If `deleteDuplicateInAuto` is true, the new card is deleted after merging.
|
||||
**Auto** (`"auto"`): When a duplicate expression is found, SubMiner merges the new card into the existing one automatically. Both sentences, audio clips, and images are preserved, and exact duplicate values are collapsed to one entry. If `deleteDuplicateInAuto` is true, the new card is deleted after merging.
|
||||
|
||||
**Manual** (`"manual"`): A modal appears in the overlay showing both cards. You choose which card to keep, preview the merge result, then confirm. The modal has a 90-second timeout, after which it cancels automatically.
|
||||
|
||||
### What Gets Merged
|
||||
|
||||
| Field | Merge behavior |
|
||||
| -------- | -------------------------------------------------------------- |
|
||||
| Sentence | Both sentences preserved, labeled `[Original]` / `[Duplicate]` |
|
||||
| Audio | Both `[sound:...]` entries kept |
|
||||
| Image | Both images kept |
|
||||
| Field | Merge behavior |
|
||||
| -------- | --------------------------------------------------------------- |
|
||||
| Sentence | Both sentences preserved (exact duplicate text is deduplicated) |
|
||||
| Audio | Both `[sound:...]` entries kept (exact duplicates deduplicated) |
|
||||
| Image | Both images kept (exact duplicates deduplicated) |
|
||||
|
||||
### Keyboard Shortcuts in the Modal
|
||||
|
||||
@@ -214,6 +296,12 @@ When you mine the same word multiple times, SubMiner can merge the cards instead
|
||||
"enabled": true,
|
||||
"url": "http://127.0.0.1:8765",
|
||||
"pollingRate": 3000,
|
||||
"proxy": {
|
||||
"enabled": false,
|
||||
"host": "127.0.0.1",
|
||||
"port": 8766,
|
||||
"upstreamUrl": "http://127.0.0.1:8765",
|
||||
},
|
||||
"fields": {
|
||||
"audio": "ExpressionAudio",
|
||||
"image": "Picture",
|
||||
|
||||
@@ -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.lua`) 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.lua # mpv plugin (auto-start, IPC, AniSkip + hover controls)
|
||||
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-window-geometry.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,15 +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
|
||||
invisible-layout*.ts # Invisible layer layout computations
|
||||
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
|
||||
@@ -121,11 +125,11 @@ 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.lua` runs inside mpv and handles IPC startup checks, 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
|
||||
|
||||
The main process has three layers: `main.ts` delegates to composition modules that wire together domain services. Three overlay windows (visible, invisible, secondary) run in separate Electron renderer processes, connected through `preload.ts`. External runtimes (launcher CLI and mpv plugin) operate independently and communicate via IPC socket or CLI passthrough.
|
||||
The main process orchestrates a single primary overlay window plus modal surfaces: `main.ts` delegates to composition modules that wire together domain services. Subtitle layers (primary + secondary bar) are rendered in the same overlay renderer process, connected through `preload.ts`. External runtimes (launcher CLI and mpv plugin) operate independently and communicate via IPC socket or CLI passthrough.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -139,7 +143,7 @@ flowchart LR
|
||||
|
||||
subgraph ExtRt["External Runtimes"]
|
||||
Launcher["launcher/<br/>CLI dispatch"]:::extrt
|
||||
Plugin["subminer.lua<br/>mpv plugin"]:::extrt
|
||||
Plugin["subminer/init.lua<br/>mpv plugin"]:::extrt
|
||||
end
|
||||
|
||||
subgraph Ext["External Systems"]
|
||||
@@ -162,8 +166,9 @@ flowchart LR
|
||||
|
||||
subgraph Svc["Services — src/core/services/"]
|
||||
Mpv["MPV Stack<br/>transport · protocol<br/>properties · metrics"]:::svc
|
||||
Overlay["Overlay Manager<br/>window · geometry<br/>visibility · bridge"]:::svc
|
||||
OverlaySvc["Overlay Manager<br/>window · visibility · bridge<br/>mpv-sub-visibility"]:::svc
|
||||
Mining["Mining & Subtitles<br/>mining · field-grouping<br/>subtitle-ws · tokenizer"]:::svc
|
||||
AnkiProxy["Anki Integration<br/>anki-connect-proxy<br/>note-update-workflow"]:::svc
|
||||
Integrations["Integrations<br/>jimaku · subsync<br/>texthooker · yomitan"]:::svc
|
||||
Tracking["Tracking<br/>anilist · jellyfin<br/>immersion · discord"]:::svc
|
||||
Config["Config & Runtime<br/>hot-reload<br/>runtime-options"]:::svc
|
||||
@@ -172,9 +177,7 @@ flowchart LR
|
||||
Bridge(["preload.ts<br/>Electron IPC"]):::bridge
|
||||
|
||||
subgraph Rend["Renderer — src/renderer/"]
|
||||
Visible["Visible window<br/>Yomitan lookups"]:::rend
|
||||
Invisible["Invisible window<br/>mpv positioning"]:::rend
|
||||
Secondary["Secondary window<br/>subtitle bar"]:::rend
|
||||
OverlayWin["Main overlay window<br/>primary + secondary subtitles"]:::rend
|
||||
UI["subtitle-render<br/>positioning<br/>handlers · modals"]:::rend
|
||||
end
|
||||
|
||||
@@ -185,18 +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 --> Visible
|
||||
Bridge --> Invisible
|
||||
Bridge --> Secondary
|
||||
Visible & Invisible & Secondary --> UI
|
||||
OverlaySvc & Mining --> Bridge
|
||||
Bridge --> OverlayWin
|
||||
OverlayWin --> UI
|
||||
|
||||
style Comp fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
style Svc fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
@@ -264,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 three overlay windows — **visible** (interactive Yomitan lookups), **invisible** (mpv-matched subtitle positioning), and **secondary** (secondary subtitle bar, top 20% via `splitOverlayGeometryForSecondaryBar`) — then registers global shortcuts and sets initial bounds from the 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.
|
||||
- **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 broadcast to all overlay windows.
|
||||
- **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.
|
||||
- **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, stops the AnkiConnect proxy server, and cleans Anki/AniList state.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -298,27 +299,24 @@ flowchart LR
|
||||
|
||||
OverlayInit["initializeOverlay<br/>Runtime()"]:::phase
|
||||
|
||||
OverlayInit --> VisWin["Visible window<br/>Yomitan lookups"]:::init
|
||||
OverlayInit --> InvWin["Invisible window<br/>mpv positioning"]:::init
|
||||
OverlayInit --> SecWin["Secondary window<br/>subtitle bar"]:::init
|
||||
OverlayInit --> MainWin["Main overlay window<br/>primary + secondary subtitles"]:::init
|
||||
OverlayInit --> Shortcuts["Register global<br/>shortcuts"]:::init
|
||||
|
||||
VisWin --> Warmups
|
||||
InvWin --> Warmups
|
||||
SecWin --> Warmups
|
||||
MainWin --> Warmups
|
||||
Shortcuts --> Warmups
|
||||
|
||||
Warmups["Background<br/>warmups"]:::phase
|
||||
|
||||
subgraph WarmupGroup[" "]
|
||||
direction TB
|
||||
W1["MeCab"]:::warmup
|
||||
W1["MeCab<br/>+ worker thread"]:::warmup
|
||||
W2["Yomitan"]:::warmup
|
||||
W3["JLPT + freq<br/>dictionaries"]:::warmup
|
||||
W4["Jellyfin"]:::warmup
|
||||
W5["Discord"]:::warmup
|
||||
W6["AniList"]:::warmup
|
||||
W1 ~~~ W2 ~~~ W3 ~~~ W4 ~~~ W5 ~~~ W6
|
||||
W7["AnkiConnect<br/>proxy"]:::warmup
|
||||
W1 ~~~ W2 ~~~ W3 ~~~ W4 ~~~ W5 ~~~ W6 ~~~ W7
|
||||
end
|
||||
|
||||
Warmups --> WarmupGroup
|
||||
@@ -330,7 +328,7 @@ flowchart LR
|
||||
ExtEvt["Shortcuts · config hot-reload"]:::runtime
|
||||
MpvEvt & IpcEvt & ExtEvt --> Route["Route via composers"]:::runtime
|
||||
Route --> Process["SubtitlePipeline<br/>normalize → tokenize → merge"]:::runtime
|
||||
Process --> Broadcast["Update AppState<br/>broadcast to windows"]:::runtime
|
||||
Process --> Broadcast["Update AppState<br/>broadcast to renderer + modals"]:::runtime
|
||||
end
|
||||
|
||||
WarmupGroup --> Loop
|
||||
@@ -342,7 +340,7 @@ flowchart LR
|
||||
Quit --> T1["Tray · config watcher<br/>global shortcuts"]:::shutdown
|
||||
Quit --> T2["WebSocket · texthooker<br/>mpv socket · OSD log"]:::shutdown
|
||||
Quit --> T3["Window tracker<br/>Yomitan parser"]:::shutdown
|
||||
Quit --> T4["Immersion tracker<br/>Jellyfin · Discord<br/>Anki · AniList"]:::shutdown
|
||||
Quit --> T4["Immersion tracker<br/>Jellyfin · Discord<br/>Anki proxy · AniList"]:::shutdown
|
||||
|
||||
style Loop fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
```
|
||||
|
||||
@@ -47,6 +47,8 @@ Malformed config syntax (invalid JSON/JSONC) is startup-blocking: SubMiner shows
|
||||
|
||||
For valid JSON/JSONC with invalid option values, SubMiner uses warn-and-fallback behavior: it logs the bad key/value and continues with the default for that option.
|
||||
|
||||
On macOS, these validation warnings also open a native dialog with full details (desktop notification banners can truncate long messages).
|
||||
|
||||
### Hot-Reload Behavior
|
||||
|
||||
SubMiner watches the active config file (`config.jsonc` or `config.json`) while running and applies supported updates automatically.
|
||||
@@ -70,26 +72,467 @@ Restart-required changes:
|
||||
|
||||
The configuration file includes several main sections:
|
||||
|
||||
- [**AnkiConnect**](#ankiconnect) - Automatic Anki card creation with media
|
||||
**Core Settings**
|
||||
|
||||
- [**Logging**](#logging) - Runtime log level
|
||||
- [**Auto-Start Overlay**](#auto-start-overlay) - Automatically show overlay on MPV connection
|
||||
- [**Visible Overlay Subtitle Binding**](#visible-overlay-subtitle-binding) - Link visible overlay toggles to MPV subtitle visibility
|
||||
- [**Auto Subtitle Sync**](#auto-subtitle-sync) - Sync current subtitle with `alass`/`ffsubsync`
|
||||
- [**Invisible Overlay**](#invisible-overlay) - Startup visibility behavior for the invisible mining layer
|
||||
- [**Startup Warmups**](#startup-warmups) - Control what preloads on startup vs first-use defer
|
||||
- [**WebSocket Server**](#websocket-server) - Built-in subtitle broadcasting server
|
||||
- [**Texthooker**](#texthooker) - Control browser opening behavior
|
||||
|
||||
**Subtitle Display**
|
||||
|
||||
- [**Subtitle Style**](#subtitle-style) - Appearance customization
|
||||
- [**Subtitle Position**](#subtitle-position) - Overlay vertical positioning
|
||||
- [**Secondary Subtitles**](#secondary-subtitles) - Dual subtitle track support
|
||||
|
||||
**Keyboard & Controls**
|
||||
|
||||
- [**Keybindings**](#keybindings) - MPV command shortcuts
|
||||
- [**Shortcuts Configuration**](#shortcuts-configuration) - Overlay keyboard shortcuts
|
||||
- [**Manual Card Update Shortcuts**](#manual-card-update-shortcuts) - Shortcuts for manual Anki card workflows
|
||||
- [**Session Help Modal**](#session-help-modal) - In-overlay shortcut reference
|
||||
- [**Runtime Option Palette**](#runtime-option-palette) - Live, session-only option toggles
|
||||
|
||||
**Anki Integration**
|
||||
|
||||
- [**AnkiConnect**](#ankiconnect) - Automatic Anki card creation with media
|
||||
- [**Kiku/Lapis Integration**](#kiku-lapis-integration) - Sentence cards and duplicate handling for Kiku/Lapis note types
|
||||
- [**N+1 Word Highlighting**](#n1-word-highlighting) - Known-word cache and single-target highlighting
|
||||
- [**Field Grouping Modes**](#field-grouping-modes) - Kiku/Lapis duplicate card merging
|
||||
|
||||
**External Integrations**
|
||||
|
||||
- [**Jimaku**](#jimaku) - Jimaku API configuration and defaults
|
||||
- [**Auto Subtitle Sync**](#auto-subtitle-sync) - Sync current subtitle with `alass`/`ffsubsync`
|
||||
- [**AniList**](#anilist) - Optional post-watch progress updates
|
||||
- [**Jellyfin**](#jellyfin) - Optional Jellyfin auth, library listing, and playback launch
|
||||
- [**Discord Rich Presence**](#discord-rich-presence) - Optional Discord activity card updates
|
||||
- [**Keybindings**](#keybindings) - MPV command shortcuts
|
||||
- [**Runtime Option Palette**](#runtime-option-palette) - Live, session-only option toggles
|
||||
- [**Secondary Subtitles**](#secondary-subtitles) - Dual subtitle track support
|
||||
- [**Shortcuts Configuration**](#shortcuts-configuration) - Overlay keyboard shortcuts
|
||||
- [**Subtitle Position**](#subtitle-position) - Overlay vertical positioning
|
||||
- [**Subtitle Style**](#subtitle-style) - Appearance customization
|
||||
- [**Texthooker**](#texthooker) - Control browser opening behavior
|
||||
- [**WebSocket Server**](#websocket-server) - Built-in subtitle broadcasting server
|
||||
- [**Immersion Tracking**](#immersion-tracking) - Track subtitle sessions and mining activity in SQLite
|
||||
- [**YouTube Subtitle Generation**](#youtube-subtitle-generation) - Launcher defaults for yt-dlp + local whisper fallback
|
||||
|
||||
## Core Settings
|
||||
|
||||
### Logging
|
||||
|
||||
Control the minimum log level for runtime output:
|
||||
|
||||
```json
|
||||
{
|
||||
"logging": {
|
||||
"level": "info"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| ------- | ----------------------------------- | ------------------------------------------------ |
|
||||
| `level` | `"debug"`, `"info"`, `"warn"`, `"error"` | Minimum log level for runtime logging (default: `"info"`) |
|
||||
|
||||
### Auto-Start Overlay
|
||||
|
||||
Control whether the overlay automatically becomes visible when it connects to mpv:
|
||||
|
||||
```json
|
||||
{
|
||||
"auto_start_overlay": false
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| -------------------- | --------------- | ------------------------------------------------------ |
|
||||
| `auto_start_overlay` | `true`, `false` | Auto-show overlay on mpv connection (default: `false`) |
|
||||
|
||||
The mpv plugin controls startup overlay visibility via `auto_start_visible_overlay` in `subminer.conf`.
|
||||
For wrapper-driven playback, `subminer.conf` can also enable startup pause gating with
|
||||
`auto_start_pause_until_ready` (requires `auto_start=yes` + `auto_start_visible_overlay=yes`).
|
||||
Current plugin defaults in `subminer.conf` are:
|
||||
|
||||
- `auto_start=yes`
|
||||
- `auto_start_visible_overlay=yes`
|
||||
- `auto_start_pause_until_ready=yes`
|
||||
|
||||
### Startup Warmups
|
||||
|
||||
Control which startup warmups run in the background versus deferring to first real usage:
|
||||
|
||||
```json
|
||||
{
|
||||
"startupWarmups": {
|
||||
"lowPowerMode": false,
|
||||
"mecab": true,
|
||||
"yomitanExtension": true,
|
||||
"subtitleDictionaries": true,
|
||||
"jellyfinRemoteSession": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| ----------------------- | --------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `lowPowerMode` | `true`, `false` | Defer all warmups except Yomitan extension |
|
||||
| `mecab` | `true`, `false` | Warm up MeCab tokenizer at startup |
|
||||
| `yomitanExtension` | `true`, `false` | Warm up Yomitan extension at startup |
|
||||
| `subtitleDictionaries` | `true`, `false` | Warm up JLPT + frequency dictionaries at startup |
|
||||
| `jellyfinRemoteSession` | `true`, `false` | Warm up Jellyfin remote session at startup (still requires Jellyfin remote auto-connect settings) |
|
||||
|
||||
Defaults warm everything (`true` for all toggles, `lowPowerMode: false`). Setting a warmup toggle to `false` defers that work until first usage.
|
||||
|
||||
### WebSocket Server
|
||||
|
||||
The overlay includes a built-in WebSocket server that broadcasts subtitle text to connected clients (such as texthooker-ui) for external processing.
|
||||
|
||||
By default, the server uses "auto" mode: it starts automatically unless [mpv_websocket](https://github.com/kuroahna/mpv_websocket) is detected at `~/.config/mpv/mpv_websocket`. If you have mpv_websocket installed, the built-in server is skipped to avoid conflicts.
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options.
|
||||
|
||||
```json
|
||||
{
|
||||
"websocket": {
|
||||
"enabled": "auto",
|
||||
"port": 6677
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| --------- | ------------------------- | -------------------------------------------------------- |
|
||||
| `enabled` | `true`, `false`, `"auto"` | `"auto"` (default) disables if mpv_websocket is detected |
|
||||
| `port` | number | WebSocket server port (default: 6677) |
|
||||
|
||||
### Texthooker
|
||||
|
||||
Control whether the browser opens automatically when texthooker starts:
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options.
|
||||
|
||||
```json
|
||||
{
|
||||
"texthooker": {
|
||||
"openBrowser": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Subtitle Display
|
||||
|
||||
### Subtitle Style
|
||||
|
||||
Customize the appearance of primary and secondary subtitles:
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options.
|
||||
|
||||
```json
|
||||
{
|
||||
"subtitleStyle": {
|
||||
"fontFamily": "M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP",
|
||||
"fontSize": 35,
|
||||
"fontColor": "#cad3f5",
|
||||
"fontWeight": "600",
|
||||
"lineHeight": 1.35,
|
||||
"letterSpacing": "-0.01em",
|
||||
"wordSpacing": 0,
|
||||
"fontKerning": "normal",
|
||||
"textRendering": "geometricPrecision",
|
||||
"textShadow": "0 3px 10px rgba(0,0,0,0.69)",
|
||||
"fontStyle": "normal",
|
||||
"backgroundColor": "rgb(30, 32, 48, 0.88)",
|
||||
"backdropFilter": "blur(6px)",
|
||||
"secondary": {
|
||||
"fontFamily": "Inter, Noto Sans, Helvetica Neue, sans-serif",
|
||||
"fontSize": 24,
|
||||
"fontColor": "#cad3f5",
|
||||
"backgroundColor": "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| ---------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `fontFamily` | string | CSS font-family value (default: `"M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP"`) |
|
||||
| `fontSize` | number (px) | Font size in pixels (default: `35`) |
|
||||
| `fontColor` | string | Any CSS color value (default: `"#cad3f5"`) |
|
||||
| `fontWeight` | string | CSS font-weight, e.g. `"bold"`, `"normal"`, `"600"` (default: `"600"`) |
|
||||
| `fontStyle` | string | `"normal"` or `"italic"` (default: `"normal"`) |
|
||||
| `backgroundColor` | string | Any CSS color, including `"transparent"` (default: `"rgb(30, 32, 48, 0.88)"`) |
|
||||
| `enableJlpt` | boolean | Enable JLPT level underline styling (`false` by default) |
|
||||
| `preserveLineBreaks` | boolean | Preserve line breaks in visible overlay subtitle rendering (`false` by default). Enable to mirror mpv line layout. |
|
||||
| `autoPauseVideoOnHover` | boolean | Pause playback while mouse hovers subtitle text, then resume on leave (`true` by default). |
|
||||
| `hoverTokenColor` | string | Hex color used for hovered subtitle token highlight in mpv (default: catppuccin mauve) |
|
||||
| `hoverTokenBackgroundColor` | string | CSS color used for hovered subtitle token background highlight (default: semi-transparent dark) |
|
||||
| `frequencyDictionary.enabled` | boolean | Enable frequency highlighting from dictionary lookups (`false` by default) |
|
||||
| `frequencyDictionary.sourcePath` | string | Path to a local frequency dictionary root. Leave empty or omit to use installed/default frequency-dictionary search paths. |
|
||||
| `frequencyDictionary.topX` | number | Only color tokens whose frequency rank is `<= topX` (`1000` by default) |
|
||||
| `frequencyDictionary.mode` | string | `"single"` or `"banded"` (`"single"` by default) |
|
||||
| `frequencyDictionary.matchMode` | string | `"headword"` or `"surface"` (`"headword"` by default) |
|
||||
| `frequencyDictionary.singleColor` | string | Color used for all highlighted tokens in single mode |
|
||||
| `frequencyDictionary.bandedColors` | string[] | Array of five hex colors used for ranked bands in banded mode |
|
||||
| `nPlusOneColor` | string | Existing n+1 highlight color (default: `#c6a0f6`) |
|
||||
| `knownWordColor` | string | Existing known-word highlight color (default: `#a6da95`) |
|
||||
| `jlptColors` | object | JLPT level underline colors object (`N1`..`N5`) |
|
||||
| `secondary` | object | Override any of the above for secondary subtitles (optional) |
|
||||
|
||||
JLPT underlining is powered by offline term-meta bank files at runtime. See [`docs/jlpt-vocab-bundle.md`](jlpt-vocab-bundle.md) for required files, source/version refresh steps, and deterministic fallback behavior.
|
||||
|
||||
Frequency dictionary highlighting uses the same dictionary file format as JLPT bundle lookups (`term_meta_bank_*.json` under discovered dictionary directories). A token is highlighted when it has a positive integer `frequencyRank` (lower is more common) and the rank is within `topX`.
|
||||
|
||||
Lookup behavior:
|
||||
|
||||
- Set `frequencyDictionary.sourcePath` to a directory containing `term_meta_bank_*.json` for a fully custom source.
|
||||
- If `sourcePath` is missing or empty, SubMiner searches default install/runtime locations for `frequency-dictionary` directories (for example app resources, user data paths, and current working directory).
|
||||
- In both cases, only terms with a valid `frequencyRank` are used; everything else falls back to no highlighting.
|
||||
- `frequencyDictionary.matchMode` controls which token text is used for frequency lookups: `headword` (dictionary form) or `surface` (visible subtitle text).
|
||||
- Frequency highlighting skips tokens that look like non-lexical SFX/interjection noise (for example kana reduplication or short kana endings like `っ`), even when dictionary ranks exist.
|
||||
|
||||
In `single` mode all highlights use `singleColor`; in `banded` mode tokens map to five ascending color bands from most common to least common inside the topX window.
|
||||
|
||||
Secondary subtitle defaults: `fontFamily: "Inter, Noto Sans, Helvetica Neue, sans-serif"`, `fontSize: 24`, `fontColor: "#cad3f5"`, `backgroundColor: "transparent"`. Any property not set in `secondary` falls back to the CSS defaults.
|
||||
|
||||
**See `config.example.jsonc`** for the complete list of subtitle style configuration options.
|
||||
|
||||
`jlptColors` keys are:
|
||||
|
||||
| Key | Default | Description |
|
||||
| ---- | --------- | ----------------------- |
|
||||
| `N1` | `#ed8796` | JLPT N1 underline color |
|
||||
| `N2` | `#f5a97f` | JLPT N2 underline color |
|
||||
| `N3` | `#f9e2af` | JLPT N3 underline color |
|
||||
| `N4` | `#a6e3a1` | JLPT N4 underline color |
|
||||
| `N5` | `#8aadf4` | JLPT N5 underline color |
|
||||
|
||||
**Image Quality Notes:**
|
||||
|
||||
- `imageQuality` affects JPG and WebP only; PNG is lossless and ignores this setting
|
||||
- JPG quality is mapped to FFmpeg's scale (2-31, lower = better)
|
||||
- WebP quality uses FFmpeg's native 0-100 scale
|
||||
|
||||
### Subtitle Position
|
||||
|
||||
Set the initial vertical subtitle position (measured from the bottom of the screen):
|
||||
|
||||
```json
|
||||
{
|
||||
"subtitlePosition": {
|
||||
"yPercent": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| ---------- | ---------------- | ---------------------------------------------------------------------- |
|
||||
| `yPercent` | number (0 - 100) | Distance from the bottom as a percent of screen height (default: `10`) |
|
||||
In the overlay, you can fine-tune subtitle position at runtime with `Right-click + drag` on subtitle text.
|
||||
|
||||
### Secondary Subtitles
|
||||
|
||||
Display a second subtitle track (e.g., English alongside Japanese) in the overlay:
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options.
|
||||
|
||||
```json
|
||||
{
|
||||
"secondarySub": {
|
||||
"secondarySubLanguages": ["eng", "en"],
|
||||
"autoLoadSecondarySub": true,
|
||||
"defaultMode": "hover"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| ----------------------- | ---------------------------------- | ------------------------------------------------------ |
|
||||
| `secondarySubLanguages` | string[] | Language codes to auto-load (e.g., `["eng", "en"]`) |
|
||||
| `autoLoadSecondarySub` | `true`, `false` | Auto-detect and load matching secondary subtitle track |
|
||||
| `defaultMode` | `"hidden"`, `"visible"`, `"hover"` | Initial display mode (default: `"hover"`) |
|
||||
|
||||
**Display modes:**
|
||||
|
||||
- **hidden** — Secondary subtitles not shown
|
||||
- **visible** — Always visible at top of overlay
|
||||
- **hover** — Only visible when hovering over the subtitle area (default)
|
||||
|
||||
**See `config.example.jsonc`** for additional secondary subtitle configuration options.
|
||||
|
||||
## Keyboard & Controls
|
||||
|
||||
### Keybindings
|
||||
|
||||
Add a `keybindings` array to configure keyboard shortcuts that send commands to mpv:
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options and more examples.
|
||||
|
||||
**Default keybindings:**
|
||||
|
||||
| Key | Command | Description |
|
||||
| ----------------- | ---------------------------- | ------------------------------------- |
|
||||
| `Space` | `["cycle", "pause"]` | Toggle pause |
|
||||
| `KeyJ` | `["cycle", "sid"]` | Cycle primary subtitle track |
|
||||
| `Shift+KeyJ` | `["cycle", "secondary-sid"]` | Cycle secondary subtitle track |
|
||||
| `ArrowRight` | `["seek", 5]` | Seek forward 5 seconds |
|
||||
| `ArrowLeft` | `["seek", -5]` | Seek backward 5 seconds |
|
||||
| `ArrowUp` | `["seek", 60]` | Seek forward 60 seconds |
|
||||
| `ArrowDown` | `["seek", -60]` | Seek backward 60 seconds |
|
||||
| `Shift+KeyH` | `["sub-seek", -1]` | Jump to previous subtitle |
|
||||
| `Shift+KeyL` | `["sub-seek", 1]` | Jump to next subtitle |
|
||||
| `Ctrl+Shift+KeyH` | `["__replay-subtitle"]` | Replay current subtitle, pause at end |
|
||||
| `Ctrl+Shift+KeyL` | `["__play-next-subtitle"]` | Play next subtitle, pause at end |
|
||||
| `KeyQ` | `["quit"]` | Quit mpv |
|
||||
| `Ctrl+KeyW` | `["quit"]` | Quit mpv |
|
||||
|
||||
**Custom keybindings example:**
|
||||
|
||||
```json
|
||||
{
|
||||
"keybindings": [
|
||||
{ "key": "ArrowRight", "command": ["seek", 5] },
|
||||
{ "key": "ArrowLeft", "command": ["seek", -5] },
|
||||
{ "key": "Shift+ArrowRight", "command": ["seek", 30] },
|
||||
{ "key": "KeyR", "command": ["script-binding", "immersive/auto-replay"] },
|
||||
{ "key": "KeyA", "command": ["script-message", "ankiconnect-add-note"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Key format:** Use `KeyboardEvent.code` values (`Space`, `ArrowRight`, `KeyR`, etc.) with optional modifiers (`Ctrl+`, `Alt+`, `Shift+`, `Meta+`).
|
||||
|
||||
**Disable a default binding:** Set command to `null`:
|
||||
|
||||
```json
|
||||
{ "key": "Space", "command": null }
|
||||
```
|
||||
|
||||
**Special commands:** Commands prefixed with `__` are handled internally by the overlay rather than sent to mpv. `__replay-subtitle` replays the current subtitle and pauses at its end. `__play-next-subtitle` seeks to the next subtitle, plays it, and pauses at its end. `__runtime-options-open` opens the runtime options palette. `__runtime-option-cycle:<id>[:next|prev]` cycles a runtime option value.
|
||||
|
||||
**Supported commands:** Any valid mpv JSON IPC command array (`["cycle", "pause"]`, `["seek", 5]`, `["script-binding", "..."]`, etc.)
|
||||
|
||||
For subtitle-position and subtitle-track proxy commands (`sub-pos`, `sid`, `secondary-sid`), SubMiner also shows an mpv OSD notification after the command runs.
|
||||
|
||||
**See `config.example.jsonc`** for more keybinding examples and configuration options.
|
||||
|
||||
### Shortcuts Configuration
|
||||
|
||||
Customize or disable the overlay keyboard shortcuts:
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options.
|
||||
|
||||
```json
|
||||
{
|
||||
"shortcuts": {
|
||||
"toggleVisibleOverlayGlobal": "Alt+Shift+O",
|
||||
"copySubtitle": "CommandOrControl+C",
|
||||
"copySubtitleMultiple": "CommandOrControl+Shift+C",
|
||||
"updateLastCardFromClipboard": "CommandOrControl+V",
|
||||
"triggerFieldGrouping": "CommandOrControl+G",
|
||||
"triggerSubsync": "Ctrl+Alt+S",
|
||||
"mineSentence": "CommandOrControl+S",
|
||||
"mineSentenceMultiple": "CommandOrControl+Shift+S",
|
||||
"markAudioCard": "CommandOrControl+Shift+A",
|
||||
"openRuntimeOptions": "CommandOrControl+Shift+O",
|
||||
"openJimaku": "Ctrl+Shift+J",
|
||||
"multiCopyTimeoutMs": 3000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| ----------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `toggleVisibleOverlayGlobal` | string \| `null` | Global accelerator for toggling visible subtitle overlay (default: `"Alt+Shift+O"`) |
|
||||
| `copySubtitle` | string \| `null` | Accelerator for copying current subtitle (default: `"CommandOrControl+C"`) |
|
||||
| `copySubtitleMultiple` | string \| `null` | Accelerator for multi-copy mode (default: `"CommandOrControl+Shift+C"`) |
|
||||
| `updateLastCardFromClipboard` | string \| `null` | Accelerator for updating card from clipboard (default: `"CommandOrControl+V"`) |
|
||||
| `triggerFieldGrouping` | string \| `null` | Accelerator for Kiku field grouping on last card (default: `"CommandOrControl+G"`; only active when `behavior.autoUpdateNewCards` is `false`) |
|
||||
| `triggerSubsync` | string \| `null` | Accelerator for running Subsync (default: `"Ctrl+Alt+S"`) |
|
||||
| `mineSentence` | string \| `null` | Accelerator for creating sentence card from current subtitle (default: `"CommandOrControl+S"`) |
|
||||
| `mineSentenceMultiple` | string \| `null` | Accelerator for multi-mine sentence card mode (default: `"CommandOrControl+Shift+S"`) |
|
||||
| `multiCopyTimeoutMs` | number | Timeout in ms for multi-copy/mine digit input (default: `3000`) |
|
||||
| `toggleSecondarySub` | string \| `null` | Accelerator for cycling secondary subtitle mode (default: `"CommandOrControl+Shift+V"`) |
|
||||
| `markAudioCard` | string \| `null` | Accelerator for marking last card as audio card (default: `"CommandOrControl+Shift+A"`) |
|
||||
| `openRuntimeOptions` | string \| `null` | Opens runtime options palette for live session-only toggles (default: `"CommandOrControl+Shift+O"`) |
|
||||
| `openJimaku` | string \| `null` | Opens the Jimaku search modal (default: `"Ctrl+Shift+J"`) |
|
||||
|
||||
**See `config.example.jsonc`** for the complete list of shortcut configuration options.
|
||||
|
||||
Set any shortcut to `null` to disable it.
|
||||
|
||||
Feature-dependent shortcuts/keybindings only run when their related integration is enabled. For example, Anki/Kiku shortcuts require `ankiConnect.enabled` (and Kiku-specific behavior where applicable), and Jellyfin remote startup behavior requires Jellyfin to be enabled.
|
||||
|
||||
### Manual Card Update Shortcuts
|
||||
|
||||
When `behavior.autoUpdateNewCards` is set to `false`, new cards are detected but not automatically updated. Use these keyboard shortcuts for manual control:
|
||||
|
||||
| Shortcut | Action |
|
||||
| -------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `Ctrl+C` | Copy the current subtitle line to clipboard (preserves line breaks) |
|
||||
| `Ctrl+Shift+C` | Enter multi-copy mode. Press `1-9` to copy that many recent lines, or `Esc` to cancel. Timeout: 3 seconds |
|
||||
| `Ctrl+V` | Update the last added Anki card using subtitles from clipboard |
|
||||
| `Ctrl+G` | Trigger Kiku duplicate field grouping for the last added card (only when `behavior.autoUpdateNewCards` is `false`) |
|
||||
| `Ctrl+S` | Create a sentence card from the current subtitle line |
|
||||
| `Ctrl+Shift+S` | Enter multi-mine mode. Press `1-9` to create a sentence card from that many recent lines, or `Esc` to cancel |
|
||||
| `Ctrl+Shift+V` | Cycle secondary subtitle display mode (hidden → visible → hover) |
|
||||
| `Ctrl+Shift+A` | Mark the last added Anki card as an audio card (sets IsAudioCard, SentenceAudio, Sentence, Picture) |
|
||||
| `Ctrl+Shift+O` | Open runtime options palette (session-only live toggles) |
|
||||
| `Ctrl/Cmd+A` | Append clipboard video path to MPV playlist (fixed, not currently configurable) |
|
||||
|
||||
**Multi-line copy workflow:**
|
||||
|
||||
1. Press `Ctrl+Shift+C`
|
||||
2. Press a number key (`1-9`) within 3 seconds
|
||||
3. The specified number of most recent subtitle lines are copied
|
||||
4. Press `Ctrl+V` to update the last added card with the copied lines
|
||||
|
||||
These shortcuts are only active when the overlay window is visible and automatically disabled when hidden.
|
||||
|
||||
### Session Help Modal
|
||||
|
||||
The session help modal is opened with `Y-H` by default (falls back to `Y-K` if needed) and shows the current session keybindings and color legend.
|
||||
|
||||
You can filter the modal quickly with `/`:
|
||||
|
||||
- Type any part of the action name or shortcut in the search bar.
|
||||
- Search is case-insensitive and ignores spaces/punctuation (`+`, `-`, `_`, `/`) so `ctrl w`, `ctrl+w`, and `ctrl+s` all match.
|
||||
- Results are filtered across active MPV shortcuts, configured overlay shortcuts, and color legend items.
|
||||
|
||||
While the modal is open:
|
||||
|
||||
- `Esc`: close the modal (or clear the filter when text is entered)
|
||||
- `↑/↓`, `j/k`: move selection
|
||||
- Mouse/trackpad: click to select and activate rows
|
||||
|
||||
The list is generated at runtime from:
|
||||
|
||||
- Your active mpv keybindings (`keybindings`).
|
||||
- Your configured overlay shortcuts (`shortcuts`, including runtime-loaded config values).
|
||||
- Current subtitle color settings from `subtitleStyle`.
|
||||
|
||||
When config hot-reload updates shortcut/keybinding/style values, close and reopen the help modal to refresh the displayed entries.
|
||||
|
||||
### Runtime Option Palette
|
||||
|
||||
Use the runtime options palette to toggle settings live while SubMiner is running. These changes are session-only and reset on restart.
|
||||
|
||||
Current runtime options:
|
||||
|
||||
- `ankiConnect.behavior.autoUpdateNewCards` (`On` / `Off`)
|
||||
- `ankiConnect.nPlusOne.highlightEnabled` (`On` / `Off`)
|
||||
- `subtitleStyle.enableJlpt` (`On` / `Off`)
|
||||
- `subtitleStyle.frequencyDictionary.enabled` (`On` / `Off`)
|
||||
- `ankiConnect.nPlusOne.matchMode` (`headword` / `surface`)
|
||||
- `ankiConnect.isKiku.fieldGrouping` (`auto` / `manual` / `disabled`)
|
||||
|
||||
Annotation toggles (`nPlusOne`, `enableJlpt`, `frequencyDictionary.enabled`) only apply to new subtitle lines after the toggle. The currently displayed line is not re-tokenized in place.
|
||||
|
||||
Default shortcut: `Ctrl+Shift+O`
|
||||
|
||||
Palette controls:
|
||||
|
||||
- `Arrow Up/Down`: select option
|
||||
- `Arrow Left/Right`: change selected value
|
||||
- `Enter`: apply selected value
|
||||
- `Esc`: close
|
||||
|
||||
## Anki Integration
|
||||
|
||||
### AnkiConnect
|
||||
|
||||
Enable automatic Anki card creation and updates with media generation:
|
||||
@@ -100,6 +543,12 @@ Enable automatic Anki card creation and updates with media generation:
|
||||
"enabled": true,
|
||||
"url": "http://127.0.0.1:8765",
|
||||
"pollingRate": 3000,
|
||||
"proxy": {
|
||||
"enabled": false,
|
||||
"host": "127.0.0.1",
|
||||
"port": 8766,
|
||||
"upstreamUrl": "http://127.0.0.1:8765"
|
||||
},
|
||||
"tags": ["SubMiner"],
|
||||
"deck": "Learning::Japanese",
|
||||
"fields": {
|
||||
@@ -163,7 +612,11 @@ This example is intentionally compact. The option table below documents availabl
|
||||
| --------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `enabled` | `true`, `false` | Enable AnkiConnect integration (default: `false`) |
|
||||
| `url` | string (URL) | AnkiConnect API URL (default: `http://127.0.0.1:8765`) |
|
||||
| `pollingRate` | number (ms) | How often to check for new cards (default: `3000`) |
|
||||
| `pollingRate` | number (ms) | How often to check for new cards in polling mode (default: `3000`; ignored for direct proxy `addNote`/`addNotes` updates) |
|
||||
| `proxy.enabled` | `true`, `false` | Enable local AnkiConnect-compatible proxy for push-based auto-enrichment (default: `true`) |
|
||||
| `proxy.host` | string | Bind host for local AnkiConnect proxy (default: `127.0.0.1`) |
|
||||
| `proxy.port` | number | Bind port for local AnkiConnect proxy (default: `8766`) |
|
||||
| `proxy.upstreamUrl` | string (URL) | Upstream AnkiConnect URL that proxy forwards to (default: `http://127.0.0.1:8765`) |
|
||||
| `tags` | array of strings | Tags automatically added to cards mined/updated by SubMiner (default: `['SubMiner']`; set `[]` to disable automatic tagging). |
|
||||
| `deck` | string | Anki deck to monitor for new cards |
|
||||
| `ankiConnect.nPlusOne.decks` | array of strings | Decks used for N+1 known-word cache lookups. When omitted/empty, falls back to `ankiConnect.deck`. |
|
||||
@@ -210,13 +663,28 @@ This example is intentionally compact. The option table below documents availabl
|
||||
| `isLapis` | object | Lapis/shared sentence-card config: `{ enabled, sentenceCardModel }`. Sentence/audio field names are fixed to `Sentence` and `SentenceAudio`. |
|
||||
| `isKiku` | object | Kiku-only config: `{ enabled, fieldGrouping, deleteDuplicateInAuto }` (shared sentence/audio/model settings are inherited from `isLapis`) |
|
||||
|
||||
**Kiku / Lapis Note Type Support:**
|
||||
### Kiku/Lapis Integration
|
||||
|
||||
SubMiner supports the [Lapis](https://github.com/donkuri/lapis) and [Kiku](https://kiku.youyoumu.my.id/) note types. Both `isLapis.enabled` and `isKiku.enabled` can be true; Kiku takes precedence for grouping behavior, while sentence-card model/field settings come from `isLapis`.
|
||||
SubMiner is intentionally built for [Kiku](https://kiku.youyoumu.my.id/) and [Lapis](https://github.com/donkuri/lapis) workflows, with note-type-specific behavior built into Anki settings.
|
||||
|
||||
When enabled, sentence cards automatically set `IsSentenceCard` to `"x"` and populate the `Expression` field. Audio cards set `IsAudioCard` to `"x"`.
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"isLapis": {
|
||||
"enabled": true,
|
||||
"sentenceCardModel": "Japanese sentences"
|
||||
},
|
||||
"isKiku": {
|
||||
"enabled": true,
|
||||
"fieldGrouping": "manual",
|
||||
"deleteDuplicateInAuto": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Kiku extends Lapis with **field grouping** — when a duplicate card is detected (same Word/Expression), SubMiner merges the two cards' content into one using Kiku's `data-group-id` HTML structure, organizing each mining instance into separate pages within the note.
|
||||
- Enable `isLapis` to mine dedicated sentence cards. SubMiner sets `IsSentenceCard` to `"x"` and fills the sentence fields for the configured model.
|
||||
- Enable `isKiku` to turn on duplicate merge behavior for mined Word/Expression hits.
|
||||
- When both are enabled, Kiku behavior is applied for grouping while sentence-card model settings are still read from `isLapis`.
|
||||
- `isKiku.fieldGrouping` supports `disabled`, `auto`, and `manual` merge modes; see [Field Grouping Modes](#field-grouping-modes).
|
||||
|
||||
### N+1 Word Highlighting
|
||||
|
||||
@@ -268,91 +736,27 @@ To refresh roughly once per day, set:
|
||||
|
||||
<a :href="'/assets/kiku-integration.webm'" target="_blank" rel="noreferrer">Open demo in a new tab</a>
|
||||
|
||||
**Image Quality Notes:**
|
||||
## External Integrations
|
||||
|
||||
- `imageQuality` affects JPG and WebP only; PNG is lossless and ignores this setting
|
||||
- JPG quality is mapped to FFmpeg's scale (2-31, lower = better)
|
||||
- WebP quality uses FFmpeg's native 0-100 scale
|
||||
### Jimaku
|
||||
|
||||
### Manual Card Update Shortcuts
|
||||
|
||||
When `behavior.autoUpdateNewCards` is set to `false`, new cards are detected but not automatically updated. Use these keyboard shortcuts for manual control:
|
||||
|
||||
| Shortcut | Action |
|
||||
| -------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `Ctrl+C` | Copy the current subtitle line to clipboard (preserves line breaks) |
|
||||
| `Ctrl+Shift+C` | Enter multi-copy mode. Press `1-9` to copy that many recent lines, or `Esc` to cancel. Timeout: 3 seconds |
|
||||
| `Ctrl+V` | Update the last added Anki card using subtitles from clipboard |
|
||||
| `Ctrl+G` | Trigger Kiku duplicate field grouping for the last added card (only when `behavior.autoUpdateNewCards` is `false`) |
|
||||
| `Ctrl+S` | Create a sentence card from the current subtitle line |
|
||||
| `Ctrl+Shift+S` | Enter multi-mine mode. Press `1-9` to create a sentence card from that many recent lines, or `Esc` to cancel |
|
||||
| `Ctrl+Shift+V` | Cycle secondary subtitle display mode (hidden → visible → hover) |
|
||||
| `Ctrl+Shift+A` | Mark the last added Anki card as an audio card (sets IsAudioCard, SentenceAudio, Sentence, Picture) |
|
||||
| `Ctrl+Shift+O` | Open runtime options palette (session-only live toggles) |
|
||||
| `Ctrl/Cmd+A` | Append clipboard video path to MPV playlist (fixed, not currently configurable) |
|
||||
|
||||
**Multi-line copy workflow:**
|
||||
|
||||
1. Press `Ctrl+Shift+C`
|
||||
2. Press a number key (`1-9`) within 3 seconds
|
||||
3. The specified number of most recent subtitle lines are copied
|
||||
4. Press `Ctrl+V` to update the last added card with the copied lines
|
||||
|
||||
These shortcuts are only active when the overlay window is visible and automatically disabled when hidden.
|
||||
|
||||
### Session help modal
|
||||
|
||||
The session help modal is opened with `Y-H` by default (falls back to `Y-K` if needed) and shows the current session keybindings and color legend.
|
||||
|
||||
You can filter the modal quickly with `/`:
|
||||
|
||||
- Type any part of the action name or shortcut in the search bar.
|
||||
- Search is case-insensitive and ignores spaces/punctuation (`+`, `-`, `_`, `/`) so `ctrl w`, `ctrl+w`, and `ctrl+s` all match.
|
||||
- Results are filtered across active MPV shortcuts, configured overlay shortcuts, and color legend items.
|
||||
|
||||
While the modal is open:
|
||||
|
||||
- `Esc`: close the modal (or clear the filter when text is entered)
|
||||
- `↑/↓`, `j/k`: move selection
|
||||
- Mouse/trackpad: click to select and activate rows
|
||||
|
||||
The list is generated at runtime from:
|
||||
|
||||
- Your active mpv keybindings (`keybindings`).
|
||||
- Your configured overlay shortcuts (`shortcuts`, including runtime-loaded config values).
|
||||
- Current subtitle color settings from `subtitleStyle`.
|
||||
|
||||
When config hot-reload updates shortcut/keybinding/style values, close and reopen the help modal to refresh the displayed entries.
|
||||
|
||||
### Auto-Start Overlay
|
||||
|
||||
Control whether the overlay automatically becomes visible when it connects to mpv:
|
||||
Configure Jimaku API access and defaults:
|
||||
|
||||
```json
|
||||
{
|
||||
"auto_start_overlay": false
|
||||
"jimaku": {
|
||||
"apiKey": "YOUR_API_KEY",
|
||||
"apiKeyCommand": "cat ~/.jimaku_key",
|
||||
"apiBaseUrl": "https://jimaku.cc",
|
||||
"languagePreference": "ja",
|
||||
"maxEntryResults": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| -------------------- | --------------- | ------------------------------------------------------ |
|
||||
| `auto_start_overlay` | `true`, `false` | Auto-show overlay on mpv connection (default: `false`) |
|
||||
Jimaku is rate limited; if you hit a limit, SubMiner will surface the retry delay from the API response.
|
||||
|
||||
The mpv plugin controls startup per layer via `auto_start_visible_overlay` and `auto_start_invisible_overlay` in `subminer.conf` (`platform-default` for invisible means hidden on Linux, visible on macOS/Windows).
|
||||
|
||||
### Visible Overlay Subtitle Binding
|
||||
|
||||
Control whether toggling the visible overlay also toggles MPV subtitle visibility:
|
||||
|
||||
```json
|
||||
{
|
||||
"bind_visible_overlay_to_mpv_sub_visibility": true
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| -------------------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `bind_visible_overlay_to_mpv_sub_visibility` | `true`, `false` | When `true` (default), visible overlay hides MPV primary/secondary subtitles and restores them when hidden. When `false`, visible overlay toggles do not change MPV subtitle visibility. |
|
||||
Set `openBrowser` to `false` to only print the URL without opening a browser.
|
||||
|
||||
### Auto Subtitle Sync
|
||||
|
||||
@@ -379,43 +783,6 @@ Sync the active subtitle track using `alass` (preferred) or `ffsubsync`:
|
||||
Default trigger is `Ctrl+Alt+S` via `shortcuts.triggerSubsync`.
|
||||
Customize it there, or set it to `null` to disable.
|
||||
|
||||
### Invisible Overlay
|
||||
|
||||
SubMiner includes a second subtitle mining layer that can be visually invisible while still interactive for Yomitan lookups.
|
||||
|
||||
- `invisibleOverlay.startupVisibility` values:
|
||||
|
||||
1. `"platform-default"`: hidden on Wayland, visible on Windows/macOS/other sessions.
|
||||
2. `"visible"`: always shown on startup.
|
||||
3. `"hidden"`: always hidden on startup.
|
||||
|
||||
Invisible subtitle positioning can be adjusted directly in the invisible layer:
|
||||
|
||||
- `Ctrl/Cmd+Shift+P` toggles position edit mode.
|
||||
- Use arrow keys to move the invisible subtitle text.
|
||||
- Press `Enter` or `Ctrl/Cmd+S` to save, or `Esc` to cancel.
|
||||
- This edit-mode shortcut is fixed (not currently configurable in `shortcuts`/`keybindings`).
|
||||
|
||||
### Jimaku
|
||||
|
||||
Configure Jimaku API access and defaults:
|
||||
|
||||
```json
|
||||
{
|
||||
"jimaku": {
|
||||
"apiKey": "YOUR_API_KEY",
|
||||
"apiKeyCommand": "cat ~/.jimaku_key",
|
||||
"apiBaseUrl": "https://jimaku.cc",
|
||||
"languagePreference": "ja",
|
||||
"maxEntryResults": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Jimaku is rate limited; if you hit a limit, SubMiner will surface the retry delay from the API response.
|
||||
|
||||
Set `openBrowser` to `false` to only print the URL without opening a browser.
|
||||
|
||||
### AniList
|
||||
|
||||
AniList integration is opt-in and disabled by default. Enable it to allow SubMiner to update watched episode progress after playback.
|
||||
@@ -508,6 +875,7 @@ Jellyfin integration is optional and disabled by default. When enabled, SubMiner
|
||||
| `transcodeVideoCodec` | string | Preferred transcode video codec fallback (default: `h264`) |
|
||||
|
||||
Jellyfin auth session (`accessToken` + `userId`) is stored in local encrypted storage after login/setup.
|
||||
|
||||
- On Linux, token storage defaults to `gnome-libsecret` for `safeStorage`. Override with `--password-store=<backend>` on launcher/app invocations when needed.
|
||||
|
||||
Launcher subcommands:
|
||||
@@ -562,276 +930,6 @@ Troubleshooting:
|
||||
- If images do not render, confirm asset keys exactly match uploaded Discord asset names.
|
||||
- If Discord is closed/not installed/disconnects, SubMiner continues running and quietly skips presence updates.
|
||||
|
||||
### Keybindings
|
||||
|
||||
Add a `keybindings` array to configure keyboard shortcuts that send commands to mpv:
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options and more examples.
|
||||
|
||||
**Default keybindings:**
|
||||
|
||||
| Key | Command | Description |
|
||||
| ----------------- | -------------------------- | ------------------------------------- |
|
||||
| `Space` | `["cycle", "pause"]` | Toggle pause |
|
||||
| `ArrowRight` | `["seek", 5]` | Seek forward 5 seconds |
|
||||
| `ArrowLeft` | `["seek", -5]` | Seek backward 5 seconds |
|
||||
| `ArrowUp` | `["seek", 60]` | Seek forward 60 seconds |
|
||||
| `ArrowDown` | `["seek", -60]` | Seek backward 60 seconds |
|
||||
| `Shift+KeyH` | `["sub-seek", -1]` | Jump to previous subtitle |
|
||||
| `Shift+KeyL` | `["sub-seek", 1]` | Jump to next subtitle |
|
||||
| `Ctrl+Shift+KeyH` | `["__replay-subtitle"]` | Replay current subtitle, pause at end |
|
||||
| `Ctrl+Shift+KeyL` | `["__play-next-subtitle"]` | Play next subtitle, pause at end |
|
||||
| `KeyQ` | `["quit"]` | Quit mpv |
|
||||
| `Ctrl+KeyW` | `["quit"]` | Quit mpv |
|
||||
|
||||
**Custom keybindings example:**
|
||||
|
||||
```json
|
||||
{
|
||||
"keybindings": [
|
||||
{ "key": "ArrowRight", "command": ["seek", 5] },
|
||||
{ "key": "ArrowLeft", "command": ["seek", -5] },
|
||||
{ "key": "Shift+ArrowRight", "command": ["seek", 30] },
|
||||
{ "key": "KeyR", "command": ["script-binding", "immersive/auto-replay"] },
|
||||
{ "key": "KeyA", "command": ["script-message", "ankiconnect-add-note"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Key format:** Use `KeyboardEvent.code` values (`Space`, `ArrowRight`, `KeyR`, etc.) with optional modifiers (`Ctrl+`, `Alt+`, `Shift+`, `Meta+`).
|
||||
|
||||
**Disable a default binding:** Set command to `null`:
|
||||
|
||||
```json
|
||||
{ "key": "Space", "command": null }
|
||||
```
|
||||
|
||||
**Special commands:** Commands prefixed with `__` are handled internally by the overlay rather than sent to mpv. `__replay-subtitle` replays the current subtitle and pauses at its end. `__play-next-subtitle` seeks to the next subtitle, plays it, and pauses at its end. `__runtime-options-open` opens the runtime options palette. `__runtime-option-cycle:<id>[:next|prev]` cycles a runtime option value.
|
||||
|
||||
**Supported commands:** Any valid mpv JSON IPC command array (`["cycle", "pause"]`, `["seek", 5]`, `["script-binding", "..."]`, etc.)
|
||||
|
||||
**See `config.example.jsonc`** for more keybinding examples and configuration options.
|
||||
|
||||
### Runtime Option Palette
|
||||
|
||||
Use the runtime options palette to toggle settings live while SubMiner is running. These changes are session-only and reset on restart.
|
||||
|
||||
Current runtime options:
|
||||
|
||||
- `ankiConnect.behavior.autoUpdateNewCards` (`On` / `Off`)
|
||||
- `ankiConnect.isKiku.fieldGrouping` (`auto` / `manual` / `disabled`)
|
||||
|
||||
Default shortcut: `Ctrl+Shift+O`
|
||||
|
||||
Palette controls:
|
||||
|
||||
- `Arrow Up/Down`: select option
|
||||
- `Arrow Left/Right`: change selected value
|
||||
- `Enter`: apply selected value
|
||||
- `Esc`: close
|
||||
|
||||
### Secondary Subtitles
|
||||
|
||||
Display a second subtitle track (e.g., English alongside Japanese) in the overlay:
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options.
|
||||
|
||||
```json
|
||||
{
|
||||
"secondarySub": {
|
||||
"secondarySubLanguages": ["eng", "en"],
|
||||
"autoLoadSecondarySub": true,
|
||||
"defaultMode": "hover"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| ----------------------- | ---------------------------------- | ------------------------------------------------------ |
|
||||
| `secondarySubLanguages` | string[] | Language codes to auto-load (e.g., `["eng", "en"]`) |
|
||||
| `autoLoadSecondarySub` | `true`, `false` | Auto-detect and load matching secondary subtitle track |
|
||||
| `defaultMode` | `"hidden"`, `"visible"`, `"hover"` | Initial display mode (default: `"hover"`) |
|
||||
|
||||
**Display modes:**
|
||||
|
||||
- **hidden** — Secondary subtitles not shown
|
||||
- **visible** — Always visible at top of overlay
|
||||
- **hover** — Only visible when hovering over the subtitle area (default)
|
||||
|
||||
**See `config.example.jsonc`** for additional secondary subtitle configuration options.
|
||||
|
||||
### Shortcuts Configuration
|
||||
|
||||
Customize or disable the overlay keyboard shortcuts:
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options.
|
||||
|
||||
```json
|
||||
{
|
||||
"shortcuts": {
|
||||
"toggleVisibleOverlayGlobal": "Alt+Shift+O",
|
||||
"toggleInvisibleOverlayGlobal": "Alt+Shift+I",
|
||||
"copySubtitle": "CommandOrControl+C",
|
||||
"copySubtitleMultiple": "CommandOrControl+Shift+C",
|
||||
"updateLastCardFromClipboard": "CommandOrControl+V",
|
||||
"triggerFieldGrouping": "CommandOrControl+G",
|
||||
"triggerSubsync": "Ctrl+Alt+S",
|
||||
"mineSentence": "CommandOrControl+S",
|
||||
"mineSentenceMultiple": "CommandOrControl+Shift+S",
|
||||
"markAudioCard": "CommandOrControl+Shift+A",
|
||||
"openRuntimeOptions": "CommandOrControl+Shift+O",
|
||||
"openJimaku": "Ctrl+Shift+J",
|
||||
"multiCopyTimeoutMs": 3000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| ------------------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `toggleVisibleOverlayGlobal` | string \| `null` | Global accelerator for toggling visible subtitle overlay (default: `"Alt+Shift+O"`) |
|
||||
| `toggleInvisibleOverlayGlobal` | string \| `null` | Global accelerator for toggling invisible interactive overlay (default: `"Alt+Shift+I"`) |
|
||||
| `copySubtitle` | string \| `null` | Accelerator for copying current subtitle (default: `"CommandOrControl+C"`) |
|
||||
| `copySubtitleMultiple` | string \| `null` | Accelerator for multi-copy mode (default: `"CommandOrControl+Shift+C"`) |
|
||||
| `updateLastCardFromClipboard` | string \| `null` | Accelerator for updating card from clipboard (default: `"CommandOrControl+V"`) |
|
||||
| `triggerFieldGrouping` | string \| `null` | Accelerator for Kiku field grouping on last card (default: `"CommandOrControl+G"`; only active when `behavior.autoUpdateNewCards` is `false`) |
|
||||
| `triggerSubsync` | string \| `null` | Accelerator for running Subsync (default: `"Ctrl+Alt+S"`) |
|
||||
| `mineSentence` | string \| `null` | Accelerator for creating sentence card from current subtitle (default: `"CommandOrControl+S"`) |
|
||||
| `mineSentenceMultiple` | string \| `null` | Accelerator for multi-mine sentence card mode (default: `"CommandOrControl+Shift+S"`) |
|
||||
| `multiCopyTimeoutMs` | number | Timeout in ms for multi-copy/mine digit input (default: `3000`) |
|
||||
| `toggleSecondarySub` | string \| `null` | Accelerator for cycling secondary subtitle mode (default: `"CommandOrControl+Shift+V"`) |
|
||||
| `markAudioCard` | string \| `null` | Accelerator for marking last card as audio card (default: `"CommandOrControl+Shift+A"`) |
|
||||
| `openRuntimeOptions` | string \| `null` | Opens runtime options palette for live session-only toggles (default: `"CommandOrControl+Shift+O"`) |
|
||||
| `openJimaku` | string \| `null` | Opens the Jimaku search modal (default: `"Ctrl+Shift+J"`) |
|
||||
|
||||
**See `config.example.jsonc`** for the complete list of shortcut configuration options.
|
||||
|
||||
Set any shortcut to `null` to disable it.
|
||||
|
||||
Feature-dependent shortcuts/keybindings only run when their related integration is enabled. For example, Anki/Kiku shortcuts require `ankiConnect.enabled` (and Kiku-specific behavior where applicable), and Jellyfin remote startup behavior requires Jellyfin to be enabled.
|
||||
|
||||
### Subtitle Position
|
||||
|
||||
Set the initial vertical subtitle position (measured from the bottom of the screen):
|
||||
|
||||
```json
|
||||
{
|
||||
"subtitlePosition": {
|
||||
"yPercent": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| ---------- | ---------------- | ---------------------------------------------------------------------- |
|
||||
| `yPercent` | number (0 - 100) | Distance from the bottom as a percent of screen height (default: `10`) |
|
||||
|
||||
### Subtitle Style
|
||||
|
||||
Customize the appearance of primary and secondary subtitles:
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options.
|
||||
|
||||
```json
|
||||
{
|
||||
"subtitleStyle": {
|
||||
"fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif",
|
||||
"fontSize": 35,
|
||||
"fontColor": "#cad3f5",
|
||||
"fontWeight": "normal",
|
||||
"fontStyle": "normal",
|
||||
"backgroundColor": "rgb(30, 32, 48, 0.88)",
|
||||
"secondary": {
|
||||
"fontSize": 24,
|
||||
"fontColor": "#ffffff",
|
||||
"backgroundColor": "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| ---------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| `fontFamily` | string | CSS font-family value (default: `"Noto Sans CJK JP Regular, ..."`) |
|
||||
| `fontSize` | number (px) | Font size in pixels (default: `35`) |
|
||||
| `fontColor` | string | Any CSS color value (default: `"#cad3f5"`) |
|
||||
| `fontWeight` | string | CSS font-weight, e.g. `"bold"`, `"normal"`, `"600"` (default: `"normal"`) |
|
||||
| `fontStyle` | string | `"normal"` or `"italic"` (default: `"normal"`) |
|
||||
| `backgroundColor` | string | Any CSS color, including `"transparent"` (default: `"rgb(30, 32, 48, 0.88)"`) |
|
||||
| `enableJlpt` | boolean | Enable JLPT level underline styling (`false` by default) |
|
||||
| `preserveLineBreaks` | boolean | Preserve line breaks in visible overlay subtitle rendering (`false` by default). Enable to mirror mpv line layout. |
|
||||
| `frequencyDictionary.enabled` | boolean | Enable frequency highlighting from dictionary lookups (`false` by default) |
|
||||
| `frequencyDictionary.sourcePath` | string | Path to a local frequency dictionary root. Leave empty or omit to use the built-in bundled dictionary search paths. |
|
||||
| `frequencyDictionary.topX` | number | Only color tokens whose frequency rank is `<= topX` (`1000` by default) |
|
||||
| `frequencyDictionary.mode` | string | `"single"` or `"banded"` (`"single"` by default) |
|
||||
| `frequencyDictionary.singleColor` | string | Color used for all highlighted tokens in single mode |
|
||||
| `frequencyDictionary.bandedColors` | string[] | Array of five hex colors used for ranked bands in banded mode |
|
||||
| `nPlusOneColor` | string | Existing n+1 highlight color (default: `#c6a0f6`) |
|
||||
| `knownWordColor` | string | Existing known-word highlight color (default: `#a6da95`) |
|
||||
| `jlptColors` | object | JLPT level underline colors object (`N1`..`N5`) |
|
||||
| `secondary` | object | Override any of the above for secondary subtitles (optional) |
|
||||
|
||||
JLPT underlining is powered by offline term-meta bank files at runtime. See [`docs/jlpt-vocab-bundle.md`](jlpt-vocab-bundle.md) for required files, source/version refresh steps, and deterministic fallback behavior.
|
||||
|
||||
Frequency dictionary highlighting uses the same dictionary file format as JLPT bundle lookups (`term_meta_bank_*.json` under discovered dictionary directories). A token is highlighted when it has a positive integer `frequencyRank` (lower is more common) and the rank is within `topX`.
|
||||
|
||||
Lookup behavior:
|
||||
|
||||
- Set `frequencyDictionary.sourcePath` to a directory containing `term_meta_bank_*.json` for a fully custom source.
|
||||
- If `sourcePath` is missing or empty, SubMiner uses bundled defaults from `vendor/jiten_freq_global` (packaged under `<resources>/jiten_freq_global` in distribution builds).
|
||||
- In both cases, only terms with a valid `frequencyRank` are used; everything else falls back to no highlighting.
|
||||
|
||||
In `single` mode all highlights use `singleColor`; in `banded` mode tokens map to five ascending color bands from most common to least common inside the topX window.
|
||||
|
||||
Secondary subtitle defaults: `fontSize: 24`, `fontColor: "#ffffff"`, `backgroundColor: "transparent"`. Any property not set in `secondary` falls back to the CSS defaults.
|
||||
|
||||
**See `config.example.jsonc`** for the complete list of subtitle style configuration options.
|
||||
|
||||
`jlptColors` keys are:
|
||||
|
||||
| Key | Default | Description |
|
||||
| ---- | --------- | ----------------------- |
|
||||
| `N1` | `#ed8796` | JLPT N1 underline color |
|
||||
| `N2` | `#f5a97f` | JLPT N2 underline color |
|
||||
| `N3` | `#f9e2af` | JLPT N3 underline color |
|
||||
| `N4` | `#a6e3a1` | JLPT N4 underline color |
|
||||
| `N5` | `#8aadf4` | JLPT N5 underline color |
|
||||
|
||||
### Texthooker
|
||||
|
||||
Control whether the browser opens automatically when texthooker starts:
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options.
|
||||
|
||||
```json
|
||||
{
|
||||
"texthooker": {
|
||||
"openBrowser": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### WebSocket Server
|
||||
|
||||
The overlay includes a built-in WebSocket server that broadcasts subtitle text to connected clients (such as texthooker-ui) for external processing.
|
||||
|
||||
By default, the server uses "auto" mode: it starts automatically unless [mpv_websocket](https://github.com/kuroahna/mpv_websocket) is detected at `~/.config/mpv/mpv_websocket`. If you have mpv_websocket installed, the built-in server is skipped to avoid conflicts.
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options.
|
||||
|
||||
```json
|
||||
{
|
||||
"websocket": {
|
||||
"enabled": "auto",
|
||||
"port": 6677
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| --------- | ------------------------- | -------------------------------------------------------- |
|
||||
| `enabled` | `true`, `false`, `"auto"` | `"auto"` (default) disables if mpv_websocket is detected |
|
||||
| `port` | number | WebSocket server port (default: 6677) |
|
||||
|
||||
### Immersion Tracking
|
||||
|
||||
Enable or disable local immersion analytics stored in SQLite for mined subtitles and media sessions:
|
||||
|
||||
72
docs/demos.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Feature Demos
|
||||
|
||||
Short recordings of SubMiner's key features and integrations from real playback sessions.
|
||||
|
||||
<script setup>
|
||||
const v = '20260301-1';
|
||||
</script>
|
||||
|
||||
## Anki Card Mining & Enrichment
|
||||
|
||||
Mine vocabulary cards from Yomitan or directly from subtitle lines. SubMiner automatically attaches the sentence, a timing-accurate audio clip, a screenshot, and a translation.
|
||||
|
||||
<video controls playsinline preload="metadata" :poster="`/assets/minecard-poster.jpg?v=${demoAssetVersion}`">
|
||||
<source :src="`/assets/minecard.webm?v=${demoAssetVersion}`" type="video/webm" />
|
||||
<source :src="`/assets/minecard.mp4?v=${demoAssetVersion}`" type="video/mp4" />
|
||||
<a :href="`/assets/minecard.webm?v=${demoAssetVersion}`" target="_blank" rel="noreferrer">
|
||||
<img :src="`/assets/minecard.webp?v=${demoAssetVersion}`" alt="SubMiner demo Animated fallback" style="width: 100%; height: auto;" />
|
||||
</a>
|
||||
</video>
|
||||
|
||||
::: info VIDEO COMING SOON
|
||||
:::
|
||||
|
||||
## Subtitle Download & Sync
|
||||
|
||||
Search and download subtitles from Jimaku, then automatically synchronize them with alass or ffsubsync — all from within SubMiner.
|
||||
|
||||
<!-- <video controls playsinline preload="metadata" :poster="`/assets/demos/subtitle-sync-poster.jpg?v=${v}`">
|
||||
<source :src="`/assets/demos/subtitle-sync.webm?v=${v}`" type="video/webm" />
|
||||
<source :src="`/assets/demos/subtitle-sync.mp4?v=${v}`" type="video/mp4" />
|
||||
</video> -->
|
||||
|
||||
::: info VIDEO COMING SOON
|
||||
:::
|
||||
|
||||
## Jellyfin Integration
|
||||
|
||||
Browse your Jellyfin library, cast to devices, and launch playback directly from SubMiner. Watch progress syncs back to your Jellyfin server.
|
||||
|
||||
<!-- <video controls playsinline preload="metadata" :poster="`/assets/demos/jellyfin-poster.jpg?v=${v}`">
|
||||
<source :src="`/assets/demos/jellyfin.webm?v=${v}`" type="video/webm" />
|
||||
<source :src="`/assets/demos/jellyfin.mp4?v=${v}`" type="video/mp4" />
|
||||
</video> -->
|
||||
|
||||
::: info VIDEO COMING SOON
|
||||
:::
|
||||
|
||||
## Texthooker
|
||||
|
||||
Open subtitles in an external texthooker page for use with browser-based tools and extensions alongside the overlay.
|
||||
|
||||
<!-- <video controls playsinline preload="metadata" :poster="`/assets/demos/texthooker-poster.jpg?v=${v}`">
|
||||
<source :src="`/assets/demos/texthooker.webm?v=${v}`" type="video/webm" />
|
||||
<source :src="`/assets/demos/texthooker.mp4?v=${v}`" type="video/mp4" />
|
||||
</video> -->
|
||||
|
||||
::: info VIDEO COMING SOON
|
||||
:::
|
||||
|
||||
<style>
|
||||
video {
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.28);
|
||||
margin: 0.75rem 0 2.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 2.5rem !important;
|
||||
}
|
||||
</style>
|
||||
@@ -60,6 +60,15 @@ bun run dev # builds + launches with --start --dev
|
||||
electron . --start --dev --log-level debug # equivalent Electron launch with verbose logging
|
||||
electron . --background # tray/background mode, minimal default logging
|
||||
make dev-start # build + launch via Makefile
|
||||
make dev-watch # watch TS + renderer and launch Electron (faster edit loop)
|
||||
make dev-watch-macos # same as dev-watch, forcing --backend macos
|
||||
```
|
||||
|
||||
For mpv-plugin-driven testing without exporting `SUBMINER_BINARY_PATH` each run, set a one-time
|
||||
dev binary path in `~/.config/mpv/script-opts/subminer.conf`:
|
||||
|
||||
```ini
|
||||
binary_path=/absolute/path/to/SubMiner/scripts/subminer-dev.sh
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
@@ -6,11 +6,14 @@ SubMiner stores immersion analytics in local SQLite (`immersion.sqlite`) by defa
|
||||
|
||||
- Write path is asynchronous and queue-backed.
|
||||
- Hot paths (subtitle parsing/render/token flows) enqueue telemetry/events and never await SQLite writes.
|
||||
- Background line processing also upserts to `imm_words` and `imm_kanji`.
|
||||
- Queue overflow policy is deterministic: drop oldest queued writes, keep newest.
|
||||
- Flush policy defaults to `25` writes or `500ms` max delay.
|
||||
- SQLite pragmas: `journal_mode=WAL`, `synchronous=NORMAL`, `foreign_keys=ON`, `busy_timeout=2500`.
|
||||
- Rollups now run incrementally from the last processed telemetry sample; startup performs a one-time bootstrap rebuild-equivalent pass.
|
||||
- If retention pruning removes telemetry/session rows, maintenance triggers a full rollup rebuild to resync historical aggregates.
|
||||
|
||||
## Schema (v1)
|
||||
## Schema (v3)
|
||||
|
||||
Schema versioning table:
|
||||
|
||||
@@ -18,15 +21,21 @@ Schema versioning table:
|
||||
|
||||
Core entities:
|
||||
|
||||
- `imm_videos`: video key/title/source metadata + optional media metadata fields
|
||||
- `imm_sessions`: session UUID, video reference, timing/status fields
|
||||
- `imm_session_telemetry`: high-frequency session aggregates over time
|
||||
- `imm_session_events`: event stream with compact numeric event types
|
||||
- `imm_videos`: video key/title/source metadata + optional media metadata fields, `CREATED_DATE`/`LAST_UPDATE_DATE`
|
||||
- `imm_sessions`: session UUID, video reference, timing/status fields, `CREATED_DATE`/`LAST_UPDATE_DATE`
|
||||
- `imm_session_telemetry`: high-frequency session aggregates over time, `CREATED_DATE`/`LAST_UPDATE_DATE`
|
||||
- `imm_session_events`: event stream with compact numeric event types, `CREATED_DATE`/`LAST_UPDATE_DATE`
|
||||
|
||||
Rollups:
|
||||
|
||||
- `imm_daily_rollups`
|
||||
- `imm_monthly_rollups`
|
||||
- `imm_daily_rollups`: includes `CREATED_DATE`/`LAST_UPDATE_DATE`
|
||||
- `imm_monthly_rollups`: includes `CREATED_DATE`/`LAST_UPDATE_DATE`
|
||||
|
||||
Vocabulary:
|
||||
|
||||
- `imm_words(id, headword, word, reading, first_seen, last_seen, frequency)`
|
||||
- `imm_kanji(id, kanji, first_seen, last_seen, frequency)`
|
||||
- `first_seen`/`last_seen` store Unix timestamps and are upserted with line ingestion
|
||||
|
||||
Primary index coverage:
|
||||
|
||||
@@ -147,4 +156,3 @@ FROM imm_monthly_rollups
|
||||
ORDER BY rollup_month DESC, video_id DESC
|
||||
LIMIT ?;
|
||||
```
|
||||
|
||||
|
||||
@@ -6,9 +6,19 @@ const docsIndexContents = readFileSync(docsIndexPath, 'utf8');
|
||||
|
||||
test('docs demo media uses shared cache-busting asset version token', () => {
|
||||
expect(docsIndexContents).toMatch(/const demoAssetVersion = ['"][^'"]+['"]/);
|
||||
expect(docsIndexContents).toContain(':poster="`/assets/minecard-poster.jpg?v=${demoAssetVersion}`"');
|
||||
expect(docsIndexContents).toContain('<source :src="`/assets/minecard.webm?v=${demoAssetVersion}`" type="video/webm" />');
|
||||
expect(docsIndexContents).toContain('<source :src="`/assets/minecard.mp4?v=${demoAssetVersion}`" type="video/mp4" />');
|
||||
expect(docsIndexContents).toContain('<a :href="`/assets/minecard.webm?v=${demoAssetVersion}`" target="_blank" rel="noreferrer">');
|
||||
expect(docsIndexContents).toContain('<img :src="`/assets/minecard.gif?v=${demoAssetVersion}`" alt="SubMiner demo GIF fallback" style="width: 100%; height: auto;" />');
|
||||
expect(docsIndexContents).toContain(
|
||||
':poster="`/assets/minecard-poster.jpg?v=${demoAssetVersion}`"',
|
||||
);
|
||||
expect(docsIndexContents).toContain(
|
||||
'<source :src="`/assets/minecard.webm?v=${demoAssetVersion}`" type="video/webm" />',
|
||||
);
|
||||
expect(docsIndexContents).toContain(
|
||||
'<source :src="`/assets/minecard.mp4?v=${demoAssetVersion}`" type="video/mp4" />',
|
||||
);
|
||||
expect(docsIndexContents).toContain(
|
||||
'<a :href="`/assets/minecard.webm?v=${demoAssetVersion}`" target="_blank" rel="noreferrer">',
|
||||
);
|
||||
expect(docsIndexContents).toContain(
|
||||
'<img :src="`/assets/minecard.webp?v=${demoAssetVersion}`" alt="SubMiner demo Animated fallback" style="width: 100%; height: auto;" />',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ titleTemplate: Immersion Mining Workflow for MPV
|
||||
hero:
|
||||
name: SubMiner
|
||||
text: Immersion Mining for MPV
|
||||
tagline: Watch media, mine vocabulary, and build cards without leaving the scene.
|
||||
tagline: Watch media, mine vocabulary, and craft anki cards without leaving the scene.
|
||||
image:
|
||||
src: /assets/SubMiner.png
|
||||
alt: SubMiner logo
|
||||
@@ -35,16 +35,11 @@ features:
|
||||
alt: Anki card icon
|
||||
title: Anki Card Enrichment
|
||||
details: Auto-fills card fields with subtitle sentence, clipping, image, and translation so you can focus on learning.
|
||||
- icon:
|
||||
src: /assets/dual-layer.svg
|
||||
alt: Dual layer icon
|
||||
title: Three-Plane Overlay Stack
|
||||
details: Secondary context plane + visible interactive layer + invisible interaction plane, each with independent behavior and startup state.
|
||||
- icon:
|
||||
src: /assets/highlight.svg
|
||||
alt: Highlight icon
|
||||
title: N+1 Highlighting
|
||||
details: Surfaces known words from your deck so unknown targets stand out during immersion sessions.
|
||||
title: Reading Annotations
|
||||
details: Combines N+1 targeting, Jiten frequency highlighting, and JLPT tagging so useful cues stay visible while you read.
|
||||
- icon:
|
||||
src: /assets/tokenization.svg
|
||||
alt: Tokenization icon
|
||||
@@ -55,16 +50,6 @@ features:
|
||||
alt: Subtitle download icon
|
||||
title: Subtitle Download & Sync
|
||||
details: Pull and synchronize subtitles with Jimaku plus alass/ffsubsync in one cohesive workflow.
|
||||
- icon:
|
||||
src: /assets/keyboard.svg
|
||||
alt: Keyboard icon
|
||||
title: Keyboard-Driven
|
||||
details: Run lookups, mining actions, clipping, and workflow toggles with one configurable shortcut surface.
|
||||
- icon:
|
||||
src: /assets/texthooker.svg
|
||||
alt: Texthooker icon
|
||||
title: Texthooker & WebSocket
|
||||
details: Stream subtitles in real time to browser tools via local WebSocket and keep your stack integrated.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
@@ -110,7 +95,7 @@ const demoAssetVersion = '20260223-2';
|
||||
<source :src="`/assets/minecard.webm?v=${demoAssetVersion}`" type="video/webm" />
|
||||
<source :src="`/assets/minecard.mp4?v=${demoAssetVersion}`" type="video/mp4" />
|
||||
<a :href="`/assets/minecard.webm?v=${demoAssetVersion}`" target="_blank" rel="noreferrer">
|
||||
<img :src="`/assets/minecard.gif?v=${demoAssetVersion}`" alt="SubMiner demo GIF fallback" style="width: 100%; height: auto;" />
|
||||
<img :src="`/assets/minecard.webp?v=${demoAssetVersion}`" alt="SubMiner demo Animated fallback" style="width: 100%; height: auto;" />
|
||||
</a>
|
||||
</video>
|
||||
</section>
|
||||
|
||||
@@ -150,7 +150,9 @@ wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer-asse
|
||||
tar -xzf /tmp/subminer-assets.tar.gz -C /tmp
|
||||
mkdir -p ~/.config/SubMiner
|
||||
cp /tmp/config.example.jsonc ~/.config/SubMiner/config.jsonc
|
||||
cp /tmp/plugin/subminer.lua ~/.config/mpv/scripts/
|
||||
mkdir -p ~/.config/mpv/scripts/subminer
|
||||
mkdir -p ~/.config/mpv/script-opts
|
||||
cp -R /tmp/plugin/subminer/. ~/.config/mpv/scripts/subminer/
|
||||
cp /tmp/plugin/subminer.conf ~/.config/mpv/script-opts/
|
||||
|
||||
# Option 2: from source checkout
|
||||
@@ -181,9 +183,6 @@ All keybindings use a `y` chord prefix — press `y`, then the second key:
|
||||
| `y-s` | Start overlay |
|
||||
| `y-S` | Stop overlay |
|
||||
| `y-t` | Toggle visible overlay |
|
||||
| `y-i` | Toggle invisible overlay |
|
||||
| `y-I` | Show invisible overlay |
|
||||
| `y-u` | Hide invisible overlay |
|
||||
| `y-o` | Open Yomitan settings |
|
||||
| `y-r` | Restart overlay |
|
||||
| `y-c` | Check overlay status |
|
||||
@@ -195,7 +194,10 @@ See [MPV Plugin](/mpv-plugin) for the full configuration reference, script messa
|
||||
After installing, confirm SubMiner is working:
|
||||
|
||||
```bash
|
||||
# Start the overlay (connects to mpv IPC)
|
||||
# Play a file (default plugin config auto-starts visible overlay and waits for annotation readiness)
|
||||
subminer video.mkv
|
||||
|
||||
# Optional explicit overlay start for setups with plugin auto_start=no
|
||||
subminer --start video.mkv
|
||||
|
||||
# Useful launch modes for troubleshooting
|
||||
|
||||
@@ -26,7 +26,7 @@ The expected files are:
|
||||
|
||||
Each bank maps terms to frequency metadata; only entries with a `frequency.displayValue` are considered for JLPT tagging.
|
||||
|
||||
SubMiner also reuses the same `term_meta_bank_*.json` format for frequency-based subtitle highlighting. The default frequency source is now bundled as `vendor/jiten_freq_global`, so users can enable `subtitleStyle.frequencyDictionary` without extra setup.
|
||||
SubMiner also reuses the same `term_meta_bank_*.json` format for frequency-based subtitle highlighting, using installed/default `frequency-dictionary` locations or an explicit `subtitleStyle.frequencyDictionary.sourcePath`.
|
||||
|
||||
## Source and update process
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ subminer -r -d ~/Anime # recursive search
|
||||
|
||||
fzf shows video files in a fuzzy-searchable list. If `chafa` is installed, you get thumbnail previews in the right pane. Thumbnails are sourced from the freedesktop thumbnail cache first, then generated on the fly with `ffmpegthumbnailer` or `ffmpeg` as fallback.
|
||||
|
||||
| Optional tool | Purpose |
|
||||
| --------------------- | -------------------------------- |
|
||||
| `chafa` | Render thumbnails in the terminal |
|
||||
| `ffmpegthumbnailer` | Generate thumbnails on the fly |
|
||||
| Optional tool | Purpose |
|
||||
| ------------------- | --------------------------------- |
|
||||
| `chafa` | Render thumbnails in the terminal |
|
||||
| `ffmpegthumbnailer` | Generate thumbnails on the fly |
|
||||
|
||||
### rofi
|
||||
|
||||
@@ -53,46 +53,49 @@ SUBMINER_ROFI_THEME=/path/to/custom-theme.rasi subminer -R
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
subminer video.mkv # play a specific file
|
||||
subminer --start video.mkv # play + explicitly start overlay
|
||||
subminer video.mkv # play a specific file (default plugin config auto-starts visible overlay)
|
||||
subminer --start video.mkv # optional explicit overlay start when plugin auto_start=no
|
||||
subminer -S video.mkv # same as above via --start-overlay
|
||||
subminer https://youtu.be/... # YouTube playback (requires yt-dlp)
|
||||
subminer ytsearch:"jp news" # YouTube search
|
||||
```
|
||||
|
||||
## Subcommands
|
||||
|
||||
| Subcommand | Purpose |
|
||||
| ------------------------- | ---------------------------------------------- |
|
||||
| Subcommand | Purpose |
|
||||
| -------------------------- | ---------------------------------------------------------- |
|
||||
| `subminer jellyfin` / `jf` | Jellyfin workflows (`-d` discovery, `-p` play, `-l` login) |
|
||||
| `subminer yt` / `youtube` | YouTube shorthand (`-o`, `-m`) |
|
||||
| `subminer doctor` | Dependency + config + socket diagnostics |
|
||||
| `subminer config path` | Print active config file path |
|
||||
| `subminer config show` | Print active config contents |
|
||||
| `subminer mpv status` | Check mpv socket readiness |
|
||||
| `subminer mpv socket` | Print active socket path |
|
||||
| `subminer mpv idle` | Launch detached idle mpv instance |
|
||||
| `subminer texthooker` | Launch texthooker-only mode |
|
||||
| `subminer app` | Pass arguments directly to SubMiner binary |
|
||||
| `subminer yt` / `youtube` | YouTube shorthand (`-o`, `-m`) |
|
||||
| `subminer doctor` | Dependency + config + socket diagnostics |
|
||||
| `subminer config path` | Print active config file path |
|
||||
| `subminer config show` | Print active config contents |
|
||||
| `subminer mpv status` | Check mpv socket readiness |
|
||||
| `subminer mpv socket` | Print active socket path |
|
||||
| `subminer mpv idle` | Launch detached idle mpv instance |
|
||||
| `subminer texthooker` | Launch texthooker-only mode |
|
||||
| `subminer app` | Pass arguments directly to SubMiner binary |
|
||||
|
||||
Use `subminer <subcommand> -h` for command-specific help.
|
||||
|
||||
## Options
|
||||
|
||||
| Flag | Description |
|
||||
| -------------------- | -------------------------------------------- |
|
||||
| `-d, --directory` | Video search directory (default: cwd) |
|
||||
| `-r, --recursive` | Search directories recursively |
|
||||
| `-R, --rofi` | Use rofi instead of fzf |
|
||||
| `-S, --start` | Start overlay after mpv launches |
|
||||
| `-T, --no-texthooker`| Disable texthooker server |
|
||||
| `-p, --profile` | mpv profile name (default: `subminer`) |
|
||||
| `-b, --backend` | Force window backend (`hyprland`, `sway`, `x11`) |
|
||||
| `--log-level` | Logger verbosity (`debug`, `info`, `warn`, `error`) |
|
||||
| `--dev`, `--debug` | Enable app dev-mode (not tied to log level) |
|
||||
| Flag | Description |
|
||||
| ----------------------- | --------------------------------------------------- |
|
||||
| `-d, --directory` | Video search directory (default: cwd) |
|
||||
| `-r, --recursive` | Search directories recursively |
|
||||
| `-R, --rofi` | Use rofi instead of fzf |
|
||||
| `--start` | Explicitly start overlay after mpv launches |
|
||||
| `-S, --start-overlay` | Explicitly start overlay after mpv launches |
|
||||
| `-T, --no-texthooker` | Disable texthooker server |
|
||||
| `-p, --profile` | mpv profile name (default: `subminer`) |
|
||||
| `-b, --backend` | Force window backend (`hyprland`, `sway`, `x11`) |
|
||||
| `--log-level` | Logger verbosity (`debug`, `info`, `warn`, `error`) |
|
||||
| `--dev`, `--debug` | Enable app dev-mode (not tied to log level) |
|
||||
|
||||
With default plugin settings (`auto_start=yes`, `auto_start_visible_overlay=yes`, `auto_start_pause_until_ready=yes`), explicit start flags are usually unnecessary.
|
||||
|
||||
## Logging
|
||||
|
||||
- Default log level is `info`
|
||||
- `--background` mode defaults to `warn` unless `--log-level` is explicitly set
|
||||
- `--dev` / `--debug` control app behavior, not logging verbosity — use `--log-level` for that
|
||||
|
||||
|
||||
@@ -20,49 +20,36 @@ SubMiner prioritizes subtitle responsiveness over heavy initialization:
|
||||
1. The first subtitle render is **plain text first** (no tokenization wait).
|
||||
2. Tokenized enrichment (word spans, known-word flags, JLPT/frequency metadata) is applied right after parsing completes.
|
||||
3. Under rapid subtitle churn, SubMiner uses a **latest-only tokenization queue** so stale lines are dropped instead of building lag.
|
||||
4. MeCab, Yomitan extension load, and dictionary prewarm run as background warmups after overlay initialization.
|
||||
4. MeCab, Yomitan extension load, and dictionary prewarm run as background warmups after overlay initialization (configurable via `startupWarmups`, including low-power mode).
|
||||
|
||||
This keeps early playback snappy and avoids mpv-side sluggishness while startup work completes.
|
||||
|
||||
## The Three Overlay Planes
|
||||
## Overlay Model
|
||||
|
||||
SubMiner uses three overlay planes, each serving a different purpose.
|
||||
SubMiner uses one overlay window with modal surfaces.
|
||||
|
||||
### Visible Overlay
|
||||
### Primary Subtitle Layer
|
||||
|
||||
The visible overlay renders subtitles as tokenized, clickable word spans. Each word is a separate element with reading and headword data attached. This plane is styled independently from mpv subtitles and supports:
|
||||
|
||||
- Word-level click targets for Yomitan lookup
|
||||
- Auto pause/resume on subtitle hover (enabled by default via `subtitleStyle.autoPauseVideoOnHover`)
|
||||
- Right-click to pause/resume
|
||||
- Right-click + drag to reposition subtitles
|
||||
- Modal dialogs for Jimaku search, field grouping, subsync, and runtime options
|
||||
- **N+1 highlighting** — known words from your Anki deck are visually highlighted
|
||||
|
||||
Toggle with `Alt+Shift+O` (global) or `y-t` (mpv plugin).
|
||||
Toggle visibility with `Alt+Shift+O` (global) or `y-t` (mpv plugin).
|
||||
|
||||
### Secondary Subtitle Plane
|
||||
### Secondary Subtitle Bar
|
||||
|
||||
The secondary plane is a compact top-strip layer for translation and context visibility while keeping primary reading flow below. It mirrors your configured secondary subtitle preference and can be independently shown or hidden.
|
||||
The secondary subtitle bar is a compact top-strip region in the same overlay window for translation/context visibility while keeping primary reading flow below. It mirrors your configured secondary subtitle preference and can be independently shown or hidden.
|
||||
|
||||
It is controlled by `secondarySub` configuration and shares lifecycle with the overlay stack.
|
||||
It is controlled by `secondarySub` configuration and shares lifecycle with the main overlay window.
|
||||
|
||||
### Invisible Overlay
|
||||
### Modal Surfaces
|
||||
|
||||
The invisible overlay is a transparent layer aligned with mpv's own subtitle rendering. It uses mpv's subtitle metrics (font size, margins, position, scaling) to map click targets accurately.
|
||||
|
||||
This layer still supports:
|
||||
|
||||
- Word-level click-through lookups over the text region
|
||||
- Optional manual position fine-tuning in pixel mode
|
||||
- Independent toggle behavior with global shortcuts
|
||||
|
||||
Position edit mode is available via `Ctrl/Cmd+Shift+P`, then arrow keys / `hjkl` to nudge position; `Shift` moves faster. Save with `Enter` or `Ctrl+S`, cancel with `Esc`.
|
||||
|
||||
Toggle controls:
|
||||
|
||||
- `Alt+Shift+O` / `y-t`: visible overlay
|
||||
- `Alt+Shift+I` / `y-i`: invisible overlay
|
||||
- Secondary plane visibility is controlled via `secondarySub` config and matching global shortcuts.
|
||||
Jimaku search, field-grouping, runtime options, and manual subsync open as modal surfaces on top of the same overlay window.
|
||||
|
||||
## Looking Up Words
|
||||
|
||||
@@ -73,10 +60,10 @@ Toggle controls:
|
||||
3. Yomitan detects the text selection and opens its popup with dictionary results.
|
||||
4. From the Yomitan popup, you can add the word directly to Anki.
|
||||
|
||||
### On the Invisible Overlay
|
||||
### On Overlay Subtitles
|
||||
|
||||
1. The invisible layer sits over mpv's own subtitle text.
|
||||
2. Click on any word in the subtitle — SubMiner maps your click position to the underlying text.
|
||||
1. Subtitles are rendered directly in the overlay.
|
||||
2. Click on any word in the subtitle.
|
||||
3. On macOS, word selection happens automatically on hover.
|
||||
4. Yomitan popup appears for lookup and card creation.
|
||||
|
||||
@@ -86,11 +73,13 @@ There are three ways to create cards, depending on your workflow.
|
||||
|
||||
### 1. Auto-Update from Yomitan
|
||||
|
||||
This is the most common flow. Yomitan creates a card in Anki, and SubMiner detects it via polling and enriches it automatically.
|
||||
This is the most common flow. Yomitan creates a card in Anki, and SubMiner enriches it automatically.
|
||||
|
||||
1. Click a word → Yomitan popup appears.
|
||||
2. Click the Anki icon in Yomitan to add the word.
|
||||
3. SubMiner detects the new card (polls AnkiConnect every 3 seconds by default).
|
||||
3. SubMiner receives or detects the new card:
|
||||
- **Proxy mode** (`ankiConnect.proxy.enabled: true`): immediate enrich after successful `addNote` / `addNotes`.
|
||||
- **Polling mode** (default): detects via AnkiConnect polling (`ankiConnect.pollingRate`, default 3 seconds).
|
||||
4. SubMiner updates the card with:
|
||||
- **Sentence**: The current subtitle line.
|
||||
- **Audio**: Extracted from the video using the subtitle's start/end timing (plus configurable padding).
|
||||
@@ -109,13 +98,13 @@ If you prefer a hands-on approach (animecards-style), you can copy the current s
|
||||
- For multiple lines: press `Ctrl/Cmd+Shift+C`, then a digit `1`–`9` to select how many recent subtitle lines to combine. The combined text is copied to the clipboard.
|
||||
3. Press `Ctrl/Cmd+V` to update the last-added card with the clipboard contents plus audio, image, and translation — the same fields auto-update would fill.
|
||||
|
||||
This is useful when auto-update polling is disabled or when you want explicit control over which subtitle line gets attached to the card.
|
||||
This is useful when auto-update is disabled or when you want explicit control over which subtitle line gets attached to the card.
|
||||
|
||||
| Shortcut | Action | Config key |
|
||||
| --------------------------- | ----------------------------------------- | ------------------------------------- |
|
||||
| `Ctrl/Cmd+C` | Copy current subtitle | `shortcuts.copySubtitle` |
|
||||
| `Ctrl/Cmd+Shift+C` + digit | Copy multiple recent lines | `shortcuts.copySubtitleMultiple` |
|
||||
| `Ctrl/Cmd+V` | Update last card from clipboard | `shortcuts.updateLastCardFromClipboard` |
|
||||
| Shortcut | Action | Config key |
|
||||
| -------------------------- | ------------------------------- | --------------------------------------- |
|
||||
| `Ctrl/Cmd+C` | Copy current subtitle | `shortcuts.copySubtitle` |
|
||||
| `Ctrl/Cmd+Shift+C` + digit | Copy multiple recent lines | `shortcuts.copySubtitleMultiple` |
|
||||
| `Ctrl/Cmd+V` | Update last card from clipboard | `shortcuts.updateLastCardFromClipboard` |
|
||||
|
||||
### 3. Mine Sentence (Hotkey)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MPV Plugin
|
||||
|
||||
The SubMiner mpv plugin (`subminer.lua`) provides in-player keybindings to control the overlay without leaving mpv. It communicates with SubMiner by invoking the AppImage (or binary) with CLI flags.
|
||||
The SubMiner mpv plugin (`subminer/main.lua`) provides in-player keybindings to control the overlay without leaving mpv. It communicates with SubMiner by invoking the AppImage (or binary) with CLI flags.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -10,7 +10,9 @@ wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer-asse
|
||||
tar -xzf /tmp/subminer-assets.tar.gz -C /tmp
|
||||
mkdir -p ~/.config/SubMiner
|
||||
cp /tmp/config.example.jsonc ~/.config/SubMiner/config.jsonc
|
||||
cp /tmp/plugin/subminer.lua ~/.config/mpv/scripts/
|
||||
mkdir -p ~/.config/mpv/scripts/subminer
|
||||
mkdir -p ~/.config/mpv/script-opts
|
||||
cp -R /tmp/plugin/subminer/. ~/.config/mpv/scripts/subminer/
|
||||
cp /tmp/plugin/subminer.conf ~/.config/mpv/script-opts/
|
||||
|
||||
# Or from source checkout: make install-plugin
|
||||
@@ -27,19 +29,16 @@ input-ipc-server=/tmp/subminer-socket
|
||||
|
||||
All keybindings use a `y` chord prefix — press `y`, then the second key:
|
||||
|
||||
| Chord | Action |
|
||||
| ----- | ------------------------ |
|
||||
| `y-y` | Open menu |
|
||||
| `y-s` | Start overlay |
|
||||
| `y-S` | Stop overlay |
|
||||
| `y-t` | Toggle visible overlay |
|
||||
| `y-i` | Toggle invisible overlay |
|
||||
| `y-I` | Show invisible overlay |
|
||||
| `y-u` | Hide invisible overlay |
|
||||
| `y-o` | Open settings window |
|
||||
| `y-r` | Restart overlay |
|
||||
| `y-c` | Check status |
|
||||
| `y-k` | Skip intro (AniSkip) |
|
||||
| Chord | Action |
|
||||
| ----- | ---------------------- |
|
||||
| `y-y` | Open menu |
|
||||
| `y-s` | Start overlay |
|
||||
| `y-S` | Stop overlay |
|
||||
| `y-t` | Toggle visible overlay |
|
||||
| `y-o` | Open settings window |
|
||||
| `y-r` | Restart overlay |
|
||||
| `y-c` | Check status |
|
||||
| `y-k` | Skip intro (AniSkip) |
|
||||
|
||||
## Menu
|
||||
|
||||
@@ -50,10 +49,9 @@ SubMiner:
|
||||
1. Start overlay
|
||||
2. Stop overlay
|
||||
3. Toggle overlay
|
||||
4. Toggle invisible overlay
|
||||
5. Open options
|
||||
6. Restart overlay
|
||||
7. Check status
|
||||
4. Open options
|
||||
5. Restart overlay
|
||||
6. Check status
|
||||
```
|
||||
|
||||
Select an item by pressing its number.
|
||||
@@ -79,14 +77,16 @@ texthooker_port=5174
|
||||
backend=auto
|
||||
|
||||
# Start the overlay automatically when a file is loaded.
|
||||
auto_start=no
|
||||
# Runs only when mpv input-ipc-server matches socket_path.
|
||||
auto_start=yes
|
||||
|
||||
# Show the visible overlay on auto-start.
|
||||
auto_start_visible_overlay=no
|
||||
# Runs only when mpv input-ipc-server matches socket_path.
|
||||
auto_start_visible_overlay=yes
|
||||
|
||||
# Invisible overlay startup: platform-default, visible, hidden.
|
||||
# platform-default = hidden on Linux, visible on macOS/Windows.
|
||||
auto_start_invisible_overlay=platform-default
|
||||
# Pause mpv on visible auto-start until SubMiner signals overlay/tokenization readiness.
|
||||
# Requires auto_start=yes and auto_start_visible_overlay=yes.
|
||||
auto_start_pause_until_ready=yes
|
||||
|
||||
# Show OSD messages for overlay status changes.
|
||||
osd_messages=yes
|
||||
@@ -120,27 +120,27 @@ aniskip_button_duration=3
|
||||
|
||||
### Option Reference
|
||||
|
||||
| Option | Default | Values | Description |
|
||||
| ------------------------------ | ---------------------- | ------------------------------------------ | -------------------------------- |
|
||||
| `binary_path` | `""` (auto-detect) | file path | Path to SubMiner binary |
|
||||
| `socket_path` | `/tmp/subminer-socket` | file path | MPV IPC socket path |
|
||||
| `texthooker_enabled` | `yes` | `yes` / `no` | Enable texthooker server |
|
||||
| `texthooker_port` | `5174` | 1–65535 | Texthooker server port |
|
||||
| `backend` | `auto` | `auto`, `hyprland`, `sway`, `x11`, `macos` | Window manager backend |
|
||||
| `auto_start` | `no` | `yes` / `no` | Auto-start overlay on file load |
|
||||
| `auto_start_visible_overlay` | `no` | `yes` / `no` | Show visible layer on auto-start |
|
||||
| `auto_start_invisible_overlay` | `platform-default` | `platform-default`, `visible`, `hidden` | Invisible layer on auto-start |
|
||||
| `osd_messages` | `yes` | `yes` / `no` | Show OSD status messages |
|
||||
| `log_level` | `info` | `debug`, `info`, `warn`, `error` | Log verbosity |
|
||||
| `aniskip_enabled` | `yes` | `yes` / `no` | Enable AniSkip intro detection |
|
||||
| `aniskip_title` | `""` | string | Override title used for lookup |
|
||||
| `aniskip_season` | `""` | numeric season | Optional season hint |
|
||||
| `aniskip_mal_id` | `""` | numeric MAL id | Skip title lookup; use fixed id |
|
||||
| `aniskip_episode` | `""` | numeric episode | Skip episode parsing; use fixed |
|
||||
| `aniskip_show_button` | `yes` | `yes` / `no` | Show in-range intro skip prompt |
|
||||
| `aniskip_button_text` | `You can skip by pressing %s` | string | OSD prompt format (`%s`=key) |
|
||||
| `aniskip_button_key` | `y-k` | mpv key chord | Primary key for intro skip action (`y-k` always works as fallback) |
|
||||
| `aniskip_button_duration` | `3` | float seconds | OSD hint duration |
|
||||
| Option | Default | Values | Description |
|
||||
| ---------------------------- | ----------------------------- | ------------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| `binary_path` | `""` (auto-detect) | file path | Path to SubMiner binary |
|
||||
| `socket_path` | `/tmp/subminer-socket` | file path | MPV IPC socket path |
|
||||
| `texthooker_enabled` | `yes` | `yes` / `no` | Enable texthooker server |
|
||||
| `texthooker_port` | `5174` | 1–65535 | Texthooker server port |
|
||||
| `backend` | `auto` | `auto`, `hyprland`, `sway`, `x11`, `macos` | Window manager backend |
|
||||
| `auto_start` | `yes` | `yes` / `no` | Auto-start overlay on file load when mpv socket matches `socket_path` |
|
||||
| `auto_start_visible_overlay` | `yes` | `yes` / `no` | Show visible layer on auto-start when mpv socket matches `socket_path` |
|
||||
| `auto_start_pause_until_ready` | `yes` | `yes` / `no` | Pause mpv on visible auto-start; resume when SubMiner signals tokenization-ready |
|
||||
| `osd_messages` | `yes` | `yes` / `no` | Show OSD status messages |
|
||||
| `log_level` | `info` | `debug`, `info`, `warn`, `error` | Log verbosity |
|
||||
| `aniskip_enabled` | `yes` | `yes` / `no` | Enable AniSkip intro detection |
|
||||
| `aniskip_title` | `""` | string | Override title used for lookup |
|
||||
| `aniskip_season` | `""` | numeric season | Optional season hint |
|
||||
| `aniskip_mal_id` | `""` | numeric MAL id | Skip title lookup; use fixed id |
|
||||
| `aniskip_episode` | `""` | numeric episode | Skip episode parsing; use fixed |
|
||||
| `aniskip_show_button` | `yes` | `yes` / `no` | Show in-range intro skip prompt |
|
||||
| `aniskip_button_text` | `You can skip by pressing %s` | string | OSD prompt format (`%s`=key) |
|
||||
| `aniskip_button_key` | `y-k` | mpv key chord | Primary key for intro skip action (`y-k` always works as fallback) |
|
||||
| `aniskip_button_duration` | `3` | float seconds | OSD hint duration |
|
||||
|
||||
## Binary Auto-Detection
|
||||
|
||||
@@ -182,13 +182,11 @@ The plugin can be controlled from other mpv scripts or the mpv command line usin
|
||||
script-message subminer-start
|
||||
script-message subminer-stop
|
||||
script-message subminer-toggle
|
||||
script-message subminer-toggle-invisible
|
||||
script-message subminer-show-invisible
|
||||
script-message subminer-hide-invisible
|
||||
script-message subminer-menu
|
||||
script-message subminer-options
|
||||
script-message subminer-restart
|
||||
script-message subminer-status
|
||||
script-message subminer-autoplay-ready
|
||||
script-message subminer-aniskip-refresh
|
||||
script-message subminer-skip-intro
|
||||
```
|
||||
@@ -204,7 +202,12 @@ script-message subminer-start backend=hyprland socket=/custom/path texthooker=no
|
||||
|
||||
## AniSkip Intro Skip
|
||||
|
||||
- On file load, plugin resolves title + episode, resolves MAL id, then calls AniSkip API.
|
||||
- AniSkip lookups are gated. The plugin only runs lookup when:
|
||||
- SubMiner launcher metadata is present, or
|
||||
- SubMiner app process is already running, or
|
||||
- You explicitly call `script-message subminer-aniskip-refresh`.
|
||||
- Lookups are asynchronous (no blocking `ps`/`curl` on `file-loaded`).
|
||||
- MAL/title resolution is cached for the current mpv session.
|
||||
- When launched via `subminer`, launcher runs `guessit` first (file targets) and passes title/season/episode to the plugin; fallback is filename-derived title.
|
||||
- Install `guessit` for best detection quality (`python3 -m pip install --user guessit`).
|
||||
- If OP interval exists, plugin adds `AniSkip Intro Start` and `AniSkip Intro End` chapters.
|
||||
@@ -213,7 +216,9 @@ script-message subminer-start backend=hyprland socket=/custom/path texthooker=no
|
||||
|
||||
## Lifecycle
|
||||
|
||||
- **File loaded**: If `auto_start=yes`, the plugin starts the overlay and applies visibility preferences after a short delay.
|
||||
- **File loaded**: If `auto_start=yes`, the plugin starts the overlay, then defers AniSkip lookup until after startup delay.
|
||||
- **Auto-start pause gate**: If `auto_start_visible_overlay=yes` and `auto_start_pause_until_ready=yes`, launcher starts mpv paused and the plugin resumes playback after SubMiner reports tokenization-ready (with timeout fallback).
|
||||
- **Duplicate auto-start events**: Repeated `file-loaded` hooks while overlay is already running are ignored for auto-start triggers (prevents duplicate start attempts).
|
||||
- **MPV shutdown**: The plugin sends a stop command to gracefully shut down both the overlay and the texthooker server.
|
||||
- **Texthooker**: Starts as a separate subprocess before the overlay to ensure the app lock is acquired first.
|
||||
|
||||
|
||||
60
docs/plans/2026-02-26-secondary-subtitles-main-overlay.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Secondary Subtitles Main Overlay Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Ensure secondary subtitles render in the unified main overlay window and remove stale secondary-window/layer paths.
|
||||
|
||||
**Architecture:** Keep secondary subtitle DOM in the shared renderer tree, rely on mode classes (`secondary-sub-hidden|visible|hover`) for visibility, and remove obsolete legacy overlay-layer assumptions. Preserve modal behavior and existing subtitle rendering flow.
|
||||
|
||||
**Tech Stack:** TypeScript, Electron renderer CSS/DOM, Bun test runner.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add Regression Tests For Main Overlay Secondary Rendering
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `src/renderer/subtitle-render.test.ts`
|
||||
- Modify: `src/renderer/error-recovery.test.ts`
|
||||
|
||||
**Step 1: Write failing tests**
|
||||
|
||||
- Assert stylesheet no longer hides secondary subtitles in `layer-visible`.
|
||||
- Assert renderer platform resolution ignores legacy `secondary` overlay layer.
|
||||
|
||||
**Step 2: Run tests to verify failures**
|
||||
|
||||
Run: `bun test src/renderer/subtitle-render.test.ts src/renderer/error-recovery.test.ts`
|
||||
Expected: FAIL on secondary subtitle hide rule + legacy secondary layer handling.
|
||||
|
||||
### Task 2: Remove Secondary-Window CSS/Routing Assumptions
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `src/renderer/style.css`
|
||||
- Modify: `src/renderer/utils/platform.ts`
|
||||
- Modify: `src/renderer/error-recovery.ts`
|
||||
- Modify: `src/types.ts`
|
||||
|
||||
**Step 1: Implement minimal changes**
|
||||
|
||||
- Remove legacy forced hide on `#secondarySubContainer`.
|
||||
- Remove obsolete layer-specific secondary-subtitle CSS blocks.
|
||||
- Drop legacy `secondary` overlay-layer parsing path from renderer platform resolver.
|
||||
- Narrow related overlay layer type unions.
|
||||
|
||||
**Step 2: Run targeted tests**
|
||||
|
||||
Run: `bun test src/renderer/subtitle-render.test.ts src/renderer/error-recovery.test.ts`
|
||||
Expected: PASS.
|
||||
|
||||
### Task 3: Validate Wider Related Surface
|
||||
|
||||
**Files:**
|
||||
|
||||
- No additional code changes required.
|
||||
|
||||
**Step 1: Run broader related tests**
|
||||
|
||||
Run: `bun test src/renderer/subtitle-render.test.ts src/renderer/error-recovery.test.ts src/main/runtime/overlay-window-runtime-handlers.test.ts src/main/runtime/overlay-window-factory.test.ts src/core/services/overlay-manager.test.ts`
|
||||
Expected: Renderer tests pass; report any unrelated pre-existing failures.
|
||||
@@ -1,15 +1,40 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="ac" x1="6" y1="6" x2="36" y2="42" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="ac-card" x1="6" y1="8" x2="38" y2="44" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#34d399"/>
|
||||
<stop offset="1" stop-color="#059669"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="ac-glow" x1="8" y1="10" x2="36" y2="42" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6ee7b7" stop-opacity="0.5"/>
|
||||
<stop offset="1" stop-color="#059669" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<filter id="ac-soft" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="1.2"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect x="12" y="5" width="24" height="34" rx="3" fill="#059669" opacity="0.18"/>
|
||||
<rect x="8" y="9" width="24" height="34" rx="3" fill="url(#ac)"/>
|
||||
<rect x="13" y="18" width="14" height="2.5" rx="1.25" fill="white" opacity="0.85"/>
|
||||
<rect x="13" y="24" width="10" height="2.5" rx="1.25" fill="white" opacity="0.4"/>
|
||||
<rect x="13" y="30" width="12" height="2.5" rx="1.25" fill="white" opacity="0.4"/>
|
||||
<path d="M39.5 8l1.8 4.2 4.2 1.8-4.2 1.8L39.5 20l-1.8-4.2L33.5 14l4.2-1.8z" fill="#34d399"/>
|
||||
<path d="M36 27l1 2.3 2.3 1-2.3 1L36 33.5l-1-2.2-2.3-1 2.3-1z" fill="#34d399" opacity="0.45"/>
|
||||
<!-- Glow aura behind card -->
|
||||
<rect x="6" y="8" width="28" height="36" rx="5" fill="url(#ac-glow)" filter="url(#ac-soft)"/>
|
||||
<!-- Shadow card (back) -->
|
||||
<rect x="14" y="5" width="26" height="34" rx="4" fill="#059669" opacity="0.15"/>
|
||||
<!-- Main card -->
|
||||
<rect x="6" y="9" width="26" height="34" rx="4" fill="url(#ac-card)"/>
|
||||
<!-- Sentence line -->
|
||||
<rect x="10" y="16" width="16" height="2.5" rx="1.25" fill="white" opacity="0.9"/>
|
||||
<!-- Audio waveform mini -->
|
||||
<rect x="10" y="22" width="1.8" height="5" rx="0.9" fill="white" opacity="0.55"/>
|
||||
<rect x="13" y="20.5" width="1.8" height="8" rx="0.9" fill="white" opacity="0.55"/>
|
||||
<rect x="16" y="21.5" width="1.8" height="6" rx="0.9" fill="white" opacity="0.55"/>
|
||||
<rect x="19" y="23" width="1.8" height="3" rx="0.9" fill="white" opacity="0.55"/>
|
||||
<rect x="22" y="21" width="1.8" height="7" rx="0.9" fill="white" opacity="0.55"/>
|
||||
<rect x="25" y="22.5" width="1.8" height="4" rx="0.9" fill="white" opacity="0.55"/>
|
||||
<!-- Image thumbnail placeholder -->
|
||||
<rect x="10" y="30" width="10" height="8" rx="2" fill="white" opacity="0.25"/>
|
||||
<path d="M12.5 35.5l2-2.5 2 1.8 1.5-1 2.5 3h-8z" fill="white" opacity="0.5"/>
|
||||
<!-- Translation line -->
|
||||
<rect x="22" y="32" width="7" height="2" rx="1" fill="white" opacity="0.35"/>
|
||||
<rect x="22" y="35.5" width="5" height="2" rx="1" fill="white" opacity="0.25"/>
|
||||
<!-- Enrichment sparkle burst -->
|
||||
<path d="M40 10l1.6 3.8 3.8 1.6-3.8 1.6L40 20.8l-1.6-3.8L34.6 15.4l3.8-1.6z" fill="#6ee7b7"/>
|
||||
<path d="M37 29l0.9 2.1 2.1 0.9-2.1 0.9L37 35l-0.9-2.1-2.1-0.9 2.1-0.9z" fill="#6ee7b7" opacity="0.5"/>
|
||||
<circle cx="43" cy="25" r="1.2" fill="#34d399" opacity="0.4"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 914 B After Width: | Height: | Size: 2.3 KiB |
@@ -1,13 +1,39 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="hl" x1="20" y1="14" x2="38" y2="34" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="hl-freq" x1="0" y1="0" x2="14" y2="8" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fbbf24"/>
|
||||
<stop offset="1" stop-color="#f59e0b"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="hl-n1" x1="0" y1="0" x2="10" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#60a5fa"/>
|
||||
<stop offset="1" stop-color="#3b82f6"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="hl-jlpt" x1="0" y1="0" x2="12" y2="8" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#a78bfa"/>
|
||||
<stop offset="1" stop-color="#7c3aed"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="2" y="17" width="10" height="14" rx="3" fill="#fbbf24" opacity="0.3"/>
|
||||
<rect x="14" y="17" width="7" height="14" rx="3" fill="#fbbf24" opacity="0.3"/>
|
||||
<rect x="23" y="13" width="13" height="22" rx="3.5" fill="url(#hl)"/>
|
||||
<rect x="38" y="17" width="8" height="14" rx="3" fill="#fbbf24" opacity="0.3"/>
|
||||
<path d="M28.2 4l1 2.4 2.4 1-2.4 1-1 2.4-1-2.4-2.4-1 2.4-1z" fill="#fbbf24" opacity="0.7"/>
|
||||
<!-- Viewport / video frame background -->
|
||||
<rect x="1" y="5" width="46" height="38" rx="4" fill="#1e293b" opacity="0.55"/>
|
||||
<rect x="1" y="5" width="46" height="38" rx="4" stroke="#334155" stroke-width="0.8" fill="none" opacity="0.5"/>
|
||||
<!-- Subtitle line 1 — tokens with frequency highlight -->
|
||||
<rect x="6" y="18" width="9" height="5" rx="1.5" fill="#cbd5e1" opacity="0.2"/>
|
||||
<!-- Frequency-highlighted token -->
|
||||
<rect x="17" y="17" width="14" height="7" rx="2" fill="url(#hl-freq)" opacity="0.2"/>
|
||||
<rect x="17.5" y="17.5" width="13" height="6" rx="1.8" fill="url(#hl-freq)"/>
|
||||
<rect x="20" y="19.5" width="8" height="2" rx="1" fill="white" opacity="0.85"/>
|
||||
<rect x="33" y="18" width="8" height="5" rx="1.5" fill="#cbd5e1" opacity="0.2"/>
|
||||
<!-- Subtitle line 2 — tokens with N+1 dot and JLPT badge -->
|
||||
<rect x="8" y="28" width="8" height="5" rx="1.5" fill="#cbd5e1" opacity="0.2"/>
|
||||
<!-- N+1 targeted token with dot -->
|
||||
<rect x="18" y="28" width="10" height="5" rx="1.5" fill="#60a5fa" opacity="0.15"/>
|
||||
<rect x="18.5" y="28.5" width="9" height="4" rx="1.2" fill="#cbd5e1" opacity="0.3"/>
|
||||
<circle cx="16.5" cy="30.5" r="2.2" fill="url(#hl-n1)"/>
|
||||
<text x="16.5" y="31.9" text-anchor="middle" font-size="2.6" font-weight="800" fill="white" font-family="sans-serif">+1</text>
|
||||
<!-- JLPT badge token -->
|
||||
<rect x="30" y="28" width="7" height="5" rx="1.5" fill="#cbd5e1" opacity="0.3"/>
|
||||
<rect x="37.5" y="27" width="9" height="7" rx="2" fill="url(#hl-jlpt)"/>
|
||||
<text x="42" y="31.8" text-anchor="middle" font-size="3.5" font-weight="700" fill="white" font-family="sans-serif">N2</text>
|
||||
<!-- Subtle sparkle -->
|
||||
<path d="M43 10l0.7 1.6 1.6 0.7-1.6 0.7L43 14.6l-0.7-1.6-1.6-0.7 1.6-0.7z" fill="#fbbf24" opacity="0.5"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 729 B After Width: | Height: | Size: 2.4 KiB |
@@ -1,21 +1,31 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="kb" x1="2" y1="10" x2="46" y2="42" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="kb-main" x1="2" y1="10" x2="46" y2="42" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#c084fc"/>
|
||||
<stop offset="1" stop-color="#7c3aed"/>
|
||||
</linearGradient>
|
||||
<filter id="kb-glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="2"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect x="2" y="12" width="44" height="30" rx="5" fill="url(#kb)" opacity="0.12"/>
|
||||
<rect x="2" y="12" width="44" height="30" rx="5" stroke="url(#kb)" stroke-width="1.5" fill="none"/>
|
||||
<rect x="6" y="16" width="8" height="6" rx="2" fill="url(#kb)"/>
|
||||
<rect x="16" y="16" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||
<rect x="26" y="16" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||
<rect x="36" y="16" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||
<rect x="6" y="24" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||
<rect x="16" y="24" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||
<rect x="26" y="24" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||
<rect x="36" y="24" width="8" height="6" rx="2" fill="url(#kb)"/>
|
||||
<rect x="6" y="32" width="8" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||
<rect x="16" y="32" width="16" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||
<rect x="34" y="32" width="10" height="6" rx="2" fill="url(#kb)" opacity="0.35"/>
|
||||
<!-- Keyboard body -->
|
||||
<rect x="2" y="14" width="44" height="28" rx="4.5" fill="url(#kb-main)" opacity="0.1"/>
|
||||
<rect x="2" y="14" width="44" height="28" rx="4.5" stroke="url(#kb-main)" stroke-width="1.4" fill="none"/>
|
||||
<!-- Row 1 -->
|
||||
<rect x="6" y="18" width="7" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||
<rect x="15" y="18" width="7" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||
<rect x="24" y="18" width="7" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||
<rect x="33" y="18" width="11" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||
<!-- Row 2 — active key with glow -->
|
||||
<rect x="6" y="25.5" width="7" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||
<!-- Active/pressed key glow -->
|
||||
<rect x="15" y="25.5" width="7" height="5.5" rx="1.8" fill="#c084fc" opacity="0.25" filter="url(#kb-glow)"/>
|
||||
<rect x="15" y="25.5" width="7" height="5.5" rx="1.8" fill="url(#kb-main)"/>
|
||||
<text x="18.5" y="30" text-anchor="middle" font-size="3.5" font-weight="700" fill="white" font-family="sans-serif">M</text>
|
||||
<rect x="24" y="25.5" width="7" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||
<rect x="33" y="25.5" width="11" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||
<!-- Row 3 — spacebar -->
|
||||
<rect x="6" y="33" width="7" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||
<rect x="15" y="33" width="16" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.25"/>
|
||||
<rect x="33" y="33" width="11" height="5.5" rx="1.8" fill="url(#kb-main)" opacity="0.3"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 141 KiB |
BIN
docs/public/assets/kiku-integration.mkv
Normal file
BIN
docs/public/assets/kiku-integration.mp4
Normal file
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 13 MiB After Width: | Height: | Size: 23 MiB |
BIN
docs/public/assets/minecard.webp
Normal file
|
After Width: | Height: | Size: 21 MiB |
@@ -1,16 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="sd" x1="4" y1="4" x2="44" y2="44" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="sd-main" x1="4" y1="4" x2="44" y2="44" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#22d3ee"/>
|
||||
<stop offset="1" stop-color="#0891b2"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="sd-sync" x1="30" y1="28" x2="46" y2="44" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#34d399"/>
|
||||
<stop offset="1" stop-color="#059669"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="8" y="4" width="24" height="32" rx="3" fill="url(#sd)" opacity="0.15"/>
|
||||
<rect x="8" y="4" width="24" height="32" rx="3" stroke="url(#sd)" stroke-width="1.5" fill="none"/>
|
||||
<rect x="13" y="12" width="14" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.5"/>
|
||||
<rect x="13" y="18" width="10" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.35"/>
|
||||
<rect x="13" y="24" width="12" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.35"/>
|
||||
<line x1="38" y1="16" x2="38" y2="32" stroke="url(#sd)" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<path d="M33 28l5 5 5-5" stroke="url(#sd)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
<line x1="33" y1="40" x2="43" y2="40" stroke="url(#sd)" stroke-width="2" stroke-linecap="round" opacity="0.5"/>
|
||||
<!-- Subtitle file -->
|
||||
<rect x="4" y="3" width="26" height="34" rx="3.5" fill="url(#sd-main)" opacity="0.12"/>
|
||||
<rect x="4" y="3" width="26" height="34" rx="3.5" stroke="url(#sd-main)" stroke-width="1.4" fill="none"/>
|
||||
<!-- SRT-style timing line -->
|
||||
<rect x="8.5" y="10" width="10" height="2" rx="1" fill="#22d3ee" opacity="0.35"/>
|
||||
<rect x="20" y="10" width="3" height="2" rx="1" fill="#22d3ee" opacity="0.25"/>
|
||||
<!-- Subtitle text lines -->
|
||||
<rect x="8.5" y="15" width="17" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.6"/>
|
||||
<rect x="8.5" y="20" width="12" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.4"/>
|
||||
<!-- Divider -->
|
||||
<line x1="8.5" y1="25.5" x2="26" y2="25.5" stroke="#22d3ee" stroke-width="0.6" opacity="0.2"/>
|
||||
<!-- Second block timing -->
|
||||
<rect x="8.5" y="28" width="10" height="2" rx="1" fill="#22d3ee" opacity="0.35"/>
|
||||
<!-- Second block text -->
|
||||
<rect x="8.5" y="32.5" width="14" height="2.5" rx="1.25" fill="#22d3ee" opacity="0.4"/>
|
||||
<!-- Download arrow -->
|
||||
<line x1="38" y1="6" x2="38" y2="20" stroke="url(#sd-main)" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<path d="M33 16.5l5 5.5 5-5.5" stroke="url(#sd-main)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
<!-- Sync arrows (circular) -->
|
||||
<path d="M35 35a6 6 0 0 1 8.5-1.5" stroke="url(#sd-sync)" stroke-width="1.8" stroke-linecap="round" fill="none"/>
|
||||
<path d="M44.5 35.5l-1-2.8-2.8 1" stroke="url(#sd-sync)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
<path d="M43.5 41a6 6 0 0 1-8.5 1.5" stroke="url(#sd-sync)" stroke-width="1.8" stroke-linecap="round" fill="none"/>
|
||||
<path d="M34 40.5l1 2.8 2.8-1" stroke="url(#sd-sync)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -1,19 +1,46 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="th" x1="4" y1="6" x2="44" y2="42" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="th-main" x1="2" y1="6" x2="22" y2="42" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#f97316"/>
|
||||
<stop offset="1" stop-color="#c2410c"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="th-browser" x1="28" y1="6" x2="46" y2="42" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fb923c"/>
|
||||
<stop offset="1" stop-color="#ea580c"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="4" y="6" width="30" height="36" rx="4" fill="url(#th)" opacity="0.12"/>
|
||||
<rect x="4" y="6" width="30" height="36" rx="4" stroke="url(#th)" stroke-width="1.5" fill="none"/>
|
||||
<rect x="9" y="14" width="14" height="2.5" rx="1.25" fill="#f97316" opacity="0.6"/>
|
||||
<rect x="9" y="20" width="18" height="2.5" rx="1.25" fill="#f97316" opacity="0.4"/>
|
||||
<rect x="9" y="26" width="12" height="2.5" rx="1.25" fill="#f97316" opacity="0.4"/>
|
||||
<rect x="9" y="32" width="16" height="2.5" rx="1.25" fill="#f97316" opacity="0.4"/>
|
||||
<circle cx="40" cy="18" r="3.5" fill="url(#th)" opacity="0.8"/>
|
||||
<circle cx="40" cy="30" r="3.5" fill="url(#th)" opacity="0.8"/>
|
||||
<line x1="36" y1="18" x2="34" y2="18" stroke="url(#th)" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="36" y1="30" x2="34" y2="30" stroke="url(#th)" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="40" y1="21.5" x2="40" y2="26.5" stroke="url(#th)" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
|
||||
<!-- Source panel (subtitle/text source) -->
|
||||
<rect x="2" y="8" width="18" height="32" rx="3" fill="url(#th-main)" opacity="0.12"/>
|
||||
<rect x="2" y="8" width="18" height="32" rx="3" stroke="url(#th-main)" stroke-width="1.3" fill="none"/>
|
||||
<!-- Subtitle text lines streaming out -->
|
||||
<rect x="5" y="14" width="12" height="2" rx="1" fill="#f97316" opacity="0.6"/>
|
||||
<rect x="5" y="19" width="10" height="2" rx="1" fill="#f97316" opacity="0.5"/>
|
||||
<rect x="5" y="24" width="11" height="2" rx="1" fill="#f97316" opacity="0.4"/>
|
||||
<rect x="5" y="29" width="9" height="2" rx="1" fill="#f97316" opacity="0.35"/>
|
||||
<rect x="5" y="34" width="12" height="2" rx="1" fill="#f97316" opacity="0.3"/>
|
||||
<!-- WebSocket stream particles -->
|
||||
<circle cx="23" cy="18" r="1.2" fill="#fb923c" opacity="0.7"/>
|
||||
<circle cx="25" cy="24" r="1" fill="#fb923c" opacity="0.5"/>
|
||||
<circle cx="23.5" cy="30" r="1.1" fill="#fb923c" opacity="0.4"/>
|
||||
<!-- Connection line (wavy/flowing) -->
|
||||
<path d="M20 15c2-1 4 2 6 1s3-3 5-2" stroke="#fb923c" stroke-width="1" stroke-linecap="round" fill="none" opacity="0.3"/>
|
||||
<path d="M20 24c2.5 0 3 2 5 1.5s3-2.5 5.5-1.5" stroke="#fb923c" stroke-width="1" stroke-linecap="round" fill="none" opacity="0.25"/>
|
||||
<path d="M20 33c2-1 3.5 1.5 5.5 0.5s3-2 5-1" stroke="#fb923c" stroke-width="1" stroke-linecap="round" fill="none" opacity="0.2"/>
|
||||
<!-- Browser window (destination) -->
|
||||
<rect x="28" y="8" width="18" height="32" rx="3" fill="url(#th-browser)" opacity="0.12"/>
|
||||
<rect x="28" y="8" width="18" height="32" rx="3" stroke="url(#th-browser)" stroke-width="1.3" fill="none"/>
|
||||
<!-- Browser chrome dots -->
|
||||
<circle cx="32" cy="12" r="1.2" fill="#f97316" opacity="0.45"/>
|
||||
<circle cx="35.5" cy="12" r="1.2" fill="#f97316" opacity="0.35"/>
|
||||
<circle cx="39" cy="12" r="1.2" fill="#f97316" opacity="0.25"/>
|
||||
<!-- Browser address bar -->
|
||||
<rect x="31" y="15.5" width="12" height="2.5" rx="1.25" fill="#f97316" opacity="0.15"/>
|
||||
<!-- Received text lines in browser -->
|
||||
<rect x="31" y="21" width="11" height="2" rx="1" fill="#fb923c" opacity="0.55"/>
|
||||
<rect x="31" y="25.5" width="9" height="2" rx="1" fill="#fb923c" opacity="0.45"/>
|
||||
<rect x="31" y="30" width="10" height="2" rx="1" fill="#fb923c" opacity="0.35"/>
|
||||
<rect x="31" y="34.5" width="8" height="2" rx="1" fill="#fb923c" opacity="0.25"/>
|
||||
<!-- WS label -->
|
||||
<rect x="21" y="5" width="8" height="5.5" rx="2.5" fill="#c2410c"/>
|
||||
<text x="25" y="9.2" text-anchor="middle" font-size="3.2" font-weight="800" fill="white" font-family="sans-serif">WS</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 3.0 KiB |
@@ -1,16 +1,34 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="tk" x1="0" y1="14" x2="48" y2="34" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#22d3ee"/>
|
||||
<stop offset="1" stop-color="#0891b2"/>
|
||||
<linearGradient id="tk-bar" x1="0" y1="40" x2="0" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0891b2"/>
|
||||
<stop offset="1" stop-color="#22d3ee"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="tk-glow" x1="4" y1="40" x2="44" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#22d3ee" stop-opacity="0.25"/>
|
||||
<stop offset="1" stop-color="#06b6d4" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="2" y="12" width="12" height="24" rx="3.5" fill="url(#tk)"/>
|
||||
<rect x="18" y="12" width="12" height="24" rx="3.5" fill="url(#tk)"/>
|
||||
<rect x="34" y="12" width="12" height="24" rx="3.5" fill="url(#tk)"/>
|
||||
<line x1="15.5" y1="10" x2="15.5" y2="38" stroke="#22d3ee" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="3 3" opacity="0.45"/>
|
||||
<line x1="32.5" y1="10" x2="32.5" y2="38" stroke="#22d3ee" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="3 3" opacity="0.45"/>
|
||||
<rect x="5" y="22" width="6" height="2.5" rx="1.25" fill="white" opacity="0.7"/>
|
||||
<rect x="21" y="22" width="6" height="2.5" rx="1.25" fill="white" opacity="0.7"/>
|
||||
<rect x="37" y="22" width="6" height="2.5" rx="1.25" fill="white" opacity="0.7"/>
|
||||
<!-- Subtle grid lines -->
|
||||
<line x1="4" y1="14" x2="44" y2="14" stroke="#22d3ee" stroke-width="0.5" opacity="0.12"/>
|
||||
<line x1="4" y1="22" x2="44" y2="22" stroke="#22d3ee" stroke-width="0.5" opacity="0.12"/>
|
||||
<line x1="4" y1="30" x2="44" y2="30" stroke="#22d3ee" stroke-width="0.5" opacity="0.12"/>
|
||||
<!-- Base line -->
|
||||
<line x1="4" y1="40" x2="44" y2="40" stroke="#0891b2" stroke-width="1" opacity="0.3"/>
|
||||
<!-- Activity bars (daily rollups) -->
|
||||
<rect x="5" y="30" width="4" height="10" rx="1.5" fill="url(#tk-bar)" opacity="0.4"/>
|
||||
<rect x="11" y="24" width="4" height="16" rx="1.5" fill="url(#tk-bar)" opacity="0.55"/>
|
||||
<rect x="17" y="28" width="4" height="12" rx="1.5" fill="url(#tk-bar)" opacity="0.5"/>
|
||||
<rect x="23" y="18" width="4" height="22" rx="1.5" fill="url(#tk-bar)" opacity="0.7"/>
|
||||
<rect x="29" y="22" width="4" height="18" rx="1.5" fill="url(#tk-bar)" opacity="0.6"/>
|
||||
<rect x="35" y="14" width="4" height="26" rx="1.5" fill="url(#tk-bar)"/>
|
||||
<rect x="41" y="20" width="4" height="20" rx="1.5" fill="url(#tk-bar)" opacity="0.65"/>
|
||||
<!-- Trend line -->
|
||||
<polyline points="7,28 13,22 19,25.5 25,16 31,20 37,12 43,18" stroke="#67e8f9" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="0.7"/>
|
||||
<!-- Trend dot on peak -->
|
||||
<circle cx="37" cy="12" r="2.2" fill="#22d3ee" opacity="0.6"/>
|
||||
<circle cx="37" cy="12" r="1" fill="white" opacity="0.9"/>
|
||||
<!-- Mini counter badge -->
|
||||
<rect x="33" y="4" width="12" height="7" rx="3.5" fill="#0891b2"/>
|
||||
<text x="39" y="9" text-anchor="middle" font-size="4" font-weight="700" fill="white" font-family="sans-serif">42d</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -5,26 +5,18 @@
|
||||
* Copy to $XDG_CONFIG_HOME/SubMiner/config.jsonc (or ~/.config/SubMiner/config.jsonc) and edit as needed.
|
||||
*/
|
||||
{
|
||||
|
||||
// ==========================================
|
||||
// Overlay Auto-Start
|
||||
// When overlay connects to mpv, automatically show overlay and hide mpv subtitles.
|
||||
// ==========================================
|
||||
"auto_start_overlay": false, // When overlay connects to mpv, automatically show overlay and hide mpv subtitles. Values: true | false
|
||||
|
||||
// ==========================================
|
||||
// Visible Overlay Subtitle Binding
|
||||
// Control whether visible overlay toggles also toggle MPV subtitle visibility.
|
||||
// When enabled, visible overlay hides MPV subtitles; when disabled, MPV subtitles are left unchanged.
|
||||
// ==========================================
|
||||
"bind_visible_overlay_to_mpv_sub_visibility": true, // Link visible overlay toggles to MPV subtitle visibility (primary and secondary). Values: true | false
|
||||
|
||||
// ==========================================
|
||||
// Texthooker Server
|
||||
// Control whether browser opens automatically for texthooker.
|
||||
// ==========================================
|
||||
"texthooker": {
|
||||
"openBrowser": true // Open browser setting. Values: true | false
|
||||
"openBrowser": true, // Open browser setting. Values: true | false
|
||||
}, // Control whether browser opens automatically for texthooker.
|
||||
|
||||
// ==========================================
|
||||
@@ -34,7 +26,7 @@
|
||||
// ==========================================
|
||||
"websocket": {
|
||||
"enabled": "auto", // Built-in subtitle websocket server mode. Values: auto | true | false
|
||||
"port": 6677 // Built-in subtitle websocket server port.
|
||||
"port": 6677, // Built-in subtitle websocket server port.
|
||||
}, // Built-in WebSocket server broadcasts subtitle text to connected clients.
|
||||
|
||||
// ==========================================
|
||||
@@ -43,7 +35,7 @@
|
||||
// Set to debug for full runtime diagnostics.
|
||||
// ==========================================
|
||||
"logging": {
|
||||
"level": "info" // Minimum log level for runtime logging. Values: debug | info | warn | error
|
||||
"level": "info", // Minimum log level for runtime logging. Values: debug | info | warn | error
|
||||
}, // Controls logging verbosity.
|
||||
|
||||
// ==========================================
|
||||
@@ -53,7 +45,6 @@
|
||||
// ==========================================
|
||||
"shortcuts": {
|
||||
"toggleVisibleOverlayGlobal": "Alt+Shift+O", // Toggle visible overlay global setting.
|
||||
"toggleInvisibleOverlayGlobal": "Alt+Shift+I", // Toggle invisible overlay global setting.
|
||||
"copySubtitle": "CommandOrControl+C", // Copy subtitle setting.
|
||||
"copySubtitleMultiple": "CommandOrControl+Shift+C", // Copy subtitle multiple setting.
|
||||
"updateLastCardFromClipboard": "CommandOrControl+V", // Update last card from clipboard setting.
|
||||
@@ -65,19 +56,9 @@
|
||||
"toggleSecondarySub": "CommandOrControl+Shift+V", // Toggle secondary sub setting.
|
||||
"markAudioCard": "CommandOrControl+Shift+A", // Mark audio card setting.
|
||||
"openRuntimeOptions": "CommandOrControl+Shift+O", // Open runtime options setting.
|
||||
"openJimaku": "Ctrl+Shift+J" // Open jimaku setting.
|
||||
"openJimaku": "Ctrl+Shift+J", // Open jimaku setting.
|
||||
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
|
||||
|
||||
// ==========================================
|
||||
// Invisible Overlay
|
||||
// Startup behavior for the invisible interactive subtitle mining layer.
|
||||
// Invisible subtitle position edit mode: Ctrl/Cmd+Shift+P to toggle, arrow keys to move, Enter or Ctrl/Cmd+S to save, Esc to cancel.
|
||||
// This edit-mode shortcut is fixed and is not currently configurable.
|
||||
// ==========================================
|
||||
"invisibleOverlay": {
|
||||
"startupVisibility": "platform-default" // Startup visibility setting.
|
||||
}, // Startup behavior for the invisible interactive subtitle mining layer.
|
||||
|
||||
// ==========================================
|
||||
// Keybindings (MPV Commands)
|
||||
// Extra keybindings that are merged with built-in defaults.
|
||||
@@ -95,7 +76,7 @@
|
||||
"secondarySub": {
|
||||
"secondarySubLanguages": [], // Secondary sub languages setting.
|
||||
"autoLoadSecondarySub": false, // Auto load secondary sub setting. Values: true | false
|
||||
"defaultMode": "hover" // Default mode setting.
|
||||
"defaultMode": "hover", // Default mode setting.
|
||||
}, // Dual subtitle track options.
|
||||
|
||||
// ==========================================
|
||||
@@ -106,7 +87,7 @@
|
||||
"defaultMode": "auto", // Subsync default mode. Values: auto | manual
|
||||
"alass_path": "", // Alass path setting.
|
||||
"ffsubsync_path": "", // Ffsubsync path setting.
|
||||
"ffmpeg_path": "" // Ffmpeg path setting.
|
||||
"ffmpeg_path": "", // Ffmpeg path setting.
|
||||
}, // Subsync engine and executable paths.
|
||||
|
||||
// ==========================================
|
||||
@@ -114,7 +95,7 @@
|
||||
// Initial vertical subtitle position from the bottom.
|
||||
// ==========================================
|
||||
"subtitlePosition": {
|
||||
"yPercent": 10 // Y percent setting.
|
||||
"yPercent": 10, // Y percent setting.
|
||||
}, // Initial vertical subtitle position from the bottom.
|
||||
|
||||
// ==========================================
|
||||
@@ -125,13 +106,22 @@
|
||||
"subtitleStyle": {
|
||||
"enableJlpt": false, // Enable JLPT vocabulary level underlines. When disabled, JLPT tagging lookup and underlines are skipped. Values: true | false
|
||||
"preserveLineBreaks": false, // Preserve line breaks in visible overlay subtitle rendering. When false, line breaks are flattened to spaces for a single-line flow. Values: true | false
|
||||
"hoverTokenColor": "#c6a0f6", // Hex color used for hovered subtitle token highlight in mpv.
|
||||
"fontFamily": "M PLUS 1, Noto Sans CJK JP Regular, Noto Sans CJK JP, Hiragino Sans, Hiragino Kaku Gothic ProN, Yu Gothic, Arial Unicode MS, Arial, sans-serif", // Font family setting.
|
||||
"autoPauseVideoOnHover": true, // Automatically pause mpv playback while hovering subtitle text, then resume on leave. Values: true | false
|
||||
"hoverTokenColor": "#f4dbd6", // Hex color used for hovered subtitle token highlight in mpv.
|
||||
"hoverTokenBackgroundColor": "rgba(54, 58, 79, 0.84)", // CSS color used for hovered subtitle token background highlight in mpv.
|
||||
"fontFamily": "M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP", // Font family setting.
|
||||
"fontSize": 35, // Font size setting.
|
||||
"fontColor": "#cad3f5", // Font color setting.
|
||||
"fontWeight": "normal", // Font weight setting.
|
||||
"fontWeight": "600", // Font weight setting.
|
||||
"lineHeight": 1.35, // Line height setting.
|
||||
"letterSpacing": "-0.01em", // Letter spacing setting.
|
||||
"wordSpacing": 0, // Word spacing setting.
|
||||
"fontKerning": "normal", // Font kerning setting.
|
||||
"textRendering": "geometricPrecision", // Text rendering setting.
|
||||
"textShadow": "0 3px 10px rgba(0,0,0,0.69)", // Text shadow setting.
|
||||
"fontStyle": "normal", // Font style setting.
|
||||
"backgroundColor": "rgb(30, 32, 48, 0.88)", // Background color setting.
|
||||
"backdropFilter": "blur(6px)", // Backdrop filter setting.
|
||||
"nPlusOneColor": "#c6a0f6", // N plus one color setting.
|
||||
"knownWordColor": "#a6da95", // Known word color setting.
|
||||
"jlptColors": {
|
||||
@@ -139,30 +129,32 @@
|
||||
"N2": "#f5a97f", // N2 setting.
|
||||
"N3": "#f9e2af", // N3 setting.
|
||||
"N4": "#a6e3a1", // N4 setting.
|
||||
"N5": "#8aadf4" // N5 setting.
|
||||
"N5": "#8aadf4", // N5 setting.
|
||||
}, // Jlpt colors setting.
|
||||
"frequencyDictionary": {
|
||||
"enabled": false, // Enable frequency-dictionary-based highlighting based on token rank. Values: true | false
|
||||
"sourcePath": "", // Optional absolute path to a frequency dictionary directory. If empty, built-in discovery search paths are used.
|
||||
"sourcePath": "", // Optional absolute path to a frequency dictionary directory. If empty, SubMiner searches installed/default frequency-dictionary locations.
|
||||
"topX": 1000, // Only color tokens with frequency rank <= topX (default: 1000).
|
||||
"mode": "single", // single: use one color for all matching tokens. banded: use color ramp by frequency band. Values: single | banded
|
||||
"matchMode": "headword", // Frequency lookup text selection mode. Values: headword | surface
|
||||
"singleColor": "#f5a97f", // Color used when frequencyDictionary.mode is `single`.
|
||||
"bandedColors": [
|
||||
"#ed8796",
|
||||
"#f5a97f",
|
||||
"#f9e2af",
|
||||
"#a6e3a1",
|
||||
"#8aadf4"
|
||||
] // Five colors used for rank bands when mode is `banded` (from most common to least within topX).
|
||||
"bandedColors": ["#ed8796", "#f5a97f", "#f9e2af", "#a6e3a1", "#8aadf4"], // Five colors used for rank bands when mode is `banded` (from most common to least within topX).
|
||||
}, // Frequency dictionary setting.
|
||||
"secondary": {
|
||||
"fontFamily": "Inter, Noto Sans, Helvetica Neue, sans-serif", // Font family setting.
|
||||
"fontSize": 24, // Font size setting.
|
||||
"fontColor": "#ffffff", // Font color setting.
|
||||
"fontColor": "#cad3f5", // Font color setting.
|
||||
"lineHeight": 1.35, // Line height setting.
|
||||
"letterSpacing": "-0.01em", // Letter spacing setting.
|
||||
"wordSpacing": 0, // Word spacing setting.
|
||||
"fontKerning": "normal", // Font kerning setting.
|
||||
"textRendering": "geometricPrecision", // Text rendering setting.
|
||||
"textShadow": "0 3px 10px rgba(0,0,0,0.69)", // Text shadow setting.
|
||||
"backgroundColor": "transparent", // Background color setting.
|
||||
"backdropFilter": "blur(6px)", // Backdrop filter setting.
|
||||
"fontWeight": "normal", // Font weight setting.
|
||||
"fontStyle": "normal", // Font style setting.
|
||||
"fontFamily": "M PLUS 1, Noto Sans CJK JP Regular, Noto Sans CJK JP, Hiragino Sans, Hiragino Kaku Gothic ProN, Yu Gothic, Arial Unicode MS, Arial, sans-serif" // Font family setting.
|
||||
} // Secondary setting.
|
||||
}, // Secondary setting.
|
||||
}, // Primary and secondary subtitle styling.
|
||||
|
||||
// ==========================================
|
||||
@@ -175,15 +167,19 @@
|
||||
"enabled": false, // Enable AnkiConnect integration. Values: true | false
|
||||
"url": "http://127.0.0.1:8765", // Url setting.
|
||||
"pollingRate": 3000, // Polling interval in milliseconds.
|
||||
"tags": [
|
||||
"SubMiner"
|
||||
], // Tags to add to cards mined or updated by SubMiner. Provide an empty array to disable automatic tagging.
|
||||
"proxy": {
|
||||
"enabled": true, // Enable local AnkiConnect-compatible proxy for push-based auto-enrichment. Values: true | false
|
||||
"host": "127.0.0.1", // Bind host for local AnkiConnect proxy.
|
||||
"port": 8766, // Bind port for local AnkiConnect proxy.
|
||||
"upstreamUrl": "http://127.0.0.1:8765", // Upstream AnkiConnect URL proxied by local AnkiConnect proxy.
|
||||
}, // Proxy setting.
|
||||
"tags": ["SubMiner"], // Tags to add to cards mined or updated by SubMiner. Provide an empty array to disable automatic tagging.
|
||||
"fields": {
|
||||
"audio": "ExpressionAudio", // Audio setting.
|
||||
"image": "Picture", // Image setting.
|
||||
"sentence": "Sentence", // Sentence setting.
|
||||
"miscInfo": "MiscInfo", // Misc info setting.
|
||||
"translation": "SelectionText" // Translation setting.
|
||||
"translation": "SelectionText", // Translation setting.
|
||||
}, // Fields setting.
|
||||
"ai": {
|
||||
"enabled": false, // Enabled setting. Values: true | false
|
||||
@@ -192,7 +188,7 @@
|
||||
"model": "openai/gpt-4o-mini", // Model setting.
|
||||
"baseUrl": "https://openrouter.ai/api", // Base url setting.
|
||||
"targetLanguage": "English", // Target language setting.
|
||||
"systemPrompt": "You are a translation engine. Return only the translated text with no explanations." // System prompt setting.
|
||||
"systemPrompt": "You are a translation engine. Return only the translated text with no explanations.", // System prompt setting.
|
||||
}, // Ai setting.
|
||||
"media": {
|
||||
"generateAudio": true, // Generate audio setting. Values: true | false
|
||||
@@ -205,7 +201,7 @@
|
||||
"animatedCrf": 35, // Animated crf setting.
|
||||
"audioPadding": 0.5, // Audio padding setting.
|
||||
"fallbackDuration": 3, // Fallback duration setting.
|
||||
"maxMediaDuration": 30 // Max media duration setting.
|
||||
"maxMediaDuration": 30, // Max media duration setting.
|
||||
}, // Media setting.
|
||||
"behavior": {
|
||||
"overwriteAudio": true, // Overwrite audio setting. Values: true | false
|
||||
@@ -213,7 +209,7 @@
|
||||
"mediaInsertMode": "append", // Media insert mode setting.
|
||||
"highlightWord": true, // Highlight word setting. Values: true | false
|
||||
"notificationType": "osd", // Notification type setting.
|
||||
"autoUpdateNewCards": true // Automatically update newly added cards. Values: true | false
|
||||
"autoUpdateNewCards": true, // Automatically update newly added cards. Values: true | false
|
||||
}, // Behavior setting.
|
||||
"nPlusOne": {
|
||||
"highlightEnabled": false, // Enable fast local highlighting for words already known in Anki. Values: true | false
|
||||
@@ -222,20 +218,20 @@
|
||||
"decks": [], // Decks used for N+1 known-word cache scope. Supports one or more deck names.
|
||||
"minSentenceWords": 3, // Minimum sentence word count required for N+1 targeting (default: 3).
|
||||
"nPlusOne": "#c6a0f6", // Color used for the single N+1 target token highlight.
|
||||
"knownWord": "#a6da95" // Color used for legacy known-word highlights.
|
||||
"knownWord": "#a6da95", // Color used for legacy known-word highlights.
|
||||
}, // N plus one setting.
|
||||
"metadata": {
|
||||
"pattern": "[SubMiner] %f (%t)" // Pattern setting.
|
||||
"pattern": "[SubMiner] %f (%t)", // Pattern setting.
|
||||
}, // Metadata setting.
|
||||
"isLapis": {
|
||||
"enabled": false, // Enabled setting. Values: true | false
|
||||
"sentenceCardModel": "Japanese sentences" // Sentence card model setting.
|
||||
"sentenceCardModel": "Japanese sentences", // Sentence card model setting.
|
||||
}, // Is lapis setting.
|
||||
"isKiku": {
|
||||
"enabled": false, // Enabled setting. Values: true | false
|
||||
"fieldGrouping": "disabled", // Kiku duplicate-card field grouping mode. Values: auto | manual | disabled
|
||||
"deleteDuplicateInAuto": true // Delete duplicate in auto setting. Values: true | false
|
||||
} // Is kiku setting.
|
||||
"deleteDuplicateInAuto": true, // Delete duplicate in auto setting. Values: true | false
|
||||
}, // Is kiku setting.
|
||||
}, // Automatic Anki updates and media generation options.
|
||||
|
||||
// ==========================================
|
||||
@@ -245,7 +241,7 @@
|
||||
"jimaku": {
|
||||
"apiBaseUrl": "https://jimaku.cc", // Api base url setting.
|
||||
"languagePreference": "ja", // Preferred language used in Jimaku search. Values: ja | en | none
|
||||
"maxEntryResults": 10 // Maximum Jimaku search results returned.
|
||||
"maxEntryResults": 10, // Maximum Jimaku search results returned.
|
||||
}, // Jimaku API configuration and defaults.
|
||||
|
||||
// ==========================================
|
||||
@@ -256,10 +252,7 @@
|
||||
"mode": "automatic", // YouTube subtitle generation mode for the launcher script. Values: automatic | preprocess | off
|
||||
"whisperBin": "", // Path to whisper.cpp CLI used as fallback transcription engine.
|
||||
"whisperModel": "", // Path to whisper model used for fallback transcription.
|
||||
"primarySubLanguages": [
|
||||
"ja",
|
||||
"jpn"
|
||||
] // Comma-separated primary subtitle language priority used by the launcher.
|
||||
"primarySubLanguages": ["ja", "jpn"], // Comma-separated primary subtitle language priority used by the launcher.
|
||||
}, // Defaults for subminer YouTube subtitle extraction/transcription mode.
|
||||
|
||||
// ==========================================
|
||||
@@ -268,7 +261,7 @@
|
||||
// ==========================================
|
||||
"anilist": {
|
||||
"enabled": false, // Enable AniList post-watch progress updates. Values: true | false
|
||||
"accessToken": "" // Optional explicit AniList access token override; leave empty to use locally stored token from setup.
|
||||
"accessToken": "", // Optional explicit AniList access token override; leave empty to use locally stored token from setup.
|
||||
}, // Anilist API credentials and update behavior.
|
||||
|
||||
// ==========================================
|
||||
@@ -292,16 +285,8 @@
|
||||
"pullPictures": false, // Enable Jellyfin poster/icon fetching for launcher menus. Values: true | false
|
||||
"iconCacheDir": "/tmp/subminer-jellyfin-icons", // Directory used by launcher for cached Jellyfin poster icons.
|
||||
"directPlayPreferred": true, // Try direct play before server-managed transcoding when possible. Values: true | false
|
||||
"directPlayContainers": [
|
||||
"mkv",
|
||||
"mp4",
|
||||
"webm",
|
||||
"mov",
|
||||
"flac",
|
||||
"mp3",
|
||||
"aac"
|
||||
], // Container allowlist for direct play decisions.
|
||||
"transcodeVideoCodec": "h264" // Preferred transcode video codec when direct play is unavailable.
|
||||
"directPlayContainers": ["mkv", "mp4", "webm", "mov", "flac", "mp3", "aac"], // Container allowlist for direct play decisions.
|
||||
"transcodeVideoCodec": "h264", // Preferred transcode video codec when direct play is unavailable.
|
||||
}, // Optional Jellyfin integration for auth, browsing, and playback launch.
|
||||
|
||||
// ==========================================
|
||||
@@ -312,7 +297,7 @@
|
||||
"discordPresence": {
|
||||
"enabled": false, // Enable optional Discord Rich Presence updates. Values: true | false
|
||||
"updateIntervalMs": 3000, // Minimum interval between presence payload updates.
|
||||
"debounceMs": 750 // Debounce delay used to collapse bursty presence updates.
|
||||
"debounceMs": 750, // Debounce delay used to collapse bursty presence updates.
|
||||
}, // Optional Discord Rich Presence activity card updates for current playback/study session.
|
||||
|
||||
// ==========================================
|
||||
@@ -334,7 +319,7 @@
|
||||
"telemetryDays": 30, // Telemetry retention window in days.
|
||||
"dailyRollupsDays": 365, // Daily rollup retention window in days.
|
||||
"monthlyRollupsDays": 1825, // Monthly rollup retention window in days.
|
||||
"vacuumIntervalDays": 7 // Minimum days between VACUUM runs.
|
||||
} // Retention setting.
|
||||
} // Enable/disable immersion tracking.
|
||||
"vacuumIntervalDays": 7, // Minimum days between VACUUM runs.
|
||||
}, // Retention setting.
|
||||
}, // Enable/disable immersion tracking.
|
||||
}
|
||||
|
||||
@@ -6,11 +6,10 @@ All shortcuts are configurable in `config.jsonc` under `shortcuts` and `keybindi
|
||||
|
||||
These work system-wide regardless of which window has focus.
|
||||
|
||||
| Shortcut | Action | Configurable |
|
||||
| ------------- | ------------------------ | ---------------------------------------- |
|
||||
| `Alt+Shift+O` | Toggle visible overlay | `shortcuts.toggleVisibleOverlayGlobal` |
|
||||
| `Alt+Shift+I` | Toggle invisible overlay | `shortcuts.toggleInvisibleOverlayGlobal` |
|
||||
| `Alt+Shift+Y` | Open Yomitan settings | Fixed (not configurable) |
|
||||
| Shortcut | Action | Configurable |
|
||||
| ------------- | ---------------------- | -------------------------------------- |
|
||||
| `Alt+Shift+O` | Toggle visible overlay | `shortcuts.toggleVisibleOverlayGlobal` |
|
||||
| `Alt+Shift+Y` | Open Yomitan settings | Fixed (not configurable) |
|
||||
|
||||
::: tip
|
||||
Global shortcuts are registered with the OS. If they conflict with another application, update them in `shortcuts` config and restart SubMiner.
|
||||
@@ -39,6 +38,8 @@ These control playback and subtitle display. They require overlay window focus.
|
||||
| Shortcut | Action |
|
||||
| -------------------- | -------------------------------------------------- |
|
||||
| `Space` | Toggle mpv pause |
|
||||
| `J` | Cycle primary subtitle track |
|
||||
| `Shift+J` | Cycle secondary subtitle track |
|
||||
| `ArrowRight` | Seek forward 5 seconds |
|
||||
| `ArrowLeft` | Seek backward 5 seconds |
|
||||
| `ArrowUp` | Seek forward 60 seconds |
|
||||
@@ -55,6 +56,8 @@ These control playback and subtitle display. They require overlay window focus.
|
||||
|
||||
These keybindings can be overridden or disabled via the `keybindings` config array.
|
||||
|
||||
Mouse-hover playback behavior is configured separately from shortcuts: `subtitleStyle.autoPauseVideoOnHover` defaults to `true` (pause on subtitle hover, resume on leave).
|
||||
|
||||
## Subtitle & Feature Shortcuts
|
||||
|
||||
| Shortcut | Action | Config key |
|
||||
@@ -64,34 +67,19 @@ These keybindings can be overridden or disabled via the `keybindings` config arr
|
||||
| `Ctrl+Shift+J` | Open Jimaku subtitle search modal | `shortcuts.openJimaku` |
|
||||
| `Ctrl+Alt+S` | Open subtitle sync (subsync) modal | `shortcuts.triggerSubsync` |
|
||||
|
||||
## Invisible Subtitle Position Edit Mode
|
||||
|
||||
Enter edit mode to fine-tune invisible overlay alignment with mpv's native subtitles.
|
||||
|
||||
| Shortcut | Action |
|
||||
| --------------------- | -------------------------------- |
|
||||
| `Ctrl/Cmd+Shift+P` | Toggle position edit mode |
|
||||
| `ArrowKeys` or `hjkl` | Nudge position by 1 px |
|
||||
| `Shift+Arrow` | Nudge position by 4 px |
|
||||
| `Enter` or `Ctrl+S` | Save position and exit edit mode |
|
||||
| `Esc` | Cancel and discard changes |
|
||||
|
||||
## MPV Plugin Chords
|
||||
|
||||
When the mpv plugin is installed, all commands use a `y` chord prefix — press `y`, then the second key within 1 second.
|
||||
|
||||
| Chord | Action |
|
||||
| ----- | --------------------------------------- |
|
||||
| `y-y` | Open SubMiner menu (OSD) |
|
||||
| `y-s` | Start overlay |
|
||||
| `y-S` | Stop overlay |
|
||||
| `y-t` | Toggle visible overlay |
|
||||
| `y-i` | Toggle invisible overlay |
|
||||
| `y-I` | Show invisible overlay |
|
||||
| `y-u` | Hide invisible overlay |
|
||||
| `y-o` | Open Yomitan settings |
|
||||
| `y-r` | Restart overlay |
|
||||
| `y-c` | Check overlay status |
|
||||
| Chord | Action |
|
||||
| ----- | ------------------------ |
|
||||
| `y-y` | Open SubMiner menu (OSD) |
|
||||
| `y-s` | Start overlay |
|
||||
| `y-S` | Stop overlay |
|
||||
| `y-t` | Toggle visible overlay |
|
||||
| `y-o` | Open Yomitan settings |
|
||||
| `y-r` | Restart overlay |
|
||||
| `y-c` | Check overlay status |
|
||||
|
||||
When the overlay has focus, press `y` then `d` to toggle DevTools (debugging helper).
|
||||
|
||||
@@ -112,7 +100,6 @@ All `shortcuts.*` keys accept [Electron accelerator strings](https://www.electro
|
||||
"mineSentence": "CommandOrControl+S",
|
||||
"copySubtitle": "CommandOrControl+C",
|
||||
"toggleVisibleOverlayGlobal": "Alt+Shift+O",
|
||||
"toggleInvisibleOverlayGlobal": "Alt+Shift+I",
|
||||
"openJimaku": null, // disabled
|
||||
},
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ SubMiner retries the connection automatically with increasing delays (200 ms, 50
|
||||
- first subtitle parse/tokenization bursts
|
||||
- media generation (`ffmpeg` audio/image and AVIF paths)
|
||||
- media sync and subtitle tooling (`alass`, `ffsubsync`, `whisper` fallback path)
|
||||
- `ankiConnect` enrichment and frequent polling
|
||||
- `ankiConnect` enrichment (plus polling overhead when proxy mode is disabled)
|
||||
|
||||
### If playback feels sluggish
|
||||
|
||||
@@ -104,11 +104,17 @@ Logged when a malformed JSON line arrives from the mpv socket. Usually harmless
|
||||
|
||||
**"AnkiConnect: unable to connect"**
|
||||
|
||||
SubMiner polls AnkiConnect at `http://127.0.0.1:8765` (configurable via `ankiConnect.url`). This error means Anki is not running or the AnkiConnect add-on is not installed.
|
||||
SubMiner connects to the active Anki endpoint:
|
||||
|
||||
- `ankiConnect.url` (direct mode, default `http://127.0.0.1:8765`)
|
||||
- `http://<ankiConnect.proxy.host>:<ankiConnect.proxy.port>` (proxy mode)
|
||||
|
||||
This error means the active endpoint is unavailable, or (in proxy mode) the proxy cannot reach `ankiConnect.proxy.upstreamUrl`.
|
||||
|
||||
- Install the [AnkiConnect](https://ankiweb.net/shared/info/2055492159) add-on in Anki.
|
||||
- Make sure Anki is running before you start mining.
|
||||
- If you changed the AnkiConnect port, update `ankiConnect.url` in your config.
|
||||
- If you changed the AnkiConnect port, update `ankiConnect.url` (or `ankiConnect.proxy.upstreamUrl` if using proxy mode).
|
||||
- If using external Yomitan/browser clients, confirm they point to your SubMiner proxy URL.
|
||||
|
||||
SubMiner retries with exponential backoff (up to 5 s) and suppresses repeated error logs after 5 consecutive failures. When Anki comes back, you will see "AnkiConnect connection restored".
|
||||
|
||||
@@ -122,7 +128,7 @@ See [Anki Integration](/anki-integration) for the full field mapping reference.
|
||||
|
||||
Shown when SubMiner tries to update a card that no longer exists, or when AnkiConnect rejects the update. Common causes:
|
||||
|
||||
- The card was deleted in Anki between polling and update.
|
||||
- The card was deleted in Anki between creation and enrichment update.
|
||||
- The note type changed and a mapped field no longer exists.
|
||||
|
||||
## Overlay
|
||||
@@ -153,7 +159,7 @@ SubMiner positions the overlay by tracking the mpv window. If tracking fails:
|
||||
- Sway: Ensure `swaymsg` is available.
|
||||
- X11: Ensure `xdotool` and `xwininfo` are installed.
|
||||
|
||||
If the overlay position is slightly off, use invisible subtitle position edit mode (`Ctrl/Cmd+Shift+P`) to fine-tune the offset with arrow keys, then save with `Enter` or `Ctrl+S`.
|
||||
If the overlay position is slightly off, right-click and drag on subtitle text to fine-tune the overlay subtitle offset.
|
||||
|
||||
## Yomitan
|
||||
|
||||
@@ -217,10 +223,10 @@ Media generation has a 30-second timeout (60 seconds for animated AVIF). If your
|
||||
|
||||
**"Failed to register global shortcut"**
|
||||
|
||||
Global shortcuts (`Alt+Shift+O`, `Alt+Shift+I`, `Alt+Shift+Y`) may conflict with other applications or desktop environment keybindings.
|
||||
Global shortcuts (`Alt+Shift+O`, `Alt+Shift+Y`) may conflict with other applications or desktop environment keybindings.
|
||||
|
||||
- Check your DE/WM keybinding settings for conflicts.
|
||||
- Change the shortcuts in your config under `shortcuts.toggleVisibleOverlayGlobal`, `shortcuts.toggleInvisibleOverlayGlobal`.
|
||||
- Change the shortcut in your config under `shortcuts.toggleVisibleOverlayGlobal`.
|
||||
- On Wayland, global shortcut registration has limitations depending on the compositor.
|
||||
|
||||
**Overlay keybindings not working**
|
||||
@@ -273,5 +279,5 @@ The Jimaku API has rate limits. If you see 429 errors, wait for the retry durati
|
||||
### macOS
|
||||
|
||||
- **Accessibility permission**: Required for window tracking. Grant it in System Settings > Privacy & Security > Accessibility.
|
||||
- **Font rendering**: macOS uses a 0.87x font compensation factor for subtitle alignment between mpv and the overlay. If text alignment looks off, adjust the invisible subtitle offset.
|
||||
- **Font rendering**: macOS uses a 0.87x font compensation factor for subtitle alignment between mpv and the overlay. If text alignment looks off, adjust subtitle offset by right-click dragging subtitle text.
|
||||
- **Gatekeeper**: If macOS blocks SubMiner, right-click the app and select "Open" to bypass the warning, or remove the quarantine attribute: `xattr -d com.apple.quarantine /path/to/SubMiner.app`
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
# Usage
|
||||
|
||||
> [!IMPORTANT]
|
||||
> SubMiner requires the bundled Yomitan instance to have at least one dictionary imported for lookups to work.
|
||||
> See [Yomitan setup](#yomitan-setup) for details.
|
||||
|
||||
There are two ways to use SubMiner — the `subminer` wrapper script or the mpv plugin:
|
||||
|
||||
| Approach | Best For |
|
||||
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **subminer script** | All-in-one solution. Handles video selection, launches MPV with the correct socket, and manages app commands. Overlay start is explicit (`--start`, `-S`, or `y-s`). |
|
||||
| **MPV plugin** | When you launch MPV yourself or from other tools. Provides in-MPV chord keybindings (e.g. `y-y` for menu) to control visible and invisible overlay layers. Requires `--input-ipc-server=/tmp/subminer-socket`. |
|
||||
| Approach | Best For |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **subminer script** | All-in-one solution. Handles video selection, launches MPV with the correct socket, and manages app commands. With default plugin settings, overlay auto-starts visible and playback resumes after annotation readiness. |
|
||||
| **MPV plugin** | When you launch MPV yourself or from other tools. Provides in-MPV chord keybindings (e.g. `y-y` for menu) to control overlay visibility. Requires `--input-ipc-server=/tmp/subminer-socket`. |
|
||||
|
||||
You can use both together—install the plugin for on-demand control, but use `subminer` when you want the streamlined workflow.
|
||||
|
||||
`subminer` is implemented as a Bun script and runs directly via shebang (no `bun run` needed), for example: `subminer --start video.mkv`.
|
||||
`subminer` is implemented as a Bun script and runs directly via shebang (no `bun run` needed), for example: `subminer video.mkv`.
|
||||
|
||||
## Live Config Reload
|
||||
|
||||
@@ -34,8 +38,9 @@ subminer # Current directory (uses fzf)
|
||||
subminer -R # Use rofi instead of fzf
|
||||
subminer -d ~/Videos # Specific directory
|
||||
subminer -r -d ~/Anime # Recursive search
|
||||
subminer video.mkv # Play specific file
|
||||
subminer --start video.mkv # Play + explicitly start overlay
|
||||
subminer video.mkv # Play specific file (default plugin config auto-starts visible overlay)
|
||||
subminer --start video.mkv # Optional explicit overlay start (use when plugin auto_start=no)
|
||||
subminer -S video.mkv # Same as above via --start-overlay
|
||||
subminer https://youtu.be/... # Play a YouTube URL
|
||||
subminer ytsearch:"jp news" # Play first YouTube search result
|
||||
subminer --log-level debug video.mkv # Enable verbose logs for launch/debugging
|
||||
@@ -68,11 +73,8 @@ SubMiner.AppImage --start --texthooker # Start overlay with texthooker
|
||||
SubMiner.AppImage --texthooker # Launch texthooker only (no overlay window)
|
||||
SubMiner.AppImage --stop # Stop overlay
|
||||
SubMiner.AppImage --start --toggle # Start MPV IPC + toggle visibility
|
||||
SubMiner.AppImage --start --toggle-invisible-overlay # Start MPV IPC + toggle invisible layer
|
||||
SubMiner.AppImage --show-visible-overlay # Force show visible overlay
|
||||
SubMiner.AppImage --hide-visible-overlay # Force hide visible overlay
|
||||
SubMiner.AppImage --show-invisible-overlay # Force show invisible overlay
|
||||
SubMiner.AppImage --hide-invisible-overlay # Force hide invisible overlay
|
||||
SubMiner.AppImage --start --dev # Enable app/dev mode only
|
||||
SubMiner.AppImage --start --debug # Alias for --dev
|
||||
SubMiner.AppImage --start --log-level debug # Force verbose logging without app/dev mode
|
||||
@@ -149,6 +151,14 @@ secondary-sub-visibility=no
|
||||
|
||||
`secondary-slang` is not an mpv option; use `slang` with `sid=auto` / `secondary-sid=auto` instead.
|
||||
|
||||
### Yomitan setup
|
||||
|
||||
SubMiner includes a bundled Yomitan extension for overlay word lookup. This bundled extension is separate from any Yomitan browser extension you may have installed.
|
||||
|
||||
For SubMiner overlay lookups to work, open Yomitan settings (`subminer app --settings` or `SubMiner.AppImage --settings`) and import at least one dictionary in the bundled Yomitan instance.
|
||||
|
||||
If you also use Yomitan in a browser, configure that browser profile separately; it does not inherit dictionaries or settings from the bundled instance.
|
||||
|
||||
### YouTube Playback
|
||||
|
||||
`subminer` accepts direct URLs (for example, YouTube links) and `ytsearch:` targets, and forwards them to mpv.
|
||||
@@ -170,11 +180,10 @@ Notes:
|
||||
|
||||
### Global Shortcuts
|
||||
|
||||
| Keybind | Action |
|
||||
| ------------- | ------------------------ |
|
||||
| `Alt+Shift+O` | Toggle visible overlay |
|
||||
| `Alt+Shift+I` | Toggle invisible overlay |
|
||||
| `Alt+Shift+Y` | Open Yomitan settings |
|
||||
| Keybind | Action |
|
||||
| ------------- | ---------------------- |
|
||||
| `Alt+Shift+O` | Toggle visible overlay |
|
||||
| `Alt+Shift+Y` | Open Yomitan settings |
|
||||
|
||||
`Alt+Shift+Y` is a fixed global shortcut; it is not part of `shortcuts` config.
|
||||
|
||||
@@ -195,14 +204,12 @@ Notes:
|
||||
| `Ctrl+W` | Quit mpv |
|
||||
| `Right-click` | Toggle MPV pause (outside subtitle area) |
|
||||
| `Right-click + drag` | Move subtitle position (on subtitle) |
|
||||
| `Ctrl/Cmd+Shift+P` | Toggle invisible subtitle position edit mode |
|
||||
| `Arrow keys` | Move invisible subtitles while edit mode is active |
|
||||
| `Enter` / `Ctrl+S` | Save invisible subtitle position in edit mode |
|
||||
| `Esc` | Cancel invisible subtitle position edit mode |
|
||||
| `Ctrl/Cmd+A` | Append clipboard video path to MPV playlist |
|
||||
|
||||
These keybindings only work when the overlay window has focus. See [Configuration](/configuration) for customization.
|
||||
|
||||
By default, hovering over subtitle text pauses mpv playback and leaving the subtitle area resumes playback. Set `subtitleStyle.autoPauseVideoOnHover` to `false` to disable this behavior.
|
||||
|
||||
### Drag-and-drop Queueing
|
||||
|
||||
- Drag and drop one or more video files onto the overlay to replace current playback (`loadfile ... replace` for first file, then append remainder).
|
||||
|
||||