docs: replace em-dashes with hyphens across docs-site

This commit is contained in:
2026-06-02 23:36:44 -07:00
parent 4cc6c12dc7
commit a80ed72b2d
23 changed files with 256 additions and 252 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ SubMiner can sync your watch progress to [AniList](https://anilist.co) automatic
AniList data also powers two additional features: [cover art](#cover-art) for the stats dashboard and the [Character Dictionary](/character-dictionary) for in-overlay name lookup.
[AniList](https://anilist.co) is a free website for tracking which anime you have watched. An **access token** is a private key SubMiner stores so it can update your list on your behalf you approve it once during setup, and you never paste a password into SubMiner.
[AniList](https://anilist.co) is a free website for tracking which anime you have watched. An **access token** is a private key SubMiner stores so it can update your list on your behalf - you approve it once during setup, and you never paste a password into SubMiner.
## Setup
+3 -3
View File
@@ -6,7 +6,7 @@ This project is built primarily for [Kiku](https://kiku.youyoumu.my.id/) and [La
::: tip New to these terms?
- **Anki** is the flashcard app where your study cards live.
- **AnkiConnect** is a free add-on that lets other programs (like SubMiner) talk to Anki over a local connection. SubMiner needs it installed to add or edit cards.
- A **note type** (also called a "model") is the template that defines what a card looks like for example the Kiku or Lapis templates many Japanese learners use.
- A **note type** (also called a "model") is the template that defines what a card looks like - for example the Kiku or Lapis templates many Japanese learners use.
- A **field** is one labeled slot in that template, such as `Sentence`, `Expression`, or `Picture`. SubMiner fills these fields when it mines a card.
:::
@@ -22,9 +22,9 @@ AnkiConnect listens on `http://127.0.0.1:8765` by default. If you changed the po
When you add a word via Yomitan, SubMiner detects the new card and fills in the sentence, audio, image, and translation fields automatically. Two detection methods are available:
**Proxy mode** (default) SubMiner runs a local *proxy*: a small middleman server that sits between Yomitan and Anki. Yomitan sends new cards to SubMiner, SubMiner enriches them, then passes them along to Anki. This makes enrichment instant.
**Proxy mode** (default) - SubMiner runs a local *proxy*: a small middleman server that sits between Yomitan and Anki. Yomitan sends new cards to SubMiner, SubMiner enriches them, then passes them along to Anki. This makes enrichment instant.
**Polling mode** (fallback, when the proxy is disabled) SubMiner asks AnkiConnect every few seconds whether any new cards were added, then enriches them. Simpler setup, but with a short delay (~3 seconds).
**Polling mode** (fallback, when the proxy is disabled) - SubMiner asks AnkiConnect every few seconds whether any new cards were added, then enriches them. Simpler setup, but with a short delay (~3 seconds).
Use proxy mode if you want immediate enrichment. Use polling mode if your Yomitan instance is external (browser-based) or you prefer minimal configuration.
+14 -14
View File
@@ -34,7 +34,7 @@ plugin/
src/
ai/ # AI translation provider utilities (client, config)
main-entry.ts # Background-mode bootstrap wrapper before loading main.js
main.ts # Entry point delegates to runtime composers/domain modules
main.ts # Entry point - delegates to runtime composers/domain modules
preload.ts # Electron preload bridge
types.ts # Shared type definitions
main/ # Main-process composition/runtime adapters
@@ -226,17 +226,17 @@ Most runtime code follows a dependency-injection pattern:
The composition root (`src/main.ts`) delegates to focused modules in `src/main/` and `src/main/runtime/composers/`:
- `startup.ts` argv/env processing and bootstrap flow
- `app-lifecycle.ts` Electron lifecycle event registration
- `startup-lifecycle.ts` app-ready initialization sequence
- `state.ts` centralized application runtime state container
- `ipc-runtime.ts` IPC channel registration and handler wiring
- `cli-runtime.ts` CLI command parsing and dispatch
- `overlay-runtime.ts` overlay window selection and modal state management
- `subsync-runtime.ts` subsync command orchestration
- `runtime/composers/anilist-tracking-composer.ts` AniList media tracking/probe/retry wiring
- `runtime/composers/jellyfin-runtime-composer.ts` Jellyfin config/client/playback/command/setup composition wiring
- `runtime/composers/mpv-runtime-composer.ts` MPV event/factory/tokenizer/warmup wiring
- `startup.ts` - argv/env processing and bootstrap flow
- `app-lifecycle.ts` - Electron lifecycle event registration
- `startup-lifecycle.ts` - app-ready initialization sequence
- `state.ts` - centralized application runtime state container
- `ipc-runtime.ts` - IPC channel registration and handler wiring
- `cli-runtime.ts` - CLI command parsing and dispatch
- `overlay-runtime.ts` - overlay window selection and modal state management
- `subsync-runtime.ts` - subsync command orchestration
- `runtime/composers/anilist-tracking-composer.ts` - AniList media tracking/probe/retry wiring
- `runtime/composers/jellyfin-runtime-composer.ts` - Jellyfin config/client/playback/command/setup composition wiring
- `runtime/composers/mpv-runtime-composer.ts` - MPV event/factory/tokenizer/warmup wiring
Composer modules share contract conventions via `src/main/runtime/composers/contracts.ts`:
@@ -271,9 +271,9 @@ For domains migrated to reducer-style transitions (for example AniList token/que
## Playback Startup Flow
Before the app boots, something has to launch mpv, inject the plugin, and bring the overlay up. SubMiner-managed launches own this step the `subminer` launcher, the app's own playback, and the packaged Windows shortcut all follow the same path. The launcher reads `config.jsonc`, spawns mpv with the IPC socket and the bundled plugin, and passes runtime settings as `--script-opts`. The plugin never reads a config file: the shipped `subminer.conf` is intentionally empty so command-line opts always win.
Before the app boots, something has to launch mpv, inject the plugin, and bring the overlay up. SubMiner-managed launches own this step - the `subminer` launcher, the app's own playback, and the packaged Windows shortcut all follow the same path. The launcher reads `config.jsonc`, spawns mpv with the IPC socket and the bundled plugin, and passes runtime settings as `--script-opts`. The plugin never reads a config file: the shipped `subminer.conf` is intentionally empty so command-line opts always win.
Once mpv is up, exactly one of two triggers brings up the overlay. On a first launch the plugin's `file-loaded` hook self-starts the app once the socket is ready (because the launcher injected `auto_start=yes`). When the app is already running or for explicit `--start-overlay` and YouTube flows the launcher instead attaches over the control socket and suppresses the plugin's auto-start, so the two never fire together. Both converge on the same app bring-up, which then runs the Program Lifecycle below.
Once mpv is up, exactly one of two triggers brings up the overlay. On a first launch the plugin's `file-loaded` hook self-starts the app once the socket is ready (because the launcher injected `auto_start=yes`). When the app is already running - or for explicit `--start-overlay` and YouTube flows - the launcher instead attaches over the control socket and suppresses the plugin's auto-start, so the two never fire together. Both converge on the same app bring-up, which then runs the Program Lifecycle below.
```mermaid
flowchart TB
+6 -6
View File
@@ -6,7 +6,7 @@
- **Linux Overlay Stacking**: Fixed the overlay intermittently dropping behind mpv on KDE Plasma and other non-Hyprland/Sway Wayland sessions; restored subtitle hover, pause-on-hover, and Yomitan lookups on X11/XWayland; the overlay now correctly layers above/below mpv based on fullscreen state, yields to foreground windows (Settings, Yomitan, AniList, etc.), and avoids startup flashes and fullscreen transition glitches.
- **Linux Overlay (Hyprland Lua)**: Fixed overlay placement on Hyprland 0.55+ when using a Lua-based config.
- **Manual Overlay Startup**: Fixed manual visible-overlay startup from mpv now correctly attaches to playback, keeps the window bounds synced with mpv, and primes current subtitles before showing.
- **Manual Overlay Startup**: Fixed manual visible-overlay startup from mpv - now correctly attaches to playback, keeps the window bounds synced with mpv, and primes current subtitles before showing.
- **Playlist Transitions**: Reused the warm overlay when mpv advances to the next playlist item, avoiding a redundant tokenization pause and preserving visible subtitles across tracks.
- **macOS Overlay**: Fixed the visible subtitle overlay staying click-through after pause-until-ready releases playback; restored mpv focus after closing modal windows so subtitles and keybinds resume without clicking the player.
- **Mouse Keybindings**: Fixed keybinding capture and runtime handling for mouse buttons, including side buttons like `MBTN_BACK` and `MBTN_FORWARD`.
@@ -25,7 +25,7 @@
- Subsync now always opens the manual subtitle picker regardless of any previously set default mode
- **N+1 Highlighting:**
- N+1 highlighting now has its own dedicated `ankiConnect.nPlusOne.enabled` option, separate from known-word highlighting
- It is no longer enabled automatically when known-word highlighting is on enable it explicitly to keep N+1 annotations
- It is no longer enabled automatically when known-word highlighting is on - enable it explicitly to keep N+1 annotations
**Added**
@@ -177,7 +177,7 @@
**Added**
- **Character Dictionary:** Added AniList-based character dictionary selection for resolving title mismatches open it in-app with the new `Ctrl+Alt+A` shortcut or from the CLI with `subminer dictionary --candidates` / `--select`. Series-scoped overrides replace stale entries in the merged dictionary.
- **Character Dictionary:** Added AniList-based character dictionary selection for resolving title mismatches - open it in-app with the new `Ctrl+Alt+A` shortcut or from the CLI with `subminer dictionary --candidates` / `--select`. Series-scoped overrides replace stale entries in the merged dictionary.
- **Primary Subtitle Bar:** Added a `V` shortcut and mpv plugin binding to toggle the primary subtitle bar without affecting mpv's native subtitle visibility.
- **Texthooker:** Added `subminer texthooker -o` and a tray menu item to open the local texthooker page in the default browser.
@@ -231,7 +231,7 @@
**Internal**
- Replaced the changelog renderer with an AI polish pass that merges related fragments and writes user-facing release notes. `CHANGELOG.md` keeps internal items in a collapsed `<details>` block; GitHub release notes omit them entirely.
- Release CI no longer auto-builds pending `changes/*.md` fragments on tag. Tagging now fails fast if fragments remain run `bun run changelog:build` (requires the `claude` CLI) and commit before tagging.
- Release CI no longer auto-builds pending `changes/*.md` fragments on tag. Tagging now fails fast if fragments remain - run `bun run changelog:build` (requires the `claude` CLI) and commit before tagging.
</details>
@@ -255,8 +255,8 @@
- Stats: Episode detail hides card events whose Anki notes have been deleted, instead of showing phantom mining activity.
- Stats: Trend and watch-time charts share a unified theme with horizontal gridlines and larger ticks for legibility.
- Stats: Overview, Library, Trends, Sessions, and Vocabulary now use generic "title" wording so YouTube videos and anime live comfortably side by side in the dashboard.
- Stats: Session timeline no longer plots seek-forward/seek-backward markers they were too noisy on sessions with lots of rewinds.
- Stats: Replaced the "Library Per Day" section on the Stats → Trends page with a "Library Summary" section. The new section shows a top-10 watch-time leaderboard chart and a sortable per-title table (watch time, videos, sessions, cards, words, lookups, lookups/100w, date range), all scoped to the current date range selector.
- Stats: Session timeline no longer plots seek-forward/seek-backward markers - they were too noisy on sessions with lots of rewinds.
- Stats: Replaced the "Library - Per Day" section on the Stats → Trends page with a "Library - Summary" section. The new section shows a top-10 watch-time leaderboard chart and a sortable per-title table (watch time, videos, sessions, cards, words, lookups, lookups/100w, date range), all scoped to the current date range selector.
**Fixed**
+32 -32
View File
@@ -1,6 +1,6 @@
# Character Dictionary
SubMiner can build a Yomitan-compatible character dictionary from [AniList](https://anilist.co) metadata so that character names in subtitles are recognized, highlighted, and enrichable with context portraits, roles, voice actors, and biographical detail without leaving the overlay. (AniList is an online anime/manga database; SubMiner pulls each show's character list from it.)
SubMiner can build a Yomitan-compatible character dictionary from [AniList](https://anilist.co) metadata so that character names in subtitles are recognized, highlighted, and enrichable with context - portraits, roles, voice actors, and biographical detail - without leaving the overlay. (AniList is an online anime/manga database; SubMiner pulls each show's character list from it.)
This is helpful because proper names rarely appear in normal dictionaries, so character names would otherwise be flagged as "unknown" words and clutter your mining. Recognizing them keeps your N+1 highlighting focused on real vocabulary.
@@ -10,25 +10,25 @@ The dictionary is generated per-media, merged across your recently-watched title
The feature has three stages: **snapshot**, **merge**, and **match**.
1. **Snapshot** When you start watching a new title, SubMiner queries the AniList GraphQL API for the media's character list. Each character's names, reading, role, description, birthday, voice actors, and portrait are fetched and saved as a local JSON snapshot in `character-dictionaries/snapshots/anilist-{mediaId}.json`. Images are downloaded and base64-encoded into the snapshot.
1. **Snapshot** - When you start watching a new title, SubMiner queries the AniList GraphQL API for the media's character list. Each character's names, reading, role, description, birthday, voice actors, and portrait are fetched and saved as a local JSON snapshot in `character-dictionaries/snapshots/anilist-{mediaId}.json`. Images are downloaded and base64-encoded into the snapshot.
2. **Merge** SubMiner maintains a most-recently-used list of media IDs (default: 3). Snapshots from those titles are merged into a single Yomitan ZIP `character-dictionaries/merged.zip` which is always named "SubMiner Character Dictionary" so Yomitan treats it as a single stable dictionary across rebuilds.
2. **Merge** - SubMiner maintains a most-recently-used list of media IDs (default: 3). Snapshots from those titles are merged into a single Yomitan ZIP - `character-dictionaries/merged.zip` - which is always named "SubMiner Character Dictionary" so Yomitan treats it as a single stable dictionary across rebuilds.
3. **Match** During subtitle rendering, Yomitan scans subtitle text against all loaded dictionaries including the character dictionary. SubMiner only accepts character entries for the current AniList media when that media ID is known, then flags matching tokens with `isNameMatch` and highlights them in the overlay with a distinct color.
3. **Match** - During subtitle rendering, Yomitan scans subtitle text against all loaded dictionaries including the character dictionary. SubMiner only accepts character entries for the current AniList media when that media ID is known, then flags matching tokens with `isNameMatch` and highlights them in the overlay with a distinct color.
## Enabling the Feature
Character dictionary sync is disabled by default. To turn it on:
1. Enable **Name Match** in Settings → Subtitle Style, or set `subtitleStyle.nameMatchEnabled: true` in your config.
2. Start watching SubMiner queries AniList's public GraphQL API (no authentication required) and imports the merged dictionary into Yomitan automatically.
2. Start watching - SubMiner queries AniList's public GraphQL API (no authentication required) and imports the merged dictionary into Yomitan automatically.
3. Optionally enable **Name Match Images** (Settings → Subtitle Style) to show inline circular character portraits next to matched names in subtitles.
```jsonc
{
"subtitleStyle": {
"nameMatchEnabled": true,
"nameMatchImagesEnabled": true, // optional inline portraits
"nameMatchImagesEnabled": true, // optional - inline portraits
},
}
```
@@ -38,7 +38,7 @@ The first sync for a media title takes a few seconds while character data and po
:::
::: info
AniList character data is fetched via public GraphQL queries no account or access token is needed. AniList authentication is only required for the separate [watch-progress sync](/anilist-integration) feature.
AniList character data is fetched via public GraphQL queries - no account or access token is needed. AniList authentication is only required for the separate [watch-progress sync](/anilist-integration) feature.
:::
::: warning
@@ -60,7 +60,7 @@ A single character produces many searchable terms so that names are recognized r
- ア・リ・ス → アリス (combined), plus individual segments
**Honorific suffixes** each base name is expanded with 15 common suffixes:
**Honorific suffixes** - each base name is expanded with 15 common suffixes:
| Honorific | Reading |
| --------- | ---------- |
@@ -80,16 +80,16 @@ A single character produces many searchable terms so that names are recognized r
| 社長 | しゃちょう |
| 部長 | ぶちょう |
**Romanized names** names stored in romaji on AniList are converted to kana aliases so they can match against Japanese subtitle text.
**Romanized names** - names stored in romaji on AniList are converted to kana aliases so they can match against Japanese subtitle text.
This means a character like "太郎" generates entries for 太郎, 太郎さん, 太郎先生, 太郎君, 太郎ちゃん, and so on all with correct readings.
This means a character like "太郎" generates entries for 太郎, 太郎さん, 太郎先生, 太郎君, 太郎ちゃん, and so on - all with correct readings.
## Name Matching
Name matching runs inside Yomitan's scanning pipeline during subtitle tokenization.
1. Yomitan receives subtitle text and scans for dictionary matches.
2. Entries from "SubMiner Character Dictionary" are checked with exact primary-source matching the token must match the entry's `originalText` with `isPrimary: true` and `matchType: 'exact'`.
2. Entries from "SubMiner Character Dictionary" are checked with exact primary-source matching - the token must match the entry's `originalText` with `isPrimary: true` and `matchType: 'exact'`.
3. When the current AniList media ID is known, entries whose embedded media ID belongs to a different title are ignored for name matching and inline portraits.
4. Matched tokens are flagged `isNameMatch: true` and forwarded to the renderer.
5. If `subtitleStyle.nameMatchEnabled` is enabled, the renderer applies the name-match highlight color (default: `#f5bde6`).
@@ -111,7 +111,7 @@ Name matches are visually distinct from [N+1 targeting, frequency highlighting,
When `subtitleStyle.nameMatchImagesEnabled` is enabled, SubMiner injects a small circular portrait image directly into the subtitle line next to each matched character name.
Portraits are sourced from the local snapshot they are embedded at snapshot-generation time and served from the cached ZIP, so no network request happens during playback. Images are downloaded from AniList CDN once per character and stored in `character-dictionaries/img/`.
Portraits are sourced from the local snapshot - they are embedded at snapshot-generation time and served from the cached ZIP, so no network request happens during playback. Images are downloaded from AniList CDN once per character and stored in `character-dictionaries/img/`.
If a snapshot was generated before portrait data was available (e.g. during an earlier version or offline sync), SubMiner detects the missing image data on the next media match and automatically refreshes the snapshot so portraits are included in the next merged dictionary build.
@@ -123,20 +123,20 @@ If a snapshot was generated before portrait data was available (e.g. during an e
The portrait size is controlled by the surrounding subtitle font size and renders as a circle clipped from the character's AniList cover image.
::: tip
Inline portraits help you quickly associate names with faces while building vocabulary especially useful for shows with large casts where you're still learning who's who.
Inline portraits help you quickly associate names with faces while building vocabulary - especially useful for shows with large casts where you're still learning who's who.
:::
## Dictionary Entries
Each character entry in the Yomitan dictionary includes structured content:
- **Name** the matched Japanese name form
- **Known names** generated non-honorific Japanese aliases for that character, excluding raw romanized/English aliases from lookup results
- **Role badge** color-coded by role: main (score 100), supporting (90), side (80), background (70)
- **Portrait** character image from AniList, embedded in the ZIP
- **Description** biography text from AniList (collapsible)
- **Character information** age, birthday, gender, blood type (collapsible)
- **Voiced by** voice actor name and portrait (collapsible)
- **Name** - the matched Japanese name form
- **Known names** - generated non-honorific Japanese aliases for that character, excluding raw romanized/English aliases from lookup results
- **Role badge** - color-coded by role: main (score 100), supporting (90), side (80), background (70)
- **Portrait** - character image from AniList, embedded in the ZIP
- **Description** - biography text from AniList (collapsible)
- **Character information** - age, birthday, gender, blood type (collapsible)
- **Voiced by** - voice actor name and portrait (collapsible)
The three collapsible sections can be configured to start open or closed:
@@ -160,12 +160,12 @@ When `subtitleStyle.nameMatchEnabled` is `true`, SubMiner runs an auto-sync rout
**Phases:**
1. **checking** Is there already a cached snapshot for this media ID?
2. **generating** No cache hit: fetch characters from AniList GraphQL, download portraits (250ms throttle between image requests), save snapshot JSON.
3. **syncing** Add the media ID to the most-recently-used list. Evict old entries beyond `maxLoaded`.
4. **building** Merge active snapshots into a single Yomitan ZIP. A SHA-1 revision hash is computed from the media set if it matches the previously imported revision, the import is skipped.
5. **importing** Push the ZIP into Yomitan. Waits for Yomitan mutation readiness (7-second timeout per operation).
6. **ready** Dictionary is live. Character names will match on the next subtitle line.
1. **checking** - Is there already a cached snapshot for this media ID?
2. **generating** - No cache hit: fetch characters from AniList GraphQL, download portraits (250ms throttle between image requests), save snapshot JSON.
3. **syncing** - Add the media ID to the most-recently-used list. Evict old entries beyond `maxLoaded`.
4. **building** - Merge active snapshots into a single Yomitan ZIP. A SHA-1 revision hash is computed from the media set - if it matches the previously imported revision, the import is skipped.
5. **importing** - Push the ZIP into Yomitan. Waits for Yomitan mutation readiness (7-second timeout per operation).
6. **ready** - Dictionary is live. Character names will match on the next subtitle line.
**State tracking** is persisted in `character-dictionaries/auto-sync-state.json`. AniList media matches are cached separately in `character-dictionaries/anilist-resolution-cache.json` so snapshot hits do not need another AniList search.
@@ -274,9 +274,9 @@ merged.zip
## Reference Implementation
SubMiner's character dictionary builder is inspired by the [Japanese Character Name Dictionary](https://github.com/bee-san/Japanese_Character_Name_Dictionary) project a standalone Rust web service that generates Yomitan character dictionaries from AniList and VNDB data.
SubMiner's character dictionary builder is inspired by the [Japanese Character Name Dictionary](https://github.com/bee-san/Japanese_Character_Name_Dictionary) project - a standalone Rust web service that generates Yomitan character dictionaries from AniList and VNDB data.
The reference implementation covers similar ground name variant generation, honorific expansion, structured Yomitan content, portrait embedding and additionally supports VNDB as a data source for visual novel characters. Key differences:
The reference implementation covers similar ground - name variant generation, honorific expansion, structured Yomitan content, portrait embedding - and additionally supports VNDB as a data source for visual novel characters. Key differences:
| | SubMiner | Reference Implementation |
| ---------------------- | -------------------------------------------- | ------------------------------------- |
@@ -291,7 +291,7 @@ If you work with visual novels or want a standalone dictionary generator indepen
## Troubleshooting
- **Names not highlighting:** Confirm `subtitleStyle.nameMatchEnabled` is `true`. Check that the current media has an AniList entry SubMiner needs a media ID to fetch characters.
- **Names not highlighting:** Confirm `subtitleStyle.nameMatchEnabled` is `true`. Check that the current media has an AniList entry - SubMiner needs a media ID to fetch characters.
- **Inline portraits missing:** Confirm `subtitleStyle.nameMatchImagesEnabled` is `true`. On the next character dictionary sync, SubMiner refreshes current-version snapshots that do not contain usable cached character portrait data. Portraits still require AniList to return an image and the image download to succeed.
- **Sync seems stuck:** The auto-sync debounces for 800ms after media changes and throttles image downloads at 250ms per image. Large casts (50+ characters) take longer. Check the status bar for the current sync phase.
- **Wrong characters showing:** Open the in-app character dictionary manager (`Ctrl/Cmd+D`) to remove/reorder loaded titles, then use **Override** to correct the active AniList match. You can also run `--dictionary-candidates`, then save the correct media with `--dictionary-select --dictionary-anilist-id <id>`. SubMiner ignores character entries from other loaded titles for subtitle name matching and inline portraits once the current media ID is known.
@@ -300,6 +300,6 @@ If you work with visual novels or want a standalone dictionary generator indepen
## Related
- [Subtitle Annotations](/subtitle-annotations) how name matches interact with N+1, frequency, and JLPT layers
- [AniList Integration](/anilist-integration) watch-progress sync and AniList authentication (separate from character dictionary)
- [Configuration Reference](/configuration) full config options
- [Subtitle Annotations](/subtitle-annotations) - how name matches interact with N+1, frequency, and JLPT layers
- [AniList Integration](/anilist-integration) - watch-progress sync and AniList authentication (separate from character dictionary)
- [Configuration Reference](/configuration) - full config options
+11 -11
View File
@@ -8,7 +8,7 @@ outline: [2, 3]
import { withBase } from 'vitepress';
</script>
SubMiner is configured through a single file (`config.jsonc`). Most settings are also editable from the in-app **Settings** window you rarely need to edit the file by hand. This page is the full reference: it explains the Settings window, where the config file lives, and documents every option grouped by topic. New to SubMiner? The Quick Start below plus the [Settings window](#settings) cover everything most users need.
SubMiner is configured through a single file (`config.jsonc`). Most settings are also editable from the in-app **Settings** window - you rarely need to edit the file by hand. This page is the full reference: it explains the Settings window, where the config file lives, and documents every option grouped by topic. New to SubMiner? The Quick Start below plus the [Settings window](#settings) cover everything most users need.
## Quick Start
@@ -39,7 +39,7 @@ Then customize as needed using the sections below.
## Settings
SubMiner includes a dedicated **Settings** window accessible from the tray menu, the app `--settings` flag, or launcher commands such as `subminer --settings` and `subminer settings`. It is the primary way to configure SubMiner all changes are written directly to `config.jsonc`, so manual file editing is not required for most users.
SubMiner includes a dedicated **Settings** window accessible from the tray menu, the app `--settings` flag, or launcher commands such as `subminer --settings` and `subminer settings`. It is the primary way to configure SubMiner - all changes are written directly to `config.jsonc`, so manual file editing is not required for most users.
The Settings window groups options by workflow instead of mirroring the raw config-file shape:
@@ -223,7 +223,7 @@ Control whether the overlay automatically becomes visible when it connects to mp
| -------------------- | --------------- | ----------------------------------------------------- |
| `auto_start_overlay` | `true`, `false` | Auto-show overlay on mpv connection (default: `true`) |
When you launch through the SubMiner app or the `subminer` wrapper, the launcher reads these settings from this config and injects them into the mpv plugin at runtime there is no separate plugin config file to edit. `auto_start_overlay` controls whether the visible overlay shows on auto-start. Two related keys in the `mpv` block tune startup behavior: `mpv.autoStartSubMiner` starts the overlay automatically when a file loads, and `mpv.pauseUntilOverlayReady` pauses mpv on visible auto-start until SubMiner signals overlay/tokenization readiness.
When you launch through the SubMiner app or the `subminer` wrapper, the launcher reads these settings from this config and injects them into the mpv plugin at runtime - there is no separate plugin config file to edit. `auto_start_overlay` controls whether the visible overlay shows on auto-start. Two related keys in the `mpv` block tune startup behavior: `mpv.autoStartSubMiner` starts the overlay automatically when a file loads, and `mpv.pauseUntilOverlayReady` pauses mpv on visible auto-start until SubMiner signals overlay/tokenization readiness.
On Windows, packaged plugin installs also rewrite the plugin socket path to `\\.\pipe\subminer-socket`.
@@ -526,9 +526,9 @@ The secondary-subtitle language list also acts as the fallback secondary-languag
**Display modes:**
- **hidden** Secondary subtitles not shown
- **visible** Always visible at top of overlay
- **hover** Only visible when hovering over the subtitle area (default)
- **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.
@@ -1101,8 +1101,8 @@ Set `openBrowser` to `false` to only print the URL without opening a browser.
Sync the active subtitle track from the overlay picker using `alass` or `ffsubsync`. Both are **optional external tools** that must be installed separately and available on your `PATH` (or configured via the path options below).
- [`alass`](https://github.com/kaegi/alass) fast, audio-independent sync using a secondary subtitle as reference
- [`ffsubsync`](https://github.com/smacke/ffsubsync) audio-based sync using the video file as reference
- [`alass`](https://github.com/kaegi/alass) - fast, audio-independent sync using a secondary subtitle as reference
- [`ffsubsync`](https://github.com/smacke/ffsubsync) - audio-based sync using the video file as reference
```json
{
@@ -1473,9 +1473,9 @@ If `mpv.profile` is configured and the launcher also receives `--profile`, SubMi
Launch mode behavior:
- **`normal`** mpv opens at its default window size with no extra flags.
- **`maximized`** mpv starts maximized via `--window-maximized=yes`, keeping taskbar access.
- **`fullscreen`** mpv starts in true fullscreen via `--fullscreen`.
- **`normal`** - mpv opens at its default window size with no extra flags.
- **`maximized`** - mpv starts maximized via `--window-maximized=yes`, keeping taskbar access.
- **`fullscreen`** - mpv starts in true fullscreen via `--fullscreen`.
### YouTube Playback Settings
+1 -1
View File
@@ -25,7 +25,7 @@ Mine vocabulary cards from Yomitan or directly from subtitle lines. SubMiner aut
## Subtitle Download & Sync
Search and download subtitles from Jimaku, then retime them with alass or ffsubsync all from within SubMiner.
Search and download subtitles from Jimaku, then retime them with alass or ffsubsync - all from within SubMiner.
<!-- <video controls playsinline preload="metadata" :poster="withBase(`/assets/demos/subtitle-sync-poster.jpg?v=${v}`)">
<source :src="withBase(`/assets/demos/subtitle-sync.webm?v=${v}`)" type="video/webm" />
+12 -12
View File
@@ -2,12 +2,12 @@
SubMiner can log your watching and mining activity to a local SQLite database, then surface it in the built-in stats dashboard. Tracking is enabled by default and can be turned off if you do not want local analytics.
"Immersion" here means time spent watching and reading native Japanese content. **All data stays on your computer** nothing is uploaded anywhere. (SQLite is just a single-file database; you do not need to install or manage anything.)
"Immersion" here means time spent watching and reading native Japanese content. **All data stays on your computer** - nothing is uploaded anywhere. (SQLite is just a single-file database; you do not need to install or manage anything.)
When enabled, SubMiner records per-session statistics (watch time, subtitle lines seen, words encountered, cards mined) and maintains exact lifetime summary tables plus daily/monthly rollups. You can view that data in SubMiner's stats UI or query the database directly with any SQLite tool.
::: tip For most users
Just leave tracking on and use the built-in [Stats Dashboard](#stats-dashboard). The retention, performance, SQL, and schema sections further down are reference material for advanced users who want to inspect or tune the database you can safely skip them.
Just leave tracking on and use the built-in [Stats Dashboard](#stats-dashboard). The retention, performance, SQL, and schema sections further down are reference material for advanced users who want to inspect or tune the database - you can safely skip them.
:::
Episode completion for local `watched` state uses the shared `DEFAULT_MIN_WATCH_RATIO` (`85%`) value from `src/shared/watch-threshold.ts`.
@@ -98,9 +98,9 @@ Stats server config lives under `stats`:
The Vocabulary tab's word detail panel shows example lines from your viewing history. Each example line with a valid source file offers three mining buttons:
- **Mine Word** performs a full Yomitan dictionary lookup for the word (definition, reading, pitch accent, etc.) via a short-lived hidden helper, then enriches the card with sentence audio, a screenshot or animated AVIF clip, the highlighted sentence, and metadata extracted from the source video file. Requires Anki and Yomitan dictionaries to be loaded.
- **Mine Sentence** creates a sentence card directly with the `IsSentenceCard` flag set (for Lapis/Kiku workflows), along with audio, image, and translation from the secondary subtitle if available.
- **Mine Audio** creates an audio-only card with the `IsAudioCard` flag, attaching only the sentence audio clip.
- **Mine Word** - performs a full Yomitan dictionary lookup for the word (definition, reading, pitch accent, etc.) via a short-lived hidden helper, then enriches the card with sentence audio, a screenshot or animated AVIF clip, the highlighted sentence, and metadata extracted from the source video file. Requires Anki and Yomitan dictionaries to be loaded.
- **Mine Sentence** - creates a sentence card directly with the `IsSentenceCard` flag set (for Lapis/Kiku workflows), along with audio, image, and translation from the secondary subtitle if available.
- **Mine Audio** - creates an audio-only card with the `IsAudioCard` flag, attaching only the sentence audio clip.
All three modes respect your `ankiConnect` config: deck, model, field mappings, media settings (static vs AVIF, quality, dimensions), audio padding, metadata pattern, and tags. Media generation runs in parallel for faster card creation.
@@ -281,11 +281,11 @@ LIMIT ?;
Core tables:
- `imm_videos` video key/title/source metadata
- `imm_sessions` session UUID, video reference, timing/status, final denormalized totals
- `imm_session_telemetry` high-frequency session aggregates over time
- `imm_session_events` event stream with compact numeric event types
- `imm_subtitle_lines` persisted subtitle text and timing per session/video
- `imm_videos` - video key/title/source metadata
- `imm_sessions` - session UUID, video reference, timing/status, final denormalized totals
- `imm_session_telemetry` - high-frequency session aggregates over time
- `imm_session_events` - event stream with compact numeric event types
- `imm_subtitle_lines` - persisted subtitle text and timing per session/video
Lifetime summary tables:
@@ -306,5 +306,5 @@ Vocabulary tables:
Media-art tables:
- `imm_media_art` per-video cover metadata plus shared blob reference
- `imm_cover_art_blobs` deduplicated image bytes keyed by blob hash
- `imm_media_art` - per-video cover metadata plus shared blob reference
- `imm_cover_art_blobs` - deduplicated image bytes keyed by blob hash
+5 -5
View File
@@ -24,7 +24,7 @@ features:
src: /assets/mpv.svg
alt: mpv icon
title: Built for mpv
details: Tracks subtitles via mpv IPC in real time. Launch with the wrapper script or the mpv plugin no external bridge needed.
details: Tracks subtitles via mpv IPC in real time. Launch with the wrapper script or the mpv plugin - no external bridge needed.
link: /usage
linkText: How it works
- icon:
@@ -45,14 +45,14 @@ features:
src: /assets/highlight.svg
alt: Highlight icon
title: Reading Annotations
details: N+1 targeting, character-name matching, frequency highlighting, and JLPT tagging all layered on subtitle text in real time.
details: N+1 targeting, character-name matching, frequency highlighting, and JLPT tagging - all layered on subtitle text in real time.
link: /subtitle-annotations
linkText: Annotation details
- icon:
src: /assets/video.svg
alt: Video playback icon
title: YouTube Playback
details: Play YouTube URLs or ytsearch targets directly SubMiner automatically selects and loads subtitles for the video.
details: Play YouTube URLs or ytsearch targets directly - SubMiner automatically selects and loads subtitles for the video.
link: /usage#youtube-playback
linkText: YouTube playback
- icon:
@@ -66,14 +66,14 @@ features:
src: /assets/subtitle-download.svg
alt: Subtitle download icon
title: Subtitle Download & Sync
details: Search and pull subtitles from Jimaku, then retime subtitles with alass or ffsubsync all from the overlay.
details: Search and pull subtitles from Jimaku, then retime subtitles with alass or ffsubsync - all from the overlay.
link: /jimaku-integration
linkText: Jimaku integration
- icon:
src: /assets/tokenization.svg
alt: Tracking chart icon
title: Stats Dashboard
details: Browse session history, streak calendars, vocabulary frequency, and per-series progress in a local dashboard then mine cards straight from your viewing history.
details: Browse session history, streak calendars, vocabulary frequency, and per-series progress in a local dashboard - then mine cards straight from your viewing history.
link: /immersion-tracking
linkText: Dashboard & tracking
- icon:
+26 -26
View File
@@ -1,12 +1,12 @@
# Installation
SubMiner is a desktop app that draws an interactive layer an **overlay** on top of the [mpv](https://mpv.io) video player. As you watch native Japanese media, you can click or hover any word in the subtitles to look it up, then turn it into an Anki flashcard without pausing to switch apps. Building flashcards from real content you're watching is called **sentence mining**, and it's what SubMiner is built for. It bundles its own copy of **Yomitan** (a pop-up dictionary) and talks to **AnkiConnect** (an add-on that lets other programs add cards to Anki) so cards get filled in automatically.
SubMiner is a desktop app that draws an interactive layer - an **overlay** - on top of the [mpv](https://mpv.io) video player. As you watch native Japanese media, you can click or hover any word in the subtitles to look it up, then turn it into an Anki flashcard without pausing to switch apps. Building flashcards from real content you're watching is called **sentence mining**, and it's what SubMiner is built for. It bundles its own copy of **Yomitan** (a pop-up dictionary) and talks to **AnkiConnect** (an add-on that lets other programs add cards to Anki) so cards get filled in automatically.
Three steps to get started:
1. **Install requirements** mpv and a few optional extras
2. **Install SubMiner** from the AUR, or download from GitHub Releases
3. **Launch the app** first-run setup walks you through dictionaries, the launcher, and everything else
1. **Install requirements** - mpv and a few optional extras
2. **Install SubMiner** - from the AUR, or download from GitHub Releases
3. **Launch the app** - first-run setup walks you through dictionaries, the launcher, and everything else
## 1. Install Requirements
@@ -29,14 +29,14 @@ Only **mpv** is strictly required to run SubMiner. Everything else enhances the
### Linux
**Window backend** you need one of these depending on your compositor:
**Window backend** - you need one of these depending on your compositor:
- **Hyprland** native Wayland support (uses `hyprctl`)
- **Sway** native Wayland support (uses `swaymsg`)
- **X11 / Xwayland** for X11 sessions or any other Wayland compositor (uses `xdotool` and `xwininfo`)
- **Hyprland** - native Wayland support (uses `hyprctl`)
- **Sway** - native Wayland support (uses `swaymsg`)
- **X11 / Xwayland** - for X11 sessions or any other Wayland compositor (uses `xdotool` and `xwininfo`)
::: warning Wayland support is compositor-specific
Wayland has no universal API for window positioning each compositor exposes its own IPC, so SubMiner needs a dedicated backend per compositor. Only Hyprland and Sway have native Wayland backends. If you run a different Wayland compositor (GNOME, KDE Plasma, river, etc.), both mpv **and** SubMiner must run under X11 or Xwayland. The `subminer` launcher handles this automatically when `--backend x11` is set or the X11 backend is auto-detected.
Wayland has no universal API for window positioning - each compositor exposes its own IPC, so SubMiner needs a dedicated backend per compositor. Only Hyprland and Sway have native Wayland backends. If you run a different Wayland compositor (GNOME, KDE Plasma, river, etc.), both mpv **and** SubMiner must run under X11 or Xwayland. The `subminer` launcher handles this automatically when `--backend x11` is set or the X11 backend is auto-detected.
:::
<details>
@@ -69,7 +69,7 @@ sudo apt install yt-dlp fzf rofi chafa ffmpegthumbnailer
sudo apt install xdotool x11-utils
# Optional: subtitle sync
pip install ffsubsync
# alass is not in apt install via cargo: cargo install alass-cli
# alass is not in apt - install via cargo: cargo install alass-cli
```
</details>
@@ -94,7 +94,7 @@ pip install ffsubsync
### macOS
macOS 11 (Big Sur) or later. Accessibility permission the macOS setting that lets one app observe and position another app's windows is required so the overlay can follow the mpv window (see [step 2](#macos-dmg)).
macOS 11 (Big Sur) or later. Accessibility permission - the macOS setting that lets one app observe and position another app's windows - is required so the overlay can follow the mpv window (see [step 2](#macos-dmg)).
```bash
brew install mpv ffmpeg
@@ -111,7 +111,7 @@ pip install ffsubsync
Windows 10 or later. Install [`mpv`](https://mpv.io/installation/) and [`ffmpeg`](https://ffmpeg.org/download.html) and ensure both are on `PATH`. Optionally install [MeCab for Windows](https://taku910.github.io/mecab/#download) with the UTF-8 dictionary.
No compositor tools or window helpers are needed native window tracking is built in.
No compositor tools or window helpers are needed - native window tracking is built in.
## 2. Install SubMiner
@@ -172,8 +172,8 @@ If you prefer to install it manually, see [manual launcher install](#manual-laun
Download the latest installer from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest):
- `SubMiner-<version>.exe` installer (recommended)
- `SubMiner-<version>-win.zip` portable fallback
- `SubMiner-<version>.exe` - installer (recommended)
- `SubMiner-<version>-win.zip` - portable fallback
Make sure `mpv.exe` is on your `PATH`, or set `mpv.executablePath` in the config during first-run setup.
@@ -240,18 +240,18 @@ subminer app --setup
# Linux (AppImage directly)
~/.local/bin/SubMiner.AppImage --setup
# macOS launch SubMiner.app from /Applications, or:
# macOS - launch SubMiner.app from /Applications, or:
subminer app --setup
```
On **Windows**, just run `SubMiner.exe` the setup wizard opens automatically on first launch.
On **Windows**, just run `SubMiner.exe` - the setup wizard opens automatically on first launch.
The setup wizard walks you through:
- **Config file** auto-created at `~/.config/SubMiner/config.jsonc` (Linux/macOS) or `%APPDATA%\SubMiner\config.jsonc` (Windows)
- **Yomitan dictionaries** import at least one dictionary so word lookups work
- **Bun + `subminer` launcher** _(optional)_ installs the command-line launcher into a writable PATH directory
- **Windows shortcut** _(Windows only)_ create a `SubMiner mpv` Start Menu/Desktop shortcut
- **Config file** - auto-created at `~/.config/SubMiner/config.jsonc` (Linux/macOS) or `%APPDATA%\SubMiner\config.jsonc` (Windows)
- **Yomitan dictionaries** - import at least one dictionary so word lookups work
- **Bun + `subminer` launcher** _(optional)_ - installs the command-line launcher into a writable PATH directory
- **Windows shortcut** _(Windows only)_ - create a `SubMiner mpv` Start Menu/Desktop shortcut
The `Finish setup` button requires a config file and at least one Yomitan dictionary. Bun and the launcher are optional and never block setup completion.
@@ -268,7 +268,7 @@ subminer video.mkv
You should see the overlay appear over mpv. If subtitles are loaded, they will appear as interactive text in the overlay.
On **Windows**, the recommended way to play video is with the **SubMiner mpv** shortcut created during setup double-click it, or drag a video file onto it.
On **Windows**, the recommended way to play video is with the **SubMiner mpv** shortcut created during setup - double-click it, or drag a video file onto it.
### Verify Setup
@@ -285,7 +285,7 @@ This checks for the app binary, mpv, ffmpeg, config file, and socket path. Fix a
If you plan to mine Anki cards:
1. Install [Anki](https://apps.ankiweb.net/)
2. Install [AnkiConnect](https://ankiweb.net/shared/info/2055492159) open Anki → **Tools → Add-ons → Get Add-ons** → enter code `2055492159`
2. Install [AnkiConnect](https://ankiweb.net/shared/info/2055492159) - open Anki → **Tools → Add-ons → Get Add-ons** → enter code `2055492159`
3. Restart Anki and keep it running while using SubMiner
AnkiConnect listens on `http://127.0.0.1:8765` by default. SubMiner connects automatically with no extra config needed.
@@ -310,9 +310,9 @@ The tray "Check for Updates" entry installs the new app automatically on Linux,
SubMiner is an overlay that sits on top of mpv. It connects to mpv through an IPC socket, renders subtitles as interactive text using a bundled Yomitan dictionary engine, and optionally creates Anki flashcards via AnkiConnect.
The `subminer` launcher handles mpv IPC socket setup automatically. If you launch mpv yourself or from another tool, you must pass `--input-ipc-server=/tmp/subminer-socket` (or `\\.\pipe\subminer-socket` on Windows) without it the overlay starts but subtitles won't appear.
The `subminer` launcher handles mpv IPC socket setup automatically. If you launch mpv yourself or from another tool, you must pass `--input-ipc-server=/tmp/subminer-socket` (or `\\.\pipe\subminer-socket` on Windows) - without it the overlay starts but subtitles won't appear.
The bundled mpv plugin is injected at runtime automatically you don't need to install it separately. It provides in-player keybindings (the `y` chord) for controlling the overlay from within mpv. See [MPV Plugin](/mpv-plugin) for the full keybinding and configuration reference.
The bundled mpv plugin is injected at runtime automatically - you don't need to install it separately. It provides in-player keybindings (the `y` chord) for controlling the overlay from within mpv. See [MPV Plugin](/mpv-plugin) for the full keybinding and configuration reference.
## Platform Notes
@@ -330,7 +330,7 @@ Ensure `mecab` is available on your PATH when launching SubMiner.
- The **SubMiner mpv** shortcut is the recommended way to launch playback. It starts `mpv.exe` with the right IPC socket and subtitle defaults.
- First-run setup adds only `%LOCALAPPDATA%\SubMiner\bin` to the HKCU user PATH. It does not add `SubMiner.exe` to PATH.
- IPC socket on Windows is `\\.\pipe\subminer-socket` do not use `/tmp/subminer-socket`.
- IPC socket on Windows is `\\.\pipe\subminer-socket` - do not use `/tmp/subminer-socket`.
- Config is stored at `%APPDATA%\SubMiner\config.jsonc`.
## Manual Launcher Install
@@ -374,4 +374,4 @@ cp /tmp/assets/themes/subminer.rasi ~/.local/share/SubMiner/themes/subminer.rasi
Override with `SUBMINER_ROFI_THEME=/absolute/path/to/theme.rasi`.
Next: [Usage](/usage) learn about the `subminer` wrapper, keybindings, and YouTube playback.
Next: [Usage](/usage) - learn about the `subminer` wrapper, keybindings, and YouTube playback.
+9 -9
View File
@@ -1,8 +1,8 @@
# IPC + Runtime Contracts
SubMiner's Electron app runs two isolated processes main and renderer that can only communicate through IPC channels. This boundary is intentional: the renderer is an untrusted surface (it loads Yomitan, renders user-controlled subtitle text, and runs in a Chromium sandbox), so every message crossing the bridge passes through a validation layer before it can reach domain logic.
SubMiner's Electron app runs two isolated processes - main and renderer - that can only communicate through IPC channels. This boundary is intentional: the renderer is an untrusted surface (it loads Yomitan, renders user-controlled subtitle text, and runs in a Chromium sandbox), so every message crossing the bridge passes through a validation layer before it can reach domain logic.
The contract system enforces this by making channel names, payload shapes, and validators co-located and co-evolved. A change to any IPC surface touches the contract, the validator, the preload bridge, and the handler in the same commit drift between any of those layers is treated as a bug.
The contract system enforces this by making channel names, payload shapes, and validators co-located and co-evolved. A change to any IPC surface touches the contract, the validator, the preload bridge, and the handler in the same commit - drift between any of those layers is treated as a bug.
## Message Flow
@@ -38,7 +38,7 @@ flowchart TB
## Runtime Sockets
The renderer↔main bridge above lives *inside* the Electron app. A separate set of OS sockets connects the app to the other runtimes mpv and the launcher/plugin. These carry no renderer payloads and bypass the contract/validator layer; they are command and property channels between processes.
The renderer↔main bridge above lives *inside* the Electron app. A separate set of OS sockets connects the app to the other runtimes - mpv and the launcher/plugin. These carry no renderer payloads and bypass the contract/validator layer; they are command and property channels between processes.
- **mpv IPC socket** (`/tmp/subminer-socket`, or `\\.\pipe\subminer-socket` on Windows): the `MpvIpcClient` in the main process connects here to send JSON commands and subscribe to playback/subtitle properties via `observe_property`. Created by mpv's `--input-ipc-server`.
- **App control socket** (`/tmp/subminer-control-<uid>-<hash>.sock`, or a named pipe on Windows): the launcher and the mpv plugin send CLI-style commands (`--start`, `--show-visible-overlay`, `--texthooker`) to a running app here. It also dedupes a second `subminer` invocation into the existing instance instead of launching twice.
@@ -73,7 +73,7 @@ How these sockets are established during launch is covered in [Playback Startup
| --- | --- |
| `src/shared/ipc/contracts.ts` | Canonical channel names and payload type contracts. Single source of truth for both processes. |
| `src/shared/ipc/validators.ts` | Runtime payload parsers and type guards. Every `invoke` payload is validated here before the handler runs. |
| `src/preload.ts` | Renderer-side bridge. Exposes a typed API surface to the renderer only approved channels are accessible. |
| `src/preload.ts` | Renderer-side bridge. Exposes a typed API surface to the renderer - only approved channels are accessible. |
| `src/main/ipc-runtime.ts` | Main-process handler registration and routing. Wires validated channels to domain handlers. |
| `src/core/services/ipc.ts` | Service-level invoke handling. Applies guardrails (validation, error wrapping) before calling domain logic. |
| `src/core/services/anki-jimaku-ipc.ts` | Integration-specific IPC boundary for Anki and Jimaku operations. |
@@ -81,19 +81,19 @@ How these sockets are established during launch is covered in [Playback Startup
## Contract Rules
These rules exist to prevent a class of bugs where the renderer and main process silently disagree about message shapes which surfaces as undefined fields, swallowed errors, or state corruption.
These rules exist to prevent a class of bugs where the renderer and main process silently disagree about message shapes - which surfaces as undefined fields, swallowed errors, or state corruption.
- **Use shared constants.** Channel names come from `contracts.ts`, never ad-hoc literal strings. This makes channels greppable and refactor-safe.
- **Validate before handling.** Every `invoke` payload passes through `validators.ts` before reaching domain logic. This catches shape drift at the boundary instead of deep inside a service.
- **Return structured failures.** Handlers return `{ ok: false, error: string }` on failure rather than throwing. The renderer can always distinguish success from failure without try/catch.
- **Keep payloads narrow.** Send only what the handler needs. Avoid passing entire state objects across the bridge it couples the renderer to internal main-process structure.
- **Keep payloads narrow.** Send only what the handler needs. Avoid passing entire state objects across the bridge - it couples the renderer to internal main-process structure.
- **Co-evolve all layers.** When a payload shape changes, update `contracts.ts`, `validators.ts`, `preload.ts`, and the handler in the same commit. Partial updates are treated as bugs.
## Two Message Patterns
**Invoke (request/response):** The renderer calls a typed bridge method and awaits a result. The main process validates the payload, runs the handler, and returns a structured response. Used for operations where the renderer needs a result lookups, config reads, mining actions.
**Invoke (request/response):** The renderer calls a typed bridge method and awaits a result. The main process validates the payload, runs the handler, and returns a structured response. Used for operations where the renderer needs a result - lookups, config reads, mining actions.
**Fire-and-forget (send):** The renderer sends a message with no response. The main process validates and handles it silently. Malformed payloads are dropped. Used for notifications where the renderer doesn't need confirmation UI state hints, focus events, position updates.
**Fire-and-forget (send):** The renderer sends a message with no response. The main process validates and handles it silently. Malformed payloads are dropped. Used for notifications where the renderer doesn't need confirmation - UI state hints, focus events, position updates.
## Add a New IPC Action
@@ -108,7 +108,7 @@ These rules exist to prevent a class of bugs where the renderer and main process
- Prefer runtime/domain composition via `src/main/runtime/composers/*` and `src/main/runtime/domains/*`. IPC handlers should delegate to composers rather than containing orchestration logic.
- Route shared mutable state updates through transition helpers in `src/main/state.ts` for migrated domains. Direct mutation from IPC handlers bypasses invariant checks.
- Keep IPC handlers thin they validate, delegate, and return. Business logic belongs in services.
- Keep IPC handlers thin - they validate, delegate, and return. Business logic belongs in services.
## Troubleshooting
+10 -10
View File
@@ -1,12 +1,12 @@
# Jellyfin Integration
[Jellyfin](https://jellyfin.org) is a free, self-hosted media server think of it as your own private streaming service for video you own. If you keep your anime on a Jellyfin server, SubMiner can play episodes through mpv with the full mining overlay.
[Jellyfin](https://jellyfin.org) is a free, self-hosted media server - think of it as your own private streaming service for video you own. If you keep your anime on a Jellyfin server, SubMiner can play episodes through mpv with the full mining overlay.
::: tip Who needs this?
This page is only relevant if you already run (or have access to) a Jellyfin server. If you watch local files or YouTube, you can skip it. The in-app setup window (`subminer jellyfin`) is the easiest starting point.
:::
SubMiner can act as a **cast-to-device target** for Jellyfin (similar to jellyfin-mpv-shim). Sign in once, turn on discovery, and SubMiner shows up in the "Play on…" / cast menu of any Jellyfin app web, phone, or TV. Pick an episode, cast it to SubMiner, and it plays in SubMiner's mpv window with the full overlay and Yomitan click-to-lookup.
SubMiner can act as a **cast-to-device target** for Jellyfin (similar to jellyfin-mpv-shim). Sign in once, turn on discovery, and SubMiner shows up in the "Play on…" / cast menu of any Jellyfin app - web, phone, or TV. Pick an episode, cast it to SubMiner, and it plays in SubMiner's mpv window with the full overlay and Yomitan click-to-lookup.
This is the recommended way to use Jellyfin with SubMiner. A terminal-only option is covered in [Launcher playback](#launcher-playback) at the end.
@@ -28,7 +28,7 @@ Open the tray menu and click **Configure Jellyfin**. In the window that opens, e
On success, SubMiner:
- saves an encrypted session token your password is never stored,
- saves an encrypted session token - your password is never stored,
- turns the Jellyfin integration on, and
- remembers the server and username for next time.
@@ -38,12 +38,12 @@ Reopen this window any time to switch servers or **Logout**.
Discovery is what makes SubMiner appear as a cast target. Two ways to enable it:
- **For the current session** open the tray menu and tick **Jellyfin Discovery**. (This item appears once you've signed in.)
- **Automatically on every launch** already on by default. After your first sign-in, SubMiner auto-connects to Jellyfin at startup, so the cast target is ready without touching the tray. You can change this under [Settings](#settings).
- **For the current session** - open the tray menu and tick **Jellyfin Discovery**. (This item appears once you've signed in.)
- **Automatically on every launch** - already on by default. After your first sign-in, SubMiner auto-connects to Jellyfin at startup, so the cast target is ready without touching the tray. You can change this under [Settings](#settings).
### 4. Cast from any Jellyfin app
In the Jellyfin web UI or mobile app, start playing something, open the **cast / "Play on"** menu, and pick your device SubMiner appears there named after your computer's hostname. Playback opens in SubMiner.
In the Jellyfin web UI or mobile app, start playing something, open the **cast / "Play on"** menu, and pick your device - SubMiner appears there named after your computer's hostname. Playback opens in SubMiner.
From then on, pause / resume / seek / stop and audio or subtitle track changes you make in the Jellyfin app are mirrored in SubMiner, and your watch progress syncs back to Jellyfin (now-playing and resume position).
@@ -63,7 +63,7 @@ All Jellyfin options live under **Settings → Integrations → Jellyfin** (open
| Setting | Default | What it does |
| ------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
| **Enabled** | Off | Turns the Jellyfin integration on. Switched on for you when you sign in. |
| **Server Url** | | Your Jellyfin server. Filled in when you sign in. |
| **Server Url** | - | Your Jellyfin server. Filled in when you sign in. |
| **Remote Control Enabled** | On | Lets SubMiner act as a cast target. |
| **Remote Control Auto Connect** | On | Connects to Jellyfin at startup so discovery is automatic. Turn off if you'd rather start it from the tray each time. |
| **Auto Announce** | Off | Re-broadcasts visibility on connect. Enable if your device is slow to appear in the cast menu. |
@@ -88,14 +88,14 @@ See [Configuration](/configuration) for the full list (transcode codec, direct-p
**SubMiner doesn't appear in the cast menu**
- Make sure SubMiner is running.
- Make sure you're signed in reopen **Configure Jellyfin** and log in again if your token expired.
- Make sure you're signed in - reopen **Configure Jellyfin** and log in again if your token expired.
- Make sure discovery is on (tray **Jellyfin Discovery**, or **Remote Control Auto Connect** in settings).
- Make sure SubMiner and the Jellyfin client point at the same server.
**Casting starts but nothing plays**
- Confirm the item plays normally in another Jellyfin client.
- If mpv was closed, give it a moment SubMiner launches it on demand and retries.
- If mpv was closed, give it a moment - SubMiner launches it on demand and retries.
**SubMiner keeps disconnecting**
@@ -104,7 +104,7 @@ See [Configuration](/configuration) for the full list (transcode codec, direct-p
## Security notes
- The Jellyfin session (access token + user ID) is kept in SubMiner's local encrypted token storage. Your password is used only to log in and is never saved.
- Treat the token storage and your `config.jsonc` as secrets don't commit them.
- Treat the token storage and your `config.jsonc` as secrets - don't commit them.
- Advanced/headless: the `SUBMINER_JELLYFIN_ACCESS_TOKEN` and `SUBMINER_JELLYFIN_USER_ID` environment variables can supply a session without the sign-in window.
## Launcher playback
+16 -16
View File
@@ -1,6 +1,6 @@
# Jimaku Integration
[Jimaku](https://jimaku.cc) is a community-driven subtitle repository for anime a shared online library of subtitle files contributed by other learners. SubMiner integrates with the Jimaku API so you can search, browse, and download Japanese subtitle files directly from the overlay no alt-tabbing or manual file management required. Downloaded subtitles are loaded into mpv immediately.
[Jimaku](https://jimaku.cc) is a community-driven subtitle repository for anime - a shared online library of subtitle files contributed by other learners. SubMiner integrates with the Jimaku API so you can search, browse, and download Japanese subtitle files directly from the overlay - no alt-tabbing or manual file management required. Downloaded subtitles are loaded into mpv immediately.
::: tip Prerequisite: a free API key
You need a Jimaku account and an API key (a personal access string) before this feature works. Create an account at [jimaku.cc](https://jimaku.cc), copy your key, and add it to your config as shown under [Configuration](#configuration) below. Without a key, the search modal will report "Jimaku API key not set."
@@ -10,14 +10,14 @@ You need a Jimaku account and an API key (a personal access string) before this
The Jimaku integration runs through an in-overlay modal accessible via a keyboard shortcut (`Ctrl+Shift+J` by default).
When you open the modal, SubMiner parses the current video filename to extract a title, season, and episode number. Common naming conventions are supported `S01E03`, `1x03`, `E03`, and dash-separated episode numbers all work. If the filename yields a high-confidence match (title + episode), SubMiner auto-searches immediately.
When you open the modal, SubMiner parses the current video filename to extract a title, season, and episode number. Common naming conventions are supported - `S01E03`, `1x03`, `E03`, and dash-separated episode numbers all work. If the filename yields a high-confidence match (title + episode), SubMiner auto-searches immediately.
From there:
1. **Search** SubMiner queries the Jimaku API with the parsed title. Results appear as a list of anime entries (Japanese and English names).
2. **Browse entries** Select an entry to load its available subtitle files, filtered by episode if one was detected.
3. **Browse files** Files show name, size, and last-modified date. If a language preference is configured, files are sorted accordingly (e.g., Japanese-tagged files first).
4. **Download** Selecting a file downloads it to the same directory as the video (or a temp directory for remote/streamed media) and loads it into mpv as a new subtitle track.
1. **Search** - SubMiner queries the Jimaku API with the parsed title. Results appear as a list of anime entries (Japanese and English names).
2. **Browse entries** - Select an entry to load its available subtitle files, filtered by episode if one was detected.
3. **Browse files** - Files show name, size, and last-modified date. If a language preference is configured, files are sorted accordingly (e.g., Japanese-tagged files first).
4. **Download** - Selecting a file downloads it to the same directory as the video (or a temp directory for remote/streamed media) and loads it into mpv as a new subtitle track.
If no files match the current episode filter, a "Show all files" button lets you broaden the search to all episodes for that entry.
@@ -48,8 +48,8 @@ Add a `jimaku` section to your `config.jsonc`:
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `jimaku.apiKey` | `string` | | Jimaku API key (plaintext). Mutually exclusive with `apiKeyCommand`. |
| `jimaku.apiKeyCommand` | `string` | | Shell command that prints the API key to stdout. Useful for secret managers (e.g., `pass jimaku/api-key`). |
| `jimaku.apiKey` | `string` | - | Jimaku API key (plaintext). Mutually exclusive with `apiKeyCommand`. |
| `jimaku.apiKeyCommand` | `string` | - | Shell command that prints the API key to stdout. Useful for secret managers (e.g., `pass jimaku/api-key`). |
| `jimaku.apiBaseUrl` | `string` | `"https://jimaku.cc"` | Base URL for the Jimaku API. Only change this if using a mirror or local instance. |
| `jimaku.languagePreference` | `"ja"` \| `"en"` \| `"none"` | `"ja"` | Sort subtitle files by language tag. `"ja"` pushes Japanese-tagged files to the top; `"en"` does the same for English. `"none"` preserves the API order. |
| `jimaku.maxEntryResults` | `number` | `10` | Maximum number of anime entries returned per search. |
@@ -68,8 +68,8 @@ The keyboard shortcut is configured separately under `shortcuts`:
An API key is required to use the Jimaku integration. You can get one from [jimaku.cc](https://jimaku.cc). There are two ways to provide it:
- **`apiKey`** set the key directly in config. Simple, but the key is stored in plaintext.
- **`apiKeyCommand`** a shell command that outputs the key. Runs with a 10-second timeout. Preferred if you use a secret manager like `pass`, `gpg`, or a keychain tool.
- **`apiKey`** - set the key directly in config. Simple, but the key is stored in plaintext.
- **`apiKeyCommand`** - a shell command that outputs the key. Runs with a 10-second timeout. Preferred if you use a secret manager like `pass`, `gpg`, or a keychain tool.
If both are set, `apiKey` takes priority.
@@ -79,8 +79,8 @@ SubMiner extracts media info from the current video path to pre-fill the search
- **Season + episode patterns:** `S01E03`, `1x03`
- **Episode-only patterns:** `E03`, `EP03`, or dash-separated numbers like `Title - 03 -`
- **Bracket tags:** `[SubGroup]`, `[1080p]`, `[HEVC]` stripped before title extraction
- **Year tags:** `(2024)` stripped
- **Bracket tags:** `[SubGroup]`, `[1080p]`, `[HEVC]` - stripped before title extraction
- **Year tags:** `(2024)` - stripped
- **Dots and underscores:** treated as spaces
- **Remote/streamed URLs:** SubMiner checks URL query parameters (`title`, `name`, `q`) and path segments to extract a meaningful title
@@ -98,7 +98,7 @@ The Jimaku API has rate limits. If you see 429 errors, wait for the retry durati
**No entries found**
Try simplifying the title remove season/episode qualifiers and search with just the anime name. Jimaku's search matches against its own database of anime titles, so the exact spelling matters.
Try simplifying the title - remove season/episode qualifiers and search with just the anime name. Jimaku's search matches against its own database of anime titles, so the exact spelling matters.
**No files found for this episode**
@@ -110,6 +110,6 @@ Verify mpv is running and connected via IPC. SubMiner loads the subtitle by issu
## Related
- [Configuration Reference](/configuration#jimaku) full config options
- [Mining Workflow](/mining-workflow#jimaku-subtitle-search) how Jimaku fits into the sentence mining loop
- [Troubleshooting](/troubleshooting#jimaku) additional error guidance
- [Configuration Reference](/configuration#jimaku) - full config options
- [Mining Workflow](/mining-workflow#jimaku-subtitle-search) - how Jimaku fits into the sentence mining loop
- [Troubleshooting](/troubleshooting#jimaku) - additional error guidance
+2 -2
View File
@@ -3,7 +3,7 @@
The `subminer` launcher is an all-in-one script that handles video selection, mpv startup, and overlay management. It is the recommended way to use SubMiner on Linux and macOS because it guarantees mpv is launched with the correct IPC socket and SubMiner defaults. It's a Bun script distributed as a release asset alongside the AppImage and DMG.
::: tip Windows users
On Windows, the recommended way to launch playback is the **SubMiner mpv** shortcut created during first-run setup double-click it, drag a file onto it, or run `SubMiner.exe --launch-mpv` from a terminal. See [Windows mpv Shortcut](/usage#windows-mpv-shortcut) for details.
On Windows, the recommended way to launch playback is the **SubMiner mpv** shortcut created during first-run setup - double-click it, drag a file onto it, or run `SubMiner.exe --launch-mpv` from a terminal. See [Windows mpv Shortcut](/usage#windows-mpv-shortcut) for details.
:::
## Video Picker
@@ -121,4 +121,4 @@ With default plugin settings (`auto_start=yes`, `auto_start_visible_overlay=yes`
- 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
- `--dev` / `--debug` control app behavior, not logging verbosity - use `--log-level` for that
+29 -29
View File
@@ -1,14 +1,14 @@
# Mining Workflow
This guide walks through the sentence mining loop from watching a video to creating Anki cards with audio, screenshots, and context.
This guide walks through the sentence mining loop - from watching a video to creating Anki cards with audio, screenshots, and context.
## Overview
*Sentence mining* means turning real sentences you encounter while watching native video into Anki flashcards, so you learn vocabulary in the context where you actually met it. SubMiner automates the tedious parts of that loop.
SubMiner runs as a transparent overlay on top of mpv (the video player). As subtitles play, the overlay displays them as interactive text. You hover a word, trigger a Yomitan dictionary lookup with your configured lookup key/modifier, then create an Anki card with a single action. SubMiner automatically attaches the sentence, an audio clip, and a screenshot to that card no manual copy-pasting or screen capturing.
SubMiner runs as a transparent overlay on top of mpv (the video player). As subtitles play, the overlay displays them as interactive text. You hover a word, trigger a Yomitan dictionary lookup with your configured lookup key/modifier, then create an Anki card with a single action. SubMiner automatically attaches the sentence, an audio clip, and a screenshot to that card - no manual copy-pasting or screen capturing.
> **Yomitan** is the popup dictionary that shows definitions when you hover or scan a word. **AnkiConnect** is the add-on that lets SubMiner talk to Anki. Both are set up during installation see [Anki Integration](/anki-integration) if you have not configured them yet.
> **Yomitan** is the popup dictionary that shows definitions when you hover or scan a word. **AnkiConnect** is the add-on that lets SubMiner talk to Anki. Both are set up during installation - see [Anki Integration](/anki-integration) if you have not configured them yet.
## Creating Anki Cards
@@ -39,7 +39,7 @@ If you prefer a hands-on approach (animecards-style), you can copy the current s
1. Add a word via Yomitan as usual.
2. Press `Ctrl/Cmd+C` to copy the current subtitle line to the clipboard.
- 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.
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.
Manual clipboard updates always replace generated sentence audio, even when `ankiConnect.behavior.overwriteAudio` is disabled. The word audio field is left unchanged because the word itself does not change in this flow.
@@ -61,7 +61,7 @@ Create a standalone sentence card without going through Yomitan:
The sentence card uses the note type configured in `isLapis.sentenceCardModel` and always maps sentence/audio to `Sentence` and `SentenceAudio`.
::: warning Requires Lapis/Kiku note type
Sentence card creation requires a [Lapis](https://github.com/donkuri/lapis) or [Kiku](https://github.com/youyoumu/kiku) compatible note type and `ankiConnect.isLapis.enabled: true` in your config. See [Anki Integration Sentence Cards](/anki-integration#sentence-cards-lapis) for setup.
Sentence card creation requires a [Lapis](https://github.com/donkuri/lapis) or [Kiku](https://github.com/youyoumu/kiku) compatible note type and `ankiConnect.isLapis.enabled: true` in your config. See [Anki Integration - Sentence Cards](/anki-integration#sentence-cards-lapis) for setup.
:::
### 4. Mark as Audio Card
@@ -69,7 +69,7 @@ Sentence card creation requires a [Lapis](https://github.com/donkuri/lapis) or [
After adding a word via Yomitan, press the audio card shortcut to overwrite the audio with a longer clip spanning the full subtitle timing.
::: warning Requires Lapis/Kiku note type
Audio card marking requires a [Lapis](https://github.com/donkuri/lapis) or [Kiku](https://github.com/youyoumu/kiku) compatible note type and `ankiConnect.isLapis.enabled: true` in your config. See [Anki Integration Sentence Cards](/anki-integration#sentence-cards-lapis) for setup.
Audio card marking requires a [Lapis](https://github.com/donkuri/lapis) or [Kiku](https://github.com/youyoumu/kiku) compatible note type and `ankiConnect.isLapis.enabled: true` in your config. See [Anki Integration - Sentence Cards](/anki-integration#sentence-cards-lapis) for setup.
:::
### Field Grouping (Kiku)
@@ -82,11 +82,11 @@ If you mine the same word from different sentences, SubMiner can merge the cards
- **Auto mode** (`ankiConnect.isKiku.fieldGrouping: "auto"`): Merges automatically. Both sentences, audio clips, and images are combined into the existing card. The duplicate is optionally deleted.
- **Manual mode** (`ankiConnect.isKiku.fieldGrouping: "manual"`): A modal appears showing both cards side by side. You choose which card to keep and preview the merged result before confirming.
See [Anki Integration Field Grouping](/anki-integration#field-grouping-kiku) for configuration options, merge behavior, and modal keyboard shortcuts.
See [Anki Integration - Field Grouping](/anki-integration#field-grouping-kiku) for configuration options, merge behavior, and modal keyboard shortcuts.
## Overlay Model
SubMiner uses one overlay window with modal surfaces. It carries two subtitle bars a primary reading bar and a secondary translation/context bar plus modal dialogs that open on top.
SubMiner uses one overlay window with modal surfaces. It carries two subtitle bars - a primary reading bar and a secondary translation/context bar - plus modal dialogs that open on top.
Toggle the entire overlay window with `Alt+Shift+O` (global) or `y-t` (mpv plugin).
@@ -99,14 +99,14 @@ The primary bar renders subtitles as tokenized hoverable word spans. Each word i
- Auto pause/resume while the Yomitan popup is open (enabled by default via `subtitleStyle.autoPauseVideoOnYomitanPopup`)
- Right-click to pause/resume
- Right-click + drag to reposition subtitles
- **Reading annotations** known words, N+1 targets, character-name matches, JLPT levels, and frequency hits can all be visually highlighted
- **Reading annotations** - known words, N+1 targets, character-name matches, JLPT levels, and frequency hits can all be visually highlighted
### Secondary Subtitle Bar
The secondary bar is a compact top-strip region in the same overlay window. It shows a secondary subtitle track (typically English) for translation/context while keeping the primary reading flow below. It is useful for:
- Quick comprehension checks without leaving the mining flow.
- Auto-populating the translation field on mined cards when a card is created, SubMiner uses the secondary subtitle text as the translation field value (unless AI translation is configured to override it).
- Auto-populating the translation field on mined cards - when a card is created, SubMiner uses the secondary subtitle text as the translation field value (unless AI translation is configured to override it).
It is controlled by `secondarySub` configuration and shares its lifecycle with the main overlay window. Cycle which track feeds it with `Shift+J`.
@@ -114,9 +114,9 @@ It is controlled by `secondarySub` configuration and shares its lifecycle with t
Both the primary and secondary subtitle bars share the same three visibility modes, and each can be changed independently at runtime:
- **Hidden** the bar is not shown.
- **Visible** the bar is always shown.
- **Hover** the bar is revealed only while you hover over the overlay.
- **Hidden** - the bar is not shown.
- **Visible** - the bar is always shown.
- **Hover** - the bar is revealed only while you hover over the overlay.
By default the **primary** bar is `visible` (`subtitleStyle.primaryDefaultMode`) and the **secondary** bar is `hover` (`secondarySub.defaultMode`).
@@ -133,7 +133,7 @@ Jimaku search, field-grouping, runtime options, and manual subsync open as modal
## Looking Up Words
1. Hover over the subtitle area the overlay activates pointer events.
1. Hover over the subtitle area - the overlay activates pointer events.
2. Hover the word you want. SubMiner keeps per-token boundaries so Yomitan can target that token cleanly.
3. Trigger Yomitan lookup with your configured lookup key/modifier (for example `Shift` if that is how your Yomitan profile is set up).
4. Yomitan opens its lookup popup for the hovered token.
@@ -143,17 +143,17 @@ Jimaku search, field-grouping, runtime options, and manual subsync open as modal
With a gamepad connected and keyboard-only mode enabled, the full mining loop works without a mouse or keyboard:
1. **Navigate** push the left stick left/right to move the token highlight across subtitle words.
2. **Look up** press `A` to trigger Yomitan lookup on the highlighted word.
3. **Browse the popup** push the left stick up/down to smooth-scroll through the Yomitan popup, or use the right stick for larger jumps.
4. **Cycle audio** press `R1` to move to the next dictionary audio entry, `L1` to play the current one.
5. **Mine** press `X` to create an Anki card for the current sentence (same as `Ctrl+S`).
6. **Close** press `B` to dismiss the Yomitan popup and return to subtitle navigation.
7. **Pause/resume** press `L3` (left stick click) to toggle mpv pause at any time.
1. **Navigate** - push the left stick left/right to move the token highlight across subtitle words.
2. **Look up** - press `A` to trigger Yomitan lookup on the highlighted word.
3. **Browse the popup** - push the left stick up/down to smooth-scroll through the Yomitan popup, or use the right stick for larger jumps.
4. **Cycle audio** - press `R1` to move to the next dictionary audio entry, `L1` to play the current one.
5. **Mine** - press `X` to create an Anki card for the current sentence (same as `Ctrl+S`).
6. **Close** - press `B` to dismiss the Yomitan popup and return to subtitle navigation.
7. **Pause/resume** - press `L3` (left stick click) to toggle mpv pause at any time.
After controller support is enabled, the controller and keyboard can be used interchangeably switching mid-session is seamless. Toggle keyboard-only mode on or off with `Y` on the controller.
After controller support is enabled, the controller and keyboard can be used interchangeably - switching mid-session is seamless. Toggle keyboard-only mode on or off with `Y` on the controller.
See [Usage Controller Support](/usage#controller-support) for setup details and [Configuration Controller Support](/configuration#controller-support) for the full mapping and tuning options.
See [Usage - Controller Support](/usage#controller-support) for setup details and [Configuration - Controller Support](/configuration#controller-support) for the full mapping and tuning options.
## Subtitle Sync (Subsync)
@@ -166,11 +166,11 @@ If your subtitle file is out of sync with the audio, SubMiner can resynchronize
For remote streams, including Jellyfin playback, the modal only offers alass. Jellyfin subtitle URLs are cached as temporary subtitle files so alass can read them, but the video stream is not downloaded. ffsubsync needs direct access to the local media file and is unavailable for stream URLs.
Install the sync tools separately see [Troubleshooting](/troubleshooting#subtitle-sync-subsync) if the tools are not found.
Install the sync tools separately - see [Troubleshooting](/troubleshooting#subtitle-sync-subsync) if the tools are not found.
## Texthooker
SubMiner runs a local HTTP server at `http://127.0.0.1:5174` (configurable port) that serves a texthooker UI. This allows external tools such as a browser-based Yomitan instance to receive subtitle text in real time.
SubMiner runs a local HTTP server at `http://127.0.0.1:5174` (configurable port) that serves a texthooker UI. This allows external tools - such as a browser-based Yomitan instance - to receive subtitle text in real time.
The texthooker page displays the current subtitle and updates as new lines arrive. This is useful if you prefer to do lookups in a browser rather than through the overlay's built-in Yomitan.
@@ -180,8 +180,8 @@ If you want to build your own browser client, websocket consumer, or automation
These features support the mining loop but have their own dedicated pages:
- **[Jimaku subtitle search](/jimaku-integration)** search and download anime subtitle files directly from the overlay (`Ctrl+Shift+J` by default), then load them into mpv.
- **[N+1 word highlighting](/subtitle-annotations#n1-word-highlighting)** cross-reference your Anki decks to highlight known words, making true N+1 sentences (exactly one unknown word) easy to spot during immersion.
- **[Immersion tracking](/immersion-tracking)** log watching and mining activity to a local database and view session times, words seen, and cards mined in the built-in stats dashboard.
- **[Jimaku subtitle search](/jimaku-integration)** - search and download anime subtitle files directly from the overlay (`Ctrl+Shift+J` by default), then load them into mpv.
- **[N+1 word highlighting](/subtitle-annotations#n1-word-highlighting)** - cross-reference your Anki decks to highlight known words, making true N+1 sentences (exactly one unknown word) easy to spot during immersion.
- **[Immersion tracking](/immersion-tracking)** - log watching and mining activity to a local database and view session times, words seen, and cards mined in the built-in stats dashboard.
Next: [Anki Integration](/anki-integration) field mapping, media generation, and card enrichment configuration.
Next: [Anki Integration](/anki-integration) - field mapping, media generation, and card enrichment configuration.
+13 -13
View File
@@ -2,7 +2,7 @@
**What this is:** mpv is the video player SubMiner overlays subtitles on. The SubMiner mpv plugin is a small Lua script that runs *inside* mpv and gives you in-player keybindings to control the SubMiner overlay (start/stop/toggle, skip intro, etc.) without leaving the player window.
**Who needs this page:** Most users never touch the plugin directly SubMiner-managed launches (the app, the `subminer` launcher, or the Windows shortcut) inject the bundled plugin automatically for that session, so there is nothing to install into mpv's global `scripts` directory. Read on if you launch mpv from another tool and want SubMiner's in-player controls, or you want to script mpv against SubMiner.
**Who needs this page:** Most users never touch the plugin directly - SubMiner-managed launches (the app, the `subminer` launcher, or the Windows shortcut) inject the bundled plugin automatically for that session, so there is nothing to install into mpv's global `scripts` directory. Read on if you launch mpv from another tool and want SubMiner's in-player controls, or you want to script mpv against SubMiner.
The plugin ships as a modular Lua package under `plugin/subminer/` (entry point `init.lua`, which loads `main.lua` and sibling modules). Earlier releases shipped a single global `main.lua`; runtime loading replaces it.
@@ -27,7 +27,7 @@ input-ipc-server=\\.\pipe\subminer-socket
## Keybindings
Most plugin actions use a `y` chord prefix press `y`, then the second key (a "chord"):
Most plugin actions use a `y` chord prefix - press `y`, then the second key (a "chord"):
| Chord | Action |
| ---------------- | -------------------------------------- |
@@ -48,7 +48,7 @@ The bare `v` binding is a forced mpv binding. It overrides mpv's default primary
## Shared Shortcuts (Session Bindings)
The `y-*` chords above are built into the plugin. Everything else you configure under [`shortcuts.*`](/shortcuts) plus any custom [`keybindings`](/configuration) and the stats toggle/mark-watched keys is **injected into mpv at runtime**, so the same shortcut works both inside mpv and in the SubMiner overlay. You do not edit any mpv config to enable them.
The `y-*` chords above are built into the plugin. Everything else you configure under [`shortcuts.*`](/shortcuts) - plus any custom [`keybindings`](/configuration) and the stats toggle/mark-watched keys - is **injected into mpv at runtime**, so the same shortcut works both inside mpv and in the SubMiner overlay. You do not edit any mpv config to enable them.
How it works:
@@ -58,11 +58,11 @@ How it works:
Because the bindings come from the same configuration the overlay uses, you maintain one set of shortcuts for both surfaces.
Live updates: changing a shortcut in the app rewrites `session-bindings.json` and sends the plugin a `subminer-reload-session-bindings` script message, so mpv re-registers the bindings immediately no mpv restart required.
Live updates: changing a shortcut in the app rewrites `session-bindings.json` and sends the plugin a `subminer-reload-session-bindings` script message, so mpv re-registers the bindings immediately - no mpv restart required.
Notes:
- Accelerators are normalized per platform `CommandOrControl` resolves to `Cmd` on macOS and `Ctrl` elsewhere.
- Accelerators are normalized per platform - `CommandOrControl` resolves to `Cmd` on macOS and `Ctrl` elsewhere.
- Multi-line actions (`copySubtitleMultiple`, `mineSentenceMultiple`) register temporary `1``9` digit follow-up bindings after the trigger key, with `Esc` to cancel.
- If two shortcuts compile to the same key, or an accelerator can't be mapped to an mpv key, the app logs a warning and skips that binding instead of registering a broken one.
@@ -110,14 +110,14 @@ Packaged Windows plugin installs also rewrite `socket_path` to `\\.\pipe\submine
When `backend=auto`, the plugin detects the window manager:
1. **macOS** detected via platform or `OSTYPE`.
2. **Hyprland** detected via `HYPRLAND_INSTANCE_SIGNATURE`.
3. **Sway** detected via `SWAYSOCK`.
4. **X11** detected via `XDG_SESSION_TYPE=x11` or `DISPLAY`.
5. **Fallback** defaults to X11 with a warning.
1. **macOS** - detected via platform or `OSTYPE`.
2. **Hyprland** - detected via `HYPRLAND_INSTANCE_SIGNATURE`.
3. **Sway** - detected via `SWAYSOCK`.
4. **X11** - detected via `XDG_SESSION_TYPE=x11` or `DISPLAY`.
5. **Fallback** - defaults to X11 with a warning.
::: tip Wayland is compositor-specific
Native Wayland support is only available for Hyprland and Sway. If you use a different Wayland compositor, auto-detection will fall back to X11 both mpv and SubMiner must be running under Xwayland, and `xdotool` and `xwininfo` must be installed.
Native Wayland support is only available for Hyprland and Sway. If you use a different Wayland compositor, auto-detection will fall back to X11 - both mpv and SubMiner must be running under Xwayland, and `xdotool` and `xwininfo` must be installed.
:::
## Script Messages
@@ -163,7 +163,7 @@ script-message subminer-start backend=hyprland socket=/custom/path texthooker=no
## Lifecycle
For how the plugin's auto-start fits into the full launch sequence including when the launcher starts the overlay instead of the plugin see [Playback Startup Flow](./architecture#playback-startup-flow).
For how the plugin's auto-start fits into the full launch sequence - including when the launcher starts the overlay instead of the plugin - see [Playback Startup Flow](./architecture#playback-startup-flow).
- **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).
@@ -181,4 +181,4 @@ The plugin is useful when you:
- Want on-demand overlay control without the wrapper.
- Use mpv's built-in file browser or playlist features.
You can install both the plugin provides chord keybindings for convenience, while the wrapper handles the full lifecycle.
You can install both - the plugin provides chord keybindings for convenience, while the wrapper handles the full lifecycle.
+6 -6
View File
@@ -1,12 +1,12 @@
# Keyboard Shortcuts
This page is the complete reference for every keystroke SubMiner responds to. If you are just getting started, focus on the **Mining Shortcuts** and **Overlay Controls** sections those cover the day-to-day mining loop. The rest can wait until you need them.
This page is the complete reference for every keystroke SubMiner responds to. If you are just getting started, focus on the **Mining Shortcuts** and **Overlay Controls** sections - those cover the day-to-day mining loop. The rest can wait until you need them.
A few terms used throughout:
- **Overlay** the transparent SubMiner window that sits on top of mpv and shows the interactive subtitles. Most shortcuts only work while this window has focus (click the video once if a shortcut seems to do nothing).
- **`Ctrl/Cmd`** use `Ctrl` on Windows/Linux and `Cmd` (⌘) on macOS. In the config file this is written as `CommandOrControl`.
- **Accelerator** Electron's name for a shortcut string like `Alt+Shift+O`.
- **Overlay** - the transparent SubMiner window that sits on top of mpv and shows the interactive subtitles. Most shortcuts only work while this window has focus (click the video once if a shortcut seems to do nothing).
- **`Ctrl/Cmd`** - use `Ctrl` on Windows/Linux and `Cmd` (⌘) on macOS. In the config file this is written as `CommandOrControl`.
- **Accelerator** - Electron's name for a shortcut string like `Alt+Shift+O`.
All shortcuts are configurable in `config.jsonc` under `shortcuts` and `keybindings`. Set any shortcut to `null` to disable it.
@@ -105,7 +105,7 @@ Controller input only drives the overlay while keyboard-only mode is enabled. Th
## 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.
When the mpv plugin is installed, all commands use a `y` chord prefix - press `y`, then the second key within 1 second.
| Chord | Action |
| ----- | -------------------------------------- |
@@ -161,4 +161,4 @@ The `keybindings` array overrides or extends the overlay's built-in key handling
Mouse keybinding names are `MBTN_LEFT`, `MBTN_MID`, `MBTN_RIGHT`, `MBTN_BACK`, and `MBTN_FORWARD`.
Both `shortcuts`, `keybindings`, and `subtitleSidebar` are [hot-reloadable](/configuration#hot-reload-behavior) changes take effect without restarting SubMiner.
Both `shortcuts`, `keybindings`, and `subtitleSidebar` are [hot-reloadable](/configuration#hot-reload-behavior) - changes take effect without restarting SubMiner.
+15 -11
View File
@@ -2,7 +2,11 @@
SubMiner annotates subtitle tokens in real time as they appear in the overlay. Four annotation layers work together to surface useful context while you watch: **N+1 highlighting**, **character-name highlighting**, **frequency highlighting**, and **JLPT tagging**.
All four are opt-in and configured under `subtitleStyle`, `ankiConnect.knownWords`, and `ankiConnect.nPlusOne` in your config. They apply independently you can enable any combination.
All four are opt-in and configured under `subtitleStyle`, `ankiConnect.knownWords`, and `ankiConnect.nPlusOne` in your config. They apply independently - you can enable any combination.
::: tip Tokenization
SubMiner's primary tokenizer is Yomitan itself - subtitle text is tokenized based entirely on the dictionaries you have installed in Yomitan. Installing many large dictionaries can increase noise and slow down lookups, so be selective about which dictionaries you install and their priority order.
:::
Before any of those layers render, SubMiner strips annotation metadata from tokens that are usually just subtitle glue or annotation noise. Standalone particles, auxiliaries, adnominals, common explanatory endings like `んです` / `のだ`, merged trailing quote-particle forms like `...って`, auxiliary-stem grammar tails like `そうだ` (MeCab POS3 `助動詞語幹`), repeated kana interjections, and similar non-lexical helper tokens remain hoverable in the subtitle text, but they render as plain tokens without known-word, N+1, frequency, JLPT, or name-match annotation styling.
@@ -39,7 +43,7 @@ Set `refreshMinutes` to `1440` (24 hours) for daily sync if your Anki collection
## Character-Name Highlighting
Character-name matches are built from the active merged SubMiner character dictionary, which auto-syncs character data from AniList for your recently-watched titles. When the current AniList media ID is known, SubMiner ignores loaded entries from other titles for subtitle name matching and inline portraits. Matching names are highlighted in subtitles and become available for hover-driven Yomitan character profiles portraits, roles, voice actors, and biographical detail.
Character-name matches are built from the active merged SubMiner character dictionary, which auto-syncs character data from AniList for your recently-watched titles. When the current AniList media ID is known, SubMiner ignores loaded entries from other titles for subtitle name matching and inline portraits. Matching names are highlighted in subtitles and become available for hover-driven Yomitan character profiles - portraits, roles, voice actors, and biographical detail.
**How it works:**
@@ -60,12 +64,12 @@ For full details on dictionary generation, name variant expansion, auto-sync lif
## Frequency Highlighting
Frequency highlighting colors tokens based on how common they are, using dictionary frequency rank data. This helps you spot high-value vocabulary at a glance.
Frequency highlighting colors tokens based on how common they are, using dictionary frequency rank data. This helps you spot high-value vocabulary at a glance. Frequency ranks are sourced from the **highest-ranked frequency dictionary** installed in Yomitan - other frequency dictionaries are not consulted.
**Modes:**
- **Single** all highlighted tokens share one color (`singleColor`).
- **Banded** tokens are assigned to five color bands from most common to least common within the `topX` window.
- **Single** - all highlighted tokens share one color (`singleColor`).
- **Banded** - tokens are assigned to five color bands from most common to least common within the `topX` window.
SubMiner looks up each token's `frequencyRank` from `term_meta_bank_*.json` files. Only tokens with a positive rank at or below `topX` are highlighted.
@@ -130,14 +134,14 @@ All annotation layers can be toggled at runtime via the mpv command menu without
- `subtitleStyle.enableJlpt` (`On` / `Off`)
- `subtitleStyle.frequencyDictionary.enabled` (`On` / `Off`)
Toggles only apply to new subtitle lines after the change the currently displayed line is not re-tokenized in place.
Toggles only apply to new subtitle lines after the change - the currently displayed line is not re-tokenized in place.
## Rendering Priority
When multiple annotations apply to the same token, the visual priority is:
1. **N+1 target** (highest) the single unknown word in an N+1 sentence
2. **Character-name match** dictionary-driven character-name token styling
3. **Known-word color** already-learned token tint
4. **Frequency highlight** common-word coloring (not applied when N+1/character-name/known-word already matched)
5. **JLPT underline** level-based underline (stacks with the above since it uses underline rather than text color)
1. **N+1 target** (highest) - the single unknown word in an N+1 sentence
2. **Character-name match** - dictionary-driven character-name token styling
3. **Known-word color** - already-learned token tint
4. **Frequency highlight** - common-word coloring (not applied when N+1/character-name/known-word already matched)
5. **JLPT underline** - level-based underline (stacks with the above since it uses underline rather than text color)
+3 -3
View File
@@ -10,7 +10,7 @@ When SubMiner parses the active subtitle source into a cue list, the sidebar bec
- The active cue is highlighted and kept in view as playback advances (when `autoScroll` is `true`).
- Clicking any cue seeks mpv to that timestamp.
- The sidebar stays synchronized with the overlay media transitions and subtitle source changes update both simultaneously.
- The sidebar stays synchronized with the overlay - media transitions and subtitle source changes update both simultaneously.
The sidebar only appears when a parsed cue list is available. External subtitle sources that SubMiner cannot parse (for example, embedded ASS tracks rendered directly by mpv) will not populate the sidebar.
@@ -18,9 +18,9 @@ The sidebar only appears when a parsed cue list is available. External subtitle
Two layout modes are available via `subtitleSidebar.layout`:
**`overlay`** (default) The sidebar floats over mpv as a panel. It does not affect the player window size or position.
**`overlay`** (default) - The sidebar floats over mpv as a panel. It does not affect the player window size or position.
**`embedded`** Reserves space on the right side of the player and shifts the video area to mimic a split-pane layout. Useful if you want the cue list visible without it covering the video. If you see unexpected positioning in your environment, switch back to `overlay` to isolate the issue.
**`embedded`** - Reserves space on the right side of the player and shifts the video area to mimic a split-pane layout. Useful if you want the cue list visible without it covering the video. If you see unexpected positioning in your environment, switch back to `overlay` to isolate the issue.
## Configuration
+27 -27
View File
@@ -1,6 +1,6 @@
# Troubleshooting
Common issues and how to resolve them. Most problems fall into one of a few buckets the overlay shows but subtitles don't (see [MPV Connection](#mpv-connection)), cards aren't being created or come out empty (see [AnkiConnect](#ankiconnect)), or word lookups don't appear (see [Yomitan](#yomitan)). If an error message popped up on screen, search this page for the exact text most headings below are quoted error strings.
Common issues and how to resolve them. Most problems fall into one of a few buckets - the overlay shows but subtitles don't (see [MPV Connection](#mpv-connection)), cards aren't being created or come out empty (see [AnkiConnect](#ankiconnect)), or word lookups don't appear (see [Yomitan](#yomitan)). If an error message popped up on screen, search this page for the exact text - most headings below are quoted error strings.
## MPV Connection
@@ -102,7 +102,7 @@ If the overlay never appears at all, see [Playback Startup Flow](./architecture#
**"Failed to parse MPV message"**
Logged when a malformed JSON line arrives from the mpv socket. Usually harmless SubMiner skips the bad line and continues. If it happens constantly, check that nothing else is writing to the same socket path.
Logged when a malformed JSON line arrives from the mpv socket. Usually harmless - SubMiner skips the bad line and continues. If it happens constantly, check that nothing else is writing to the same socket path.
## Updates
@@ -132,7 +132,7 @@ The detected launcher is installed in a protected path such as `/usr/local/bin/s
**"AnkiConnect: unable to connect"**
First confirm you've completed the [Anki Integration prerequisites](/anki-integration#prerequisites) Anki must be running with the AnkiConnect add-on installed.
First confirm you've completed the [Anki Integration prerequisites](/anki-integration#prerequisites) - Anki must be running with the AnkiConnect add-on installed.
SubMiner connects to the active Anki endpoint:
@@ -148,7 +148,7 @@ SubMiner retries with exponential backoff (up to 5 s) and suppresses repeated er
**Cards are created but fields are empty**
Field names in your config must match your Anki note type exactly (case-sensitive). Check `ankiConnect.fields` for example, if your note type uses `SentenceAudio` but your config says `Audio`, the field will not be populated.
Field names in your config must match your Anki note type exactly (case-sensitive). Check `ankiConnect.fields` - for example, if your note type uses `SentenceAudio` but your config says `Audio`, the field will not be populated.
See [Anki Integration](/anki-integration) for the full field mapping reference.
@@ -169,7 +169,7 @@ Shown when SubMiner tries to update a card that no longer exists, or when AnkiCo
**Overlay appears but clicks pass through / cannot interact**
- Make sure you are hovering over subtitle text the overlay only becomes interactive when the cursor is over a subtitle.
- Make sure you are hovering over subtitle text - the overlay only becomes interactive when the cursor is over a subtitle.
- On macOS/Windows: toggle the overlay off and back on (`Alt+Shift+O`) to re-enable pointer events.
- On Linux: mouse event handling is unreliable in some Electron/compositor combinations. If clicks consistently fail, toggle the overlay off, click the underlying mpv window, then toggle it back on.
@@ -208,7 +208,7 @@ If you installed from the AppImage and see this error, the package may be incomp
**Yomitan lookup popup does not appear when hovering words or triggering lookup**
- Verify Yomitan loaded successfully check the terminal output for "Loaded Yomitan extension".
- Verify Yomitan loaded successfully - check the terminal output for "Loaded Yomitan extension".
- Yomitan requires dictionaries to be installed. Open Yomitan settings (`Alt+Shift+Y` or `SubMiner.AppImage --yomitan`) and confirm at least one dictionary is imported.
- If `yomitan.externalProfilePath` is set, import/check dictionaries in the external app/profile instead. SubMiner treats that profile as read-only and does not open its own Yomitan settings window.
- If the overlay shows subtitles but hover lookup never resolves on tokens, the tokenizer may have failed. See the MeCab section below.
@@ -234,9 +234,9 @@ Japanese word boundaries depend on Yomitan parser output. If segmentation seems
## Character Dictionary
Character names from AniList are matched and highlighted in subtitles via the bundled Yomitan. See [Character Dictionary](/character-dictionary) for setup and the full troubleshooting list the most common issues:
Character names from AniList are matched and highlighted in subtitles via the bundled Yomitan. See [Character Dictionary](/character-dictionary) for setup and the full troubleshooting list - the most common issues:
- **Names not highlighting:** Confirm `subtitleStyle.nameMatchEnabled` is `true`, and that the current media resolved to an AniList entry (SubMiner needs a media ID to fetch characters). No AniList account or token is required character data uses public GraphQL queries.
- **Names not highlighting:** Confirm `subtitleStyle.nameMatchEnabled` is `true`, and that the current media resolved to an AniList entry (SubMiner needs a media ID to fetch characters). No AniList account or token is required - character data uses public GraphQL queries.
- **Inline portraits missing:** Confirm `subtitleStyle.nameMatchImagesEnabled` is `true`. Portraits also require AniList to return an image and the download to succeed during snapshot generation.
- **Wrong characters showing:** Open the in-app manager (`Ctrl/Cmd+D`) and use **Override** to pin the correct AniList match for the series.
- **Feature unavailable:** If `yomitan.externalProfilePath` is set, SubMiner runs in read-only external-profile mode and its character-dictionary features are disabled.
@@ -269,7 +269,7 @@ Global shortcuts (`Alt+Shift+O`, `Alt+Shift+Y`) may conflict with other applicat
- Check your DE/WM keybinding settings for conflicts.
- Change the shortcut in your config under `shortcuts.toggleVisibleOverlayGlobal`.
- On Wayland, global shortcut registration has limitations depending on the compositor. Only Hyprland and Sway are supported natively see the [Hyprland](#hyprland) section below for shortcut passthrough rules. Other Wayland compositors require X11/Xwayland.
- On Wayland, global shortcut registration has limitations depending on the compositor. Only Hyprland and Sway are supported natively - see the [Hyprland](#hyprland) section below for shortcut passthrough rules. Other Wayland compositors require X11/Xwayland.
**Overlay keybindings not working**
@@ -326,14 +326,14 @@ The Jimaku API has rate limits. If you see 429 errors, wait for the retry durati
### Linux
- **Wayland (Hyprland/Sway only)**: Native Wayland support is limited to Hyprland and Sway. Window tracking uses compositor-specific commands (`hyprctl` / `swaymsg`). If these are not on `PATH`, tracking will fail silently. Other Wayland compositors (KDE Plasma, GNOME, …) are not supported natively both mpv and SubMiner must run under X11 or Xwayland instead. On those sessions SubMiner forces XWayland automatically for itself and for every mpv it launches (see [KDE Plasma & other Wayland compositors](#kde-plasma--other-wayland-compositors)).
- **X11 / Xwayland**: Requires `xdotool`, `xprop`, and `xwininfo`. If missing, the overlay cannot track the mpv window position. This is the required backend for any Wayland compositor other than Hyprland or Sway both mpv and SubMiner must be running under X11/Xwayland for window tracking _and_ for the overlay to stay above mpv (Wayland forbids clients from controlling window stacking). SubMiner uses a managed X11 overlay while mpv is windowed, switches to an override-redirect X11 overlay while tracked mpv is fullscreen, and hides/releases that overlay when another X11/Xwayland app takes focus. The visible overlay stays hidden until SubMiner has tracked mpv geometry, so startup should not create a display-sized fallback overlay while tokenization warms up.
- **Wayland (Hyprland/Sway only)**: Native Wayland support is limited to Hyprland and Sway. Window tracking uses compositor-specific commands (`hyprctl` / `swaymsg`). If these are not on `PATH`, tracking will fail silently. Other Wayland compositors (KDE Plasma, GNOME, …) are not supported natively - both mpv and SubMiner must run under X11 or Xwayland instead. On those sessions SubMiner forces XWayland automatically for itself and for every mpv it launches (see [KDE Plasma & other Wayland compositors](#kde-plasma--other-wayland-compositors)).
- **X11 / Xwayland**: Requires `xdotool`, `xprop`, and `xwininfo`. If missing, the overlay cannot track the mpv window position. This is the required backend for any Wayland compositor other than Hyprland or Sway - both mpv and SubMiner must be running under X11/Xwayland for window tracking _and_ for the overlay to stay above mpv (Wayland forbids clients from controlling window stacking). SubMiner uses a managed X11 overlay while mpv is windowed, switches to an override-redirect X11 overlay while tracked mpv is fullscreen, and hides/releases that overlay when another X11/Xwayland app takes focus. The visible overlay stays hidden until SubMiner has tracked mpv geometry, so startup should not create a display-sized fallback overlay while tokenization warms up.
- **Tray icon missing**: SubMiner creates an Electron tray icon in `--background` mode, but Linux trays require a StatusNotifier/AppIndicator host. Hyprland does not provide one by itself; enable a tray in Waybar, Hyprpanel, or another panel. If Electron cannot register the tray, SubMiner logs a warning that mentions the missing tray host.
- **Mouse passthrough**: On Linux X11/Xwayland, SubMiner uses `xdotool` to poll the cursor and only enables overlay input while the cursor is over subtitle or popup regions. Outside those regions, pointer input passes through to mpv. Native Wayland compositors other than Hyprland/Sway cannot provide the stacking control SubMiner needs.
### Hyprland
SubMiner's overlay is a transparent, frameless Electron window that must be kept above mpv. SubMiner tries to apply the floating, borderless, no-shadow, and no-blur properties itself each time it places the overlay. It detects Hyprland's active config provider and uses Lua `hl.dsp.window.*` dispatchers for recent Hyprland Lua configs, or the legacy dispatcher syntax for older hyprlang configs. On many configurations that is enough, but if your Hyprland version doesn't honor those runtime dispatches or a broad rule in your config forces opacity/blur on every window add explicit window rules so the overlay is exempt. You also need `pass` bindings to forward global shortcuts to SubMiner (see below).
SubMiner's overlay is a transparent, frameless Electron window that must be kept above mpv. SubMiner tries to apply the floating, borderless, no-shadow, and no-blur properties itself each time it places the overlay. It detects Hyprland's active config provider and uses Lua `hl.dsp.window.*` dispatchers for recent Hyprland Lua configs, or the legacy dispatcher syntax for older hyprlang configs. On many configurations that is enough, but if your Hyprland version doesn't honor those runtime dispatches - or a broad rule in your config forces opacity/blur on every window - add explicit window rules so the overlay is exempt. You also need `pass` bindings to forward global shortcuts to SubMiner (see below).
**Overlay is not transparent or has a visible border**
@@ -364,7 +364,7 @@ windowrule = no_shadow on, match:class SubMiner
windowrule = no_blur on, match:class SubMiner
```
If you still see a solid background or visual artifacts instead of the mpv video underneath, the culprit is almost always a global opacity/blur rule applying to the overlay the `opaque`/`opacity` and `no_blur` fields above override it.
If you still see a solid background or visual artifacts instead of the mpv video underneath, the culprit is almost always a global opacity/blur rule applying to the overlay - the `opaque`/`opacity` and `no_blur` fields above override it.
**Global shortcuts not working**
@@ -387,12 +387,12 @@ For more details, see the Hyprland docs on [global keybinds](https://wiki.hypr.l
### KDE Plasma & other Wayland compositors
On any Wayland session that is not Hyprland or Sway (KDE Plasma, GNOME, and others), the overlay can only stay above mpv when both processes run under **XWayland** the Wayland protocol forbids clients from controlling window stacking, so the overlay's "always on top" becomes a no-op on a native Wayland surface.
On any Wayland session that is not Hyprland or Sway (KDE Plasma, GNOME, and others), the overlay can only stay above mpv when both processes run under **XWayland** - the Wayland protocol forbids clients from controlling window stacking, so the overlay's "always on top" becomes a no-op on a native Wayland surface.
SubMiner handles this automatically:
- It launches its own window under XWayland (it sets `--ozone-platform-hint=x11`).
- Every mpv it launches (via the `subminer` launcher, Jellyfin, or YouTube) is pinned to XWayland too Wayland environment hints are stripped and an X11 GPU context (`--gpu-context=x11egl,x11`) is applied.
- Every mpv it launches (via the `subminer` launcher, Jellyfin, or YouTube) is pinned to XWayland too - Wayland environment hints are stripped and an X11 GPU context (`--gpu-context=x11egl,x11`) is applied.
- While mpv is windowed, the overlay is a managed X11 window owned by the tracked mpv window (`WM_TRANSIENT_FOR`), so it stays above mpv while other foreground X11/Xwayland apps can still cover both windows.
- While tracked mpv is fullscreen, SubMiner swaps the visible overlay to a focusable-false X11 override-redirect window. That path can stay above the active fullscreen mpv window without requiring a KDE/KWin-specific rule, and SubMiner hides/releases it when mpv is no longer the active X11/Xwayland window.
- The visible overlay is shown inactive on Linux, so normal hover should not steal keyboard focus from mpv.
@@ -423,15 +423,15 @@ SubMiner can only detect focus for X11/Xwayland windows in this mode. If a nativ
Feature-specific issues are covered in each feature's own page:
- [Anki Integration](/anki-integration) card creation, field mapping, and AnkiConnect setup
- [AniList Integration](/anilist-integration) watch-progress sync and authentication
- [Character Dictionary](/character-dictionary) AniList character name matching and inline portraits
- [Jellyfin Integration](/jellyfin-integration) remote playback and library connection
- [Jimaku Integration](/jimaku-integration) subtitle fetching and API rate limits
- [YouTube Integration](/youtube-integration) subtitle generation and playback
- [Immersion Tracking](/immersion-tracking) telemetry and session logging
- [WebSocket / Texthooker API](/websocket-texthooker-api) external texthooker clients
- [Subtitle Annotations](/subtitle-annotations) N+1, frequency, JLPT, and name-match layers
- [Subtitle Sidebar](/subtitle-sidebar) sidebar navigation and behavior
- [Configuration Reference](/configuration) full config options
- [Shortcuts](/shortcuts) keybinding reference
- [Anki Integration](/anki-integration) - card creation, field mapping, and AnkiConnect setup
- [AniList Integration](/anilist-integration) - watch-progress sync and authentication
- [Character Dictionary](/character-dictionary) - AniList character name matching and inline portraits
- [Jellyfin Integration](/jellyfin-integration) - remote playback and library connection
- [Jimaku Integration](/jimaku-integration) - subtitle fetching and API rate limits
- [YouTube Integration](/youtube-integration) - subtitle generation and playback
- [Immersion Tracking](/immersion-tracking) - telemetry and session logging
- [WebSocket / Texthooker API](/websocket-texthooker-api) - external texthooker clients
- [Subtitle Annotations](/subtitle-annotations) - N+1, frequency, JLPT, and name-match layers
- [Subtitle Sidebar](/subtitle-sidebar) - sidebar navigation and behavior
- [Configuration Reference](/configuration) - full config options
- [Shortcuts](/shortcuts) - keybinding reference
+8 -8
View File
@@ -8,7 +8,7 @@ Play a video with SubMiner:
subminer video.mkv
```
On **Windows**, use the **SubMiner mpv** shortcut created during first-run setup double-click it, or drag a video file onto it.
On **Windows**, use the **SubMiner mpv** shortcut created during first-run setup - double-click it, or drag a video file onto it.
That's the simplest way to get started. The `subminer` launcher handles mpv, the IPC socket, and the overlay automatically.
@@ -41,20 +41,20 @@ Field names must match your Anki note type exactly (case-sensitive). See [Anki I
When you launch SubMiner, it wires up mpv and the overlay for you:
1. SubMiner starts the overlay app in the background
2. mpv runs with an **IPC socket** at `/tmp/subminer-socket` a small local channel two programs use to talk to each other, so the overlay can ask mpv what subtitle is on screen right now
2. mpv runs with an **IPC socket** at `/tmp/subminer-socket` - a small local channel two programs use to talk to each other, so the overlay can ask mpv what subtitle is on screen right now
3. The overlay connects and subscribes to subtitle changes
From there, subtitles render as interactive, hoverable word spans and you mine cards directly from the overlay. For the overlay anatomy and the full mining loop word lookup, card creation, annotations see [Mining Workflow](/mining-workflow).
From there, subtitles render as interactive, hoverable word spans and you mine cards directly from the overlay. For the overlay anatomy and the full mining loop - word lookup, card creation, annotations - see [Mining Workflow](/mining-workflow).
### Ways to Launch
| Approach | Use when | How |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| **`subminer` launcher** | You want SubMiner to handle everything launch mpv, set up the socket, start the overlay. **Recommended for most users.** | `subminer video.mkv` |
| **`subminer` launcher** | You want SubMiner to handle everything - launch mpv, set up the socket, start the overlay. **Recommended for most users.** | `subminer video.mkv` |
| **SubMiner mpv shortcut** (Windows) | The recommended Windows entry point. Created during first-run setup, launches mpv with SubMiner's defaults. | Double-click, drag a file onto it, or run `SubMiner.exe --launch-mpv` |
| **mpv plugin** (all platforms) | Bundled and injected at runtime. Provides `y` chord keybindings for controlling the overlay from within mpv. No manual install needed. | Automatic when using the launcher or shortcut |
The mpv plugin is always available it's bundled with SubMiner and injected at runtime. If you launch mpv yourself (without the launcher), pass `--input-ipc-server=/tmp/subminer-socket` in your mpv config for the overlay to connect.
The mpv plugin is always available - it's bundled with SubMiner and injected at runtime. If you launch mpv yourself (without the launcher), pass `--input-ipc-server=/tmp/subminer-socket` in your mpv config for the overlay to connect.
## Live Config Reload
@@ -301,7 +301,7 @@ SubMiner supports gamepad/controller input for couch-friendly usage via the Chro
1. Connect a controller before or after launching SubMiner.
2. Set `controller.enabled` to `true` in your config.
3. Press `Alt+C` in the overlay by default to pick the controller you want to save and remap any action inline.
4. Enable keyboard-only mode press `Y` on the controller (default binding) or use the overlay keybinding.
4. Enable keyboard-only mode - press `Y` on the controller (default binding) or use the overlay keybinding.
5. Click the binding badge, edit pencil, or `Learn` on the overlay action you want, then press the matching button, trigger, or stick direction on the controller.
6. Use the left stick to navigate subtitle tokens and scroll the popup; use the right stick vertically for popup page jumps.
7. Press `A` to look up the selected word, `X` to mine a card, `B` to close the popup.
@@ -333,7 +333,7 @@ By default SubMiner uses the first connected controller after controller support
Learn mode ignores already-held inputs and waits for the next fresh button press or axis direction, which avoids accidental captures when you open the modal mid-input.
All button and axis mappings are configurable under the `controller` config block. Learned remaps are saved under `controller.profiles` for the selected controller id. See [Configuration Controller Support](/configuration#controller-support) for the full options.
All button and axis mappings are configurable under the `controller` config block. Learned remaps are saved under `controller.profiles` for the selected controller id. See [Configuration - Controller Support](/configuration#controller-support) for the full options.
## Keybindings
@@ -361,4 +361,4 @@ Hovering over subtitle text pauses mpv by default; leaving resumes it. Yomitan p
- Drop video files onto the overlay to replace current playback.
- Hold `Shift` while dropping to append to the playlist instead.
Next: [Mining Workflow](/mining-workflow) word lookup, card creation, and the full mining loop.
Next: [Mining Workflow](/mining-workflow) - word lookup, card creation, and the full mining loop.
+4 -4
View File
@@ -1,6 +1,6 @@
# WebSocket / Texthooker API & Integration
**Who this page is for:** developers and tinkerers who want to consume SubMiner's live subtitle stream from their own tools a browser tab, an automation script, or another mpv plugin. If you just want subtitles in a browser tab for Yomitan, skip to [Texthooker Integration Guide](#texthooker-integration-guide); the rest is reference for building custom clients.
**Who this page is for:** developers and tinkerers who want to consume SubMiner's live subtitle stream from their own tools - a browser tab, an automation script, or another mpv plugin. If you just want subtitles in a browser tab for Yomitan, skip to [Texthooker Integration Guide](#texthooker-integration-guide); the rest is reference for building custom clients.
A *texthooker* is a page/tool that receives the text currently on screen so a dictionary extension (like Yomitan) can look words up. SubMiner ships its own texthooker UI and also broadcasts subtitle text over local WebSockets that any client can connect to.
@@ -24,7 +24,7 @@ This page documents those integration points and shows how to build custom consu
## Enable and Configure the Services
SubMiner's integration ports are configured in `config.jsonc`. All three services are **off by default** the block below shows the values to set to turn them on.
SubMiner's integration ports are configured in `config.jsonc`. All three services are **off by default** - the block below shows the values to set to turn them on.
```jsonc
{
@@ -50,7 +50,7 @@ SubMiner's integration ports are configured in `config.jsonc`. All three service
- `texthooker.launchAtStartup` defaults to `false`. Set it to `true` to start the local HTTP UI automatically.
- `texthooker.openBrowser` controls whether SubMiner opens the texthooker page in your browser when it starts.
If you use the [mpv plugin](/mpv-plugin), it can also start a texthooker-only helper process. The launcher derives the plugin's texthooker setting from your SubMiner config (`texthooker.launchAtStartup`) and injects it at runtime there is no plugin config file to edit.
If you use the [mpv plugin](/mpv-plugin), it can also start a texthooker-only helper process. The launcher derives the plugin's texthooker setting from your SubMiner config (`texthooker.launchAtStartup`) and injects it at runtime - there is no plugin config file to edit.
## Developer API Documentation
@@ -368,7 +368,7 @@ ws.on('message', async (raw) => {
## Related Pages
- [Configuration](/configuration#websocket-server)
- [Mining Workflow Texthooker](/mining-workflow#texthooker)
- [Mining Workflow - Texthooker](/mining-workflow#texthooker)
- [MPV Plugin](/mpv-plugin)
- [Launcher Script](/launcher-script)
- [Anki Integration](/anki-integration#proxy-mode-setup-yomitan--texthooker)
+3 -3
View File
@@ -4,8 +4,8 @@ SubMiner auto-loads Japanese subtitles when you play a YouTube URL, giving you t
## Requirements
- **[yt-dlp](https://github.com/yt-dlp/yt-dlp)** must be installed and on your `PATH`. yt-dlp is a free command-line tool that reads YouTube video and subtitle info; SubMiner calls it behind the scenes. (`PATH` is the list of folders your system searches for programs most installers add yt-dlp to it automatically. If yours did not, set `SUBMINER_YTDLP_BIN` to the full path of the yt-dlp binary.)
- mpv with `--input-ipc-server` configured (handled automatically when you launch playback through the `subminer` launcher no manual setup needed).
- **[yt-dlp](https://github.com/yt-dlp/yt-dlp)** must be installed and on your `PATH`. yt-dlp is a free command-line tool that reads YouTube video and subtitle info; SubMiner calls it behind the scenes. (`PATH` is the list of folders your system searches for programs - most installers add yt-dlp to it automatically. If yours did not, set `SUBMINER_YTDLP_BIN` to the full path of the yt-dlp binary.)
- mpv with `--input-ipc-server` configured (handled automatically when you launch playback through the `subminer` launcher - no manual setup needed).
## How It Works
@@ -32,7 +32,7 @@ flowchart TD
C[Track discovery]:::action
D{Auto or manual selection?}:::step
E[Auto-select best tracks]:::action
F[Manual picker Ctrl+Alt+C]:::action
F[Manual picker - Ctrl+Alt+C]:::action
G[Download subtitle files]:::action
H[Convert TimedText to VTT]:::enrich
I[Normalize auto-caption duplicates]:::enrich