docs: update docs for youtube subtitle and mining flow

This commit is contained in:
2026-03-22 18:38:51 -07:00
parent 8ddace5536
commit 3fb33af116
12 changed files with 241 additions and 90 deletions

View File

@@ -1,16 +1,25 @@
# Changelog # Changelog
## v0.9.0 (2026-03-22)
### Changed
- Subtitle Sidebar: Added subtitle sidebar state and behavior updates, including startup-auto-open controls and resume positioning improvements.
- Subtitle Sidebar: Fixed subtitle prefetch and embedded overlay passthrough sync between sidebar and overlay subtitle rendering.
- Launcher: Added an app-owned YouTube subtitle picker flow that boots mpv paused, opens an overlay track picker, and downloads selected subtitles into external files.
- Launcher: Added explicit `download` and `generate` YouTube subtitle modes with `download` as the default path.
- Launcher: Disabled mpv native YouTube subtitle auto-loading for the app-owned flow so external subtitle files stay authoritative.
- Launcher: Added OSD status messages for YouTube playback startup, subtitle acquisition, and subtitle loading so the flow stays visible before and during the picker.
## v0.8.0 (2026-03-22) ## v0.8.0 (2026-03-22)
### Added ### Added
- Overlay: Added the subtitle sidebar feature with a new `subtitleSidebar` configuration surface. - Overlay: Added the subtitle sidebar feature with a new `subtitleSidebar` configuration surface and rendered sidebar modal with cue list rendering, click-to-seek, active-cue highlighting, and embedded layout support.
- Overlay: Added a sidebar modal with cue list rendering, click-to-seek, active-cue highlighting, and embedded layout support.
- IPC: Added sidebar snapshot plumbing between renderer and main process for overlay/sidebar synchronization. - IPC: Added sidebar snapshot plumbing between renderer and main process for overlay/sidebar synchronization.
### Changed ### Changed
- Config: Added hot-reloadable sidebar options for enablement, layout, visibility, typography, opacity, sizing, and interaction behavior (`autoOpen`, `pauseOnHover`, `autoScroll`, toggle key). - Config: Added hot-reloadable sidebar options for enablement, layout, visibility, typography, opacity, sizing, and interaction behavior (`autoOpen`, `pauseOnHover`, `autoScroll`, toggle key).
- Docs: Added full `subtitleSidebar` documentation coverage, including sample config, option table, and toggle shortcut notes. - Docs: Added full `subtitleSidebar` documentation coverage, including sample config, option table, and toggle shortcut notes.
- Runtime: Improved subtitle prefetch and rendering flow so sidebar and overlay subtitle states are kept in sync across media transitions. - Runtime: Improved subtitle prefetch/rendering flow so sidebar and overlay subtitle states stay in sync across media transitions.
### Fixed ### Fixed
- Overlay: Kept sidebar cue tracking stable across playback transitions and timing edge cases. - Overlay: Kept sidebar cue tracking stable across playback transitions and timing edge cases.

216
README.md
View File

@@ -1,60 +1,159 @@
<div align="center"> <div align="center">
<img src="assets/SubMiner.png" width="140" alt="SubMiner logo">
<img src="assets/SubMiner.png" width="160" alt="SubMiner logo">
# SubMiner # SubMiner
**Sentence-mine from mpv — look up words, one-key Anki export, immersion tracking.** ### Turn mpv into a sentence-mining workstation.
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) Look up words with Yomitan, export to Anki in one key, track your immersion — all without leaving mpv.
[![Linux](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-informational)](https://github.com/ksyasuda/SubMiner)
[![Docs](https://img.shields.io/badge/docs-docs.subminer.moe-blueviolet)](https://docs.subminer.moe) [![License: GPL v3](https://img.shields.io/badge/license-GPLv3-1a1a2e?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0)
[![AUR](https://img.shields.io/aur/version/subminer-bin)](https://aur.archlinux.org/packages/subminer-bin) [![Platform](https://img.shields.io/badge/platform-Linux%20·%20macOS%20·%20Windows-1a1a2e?style=flat-square)](https://github.com/ksyasuda/SubMiner)
[![Docs](https://img.shields.io/badge/docs-docs.subminer.moe-e6a817?style=flat-square)](https://docs.subminer.moe)
[![AUR](https://img.shields.io/aur/version/subminer-bin?style=flat-square&color=1a1a2e)](https://aur.archlinux.org/packages/subminer-bin)
[![SubMiner demo](./assets/minecard.webp)](./assets/minecard.mp4)
</div> </div>
--- ## How It Works
SubMiner is an Electron overlay for [mpv](https://mpv.io) that turns video into a sentence-mining workstation. Look up any word with [Yomitan](https://github.com/yomidevs/yomitan), mine it to Anki with one key, and track your immersion over time. SubMiner runs as an invisible Electron overlay on top of mpv. Subtitles render as an interactive layer. Move your cursor over any word and trigger a [Yomitan](https://github.com/yomidevs/yomitan) lookup. Press one key to snapshot the sentence, audio, and screenshot into Anki via AnkiConnect.
<div align="center">
[![SubMiner demo (Animated preview)](./assets/minecard.webp)](./assets/minecard.mp4)
</div>
## Features ## Features
**Dictionary lookups** — Yomitan runs inside the overlay. Hover or navigate to any word for full dictionary popups without leaving mpv. ### Dictionary Lookups
**One-key Anki mining** — Press one key to create a card with the sentence, audio clip, screenshot, and machine translation from the exact playback moment. Yomitan runs inside the overlay. Trigger a lookup on any word for full dictionary popups — definitions, pitch accent, frequency data — without ever leaving mpv.
<div align="center"> <div align="center">
<img src="docs-site/public/screenshots/yomitan-lookup.png" width="800" alt="Yomitan popup with dictionary entry and mine button over annotated subtitles in mpv"> <img src="docs-site/public/screenshots/yomitan-lookup.png" width="800" alt="Yomitan dictionary popup over annotated subtitles in mpv">
</div> </div>
**Reading annotations** — Real-time subtitle annotations with N+1 targeting, frequency highlighting, JLPT tags, and a character name dictionary. Grammar-only tokens render as plain text. <br>
### Instant Anki Mining
Create an Anki card with the sentence, audio clip, screenshot, and machine translation from the exact playback moment with one key press, click, or controller input.
<div align="center"> <div align="center">
<img src="docs-site/public/screenshots/annotations.png" width="800" alt="Annotated subtitles with frequency highlighting, JLPT underlines, known words, and N+1 targets"> <img src="docs-site/public/screenshots/one-key-mining.png" width="800" alt="Anki card created from SubMiner with sentence, audio, and screenshot">
</div> </div>
**Immersion dashboard** — Local stats dashboard with watch time, anime progress, vocabulary growth, mining throughput, and session history. <br>
### Reading Annotations
Real-time subtitle annotations with frequency highlighting, JLPT tags, N+1 targeting, and a character name dictionary. Known words fade back; new words stand out. Grammar-only tokens render as plain text so you focus on what matters.
<div align="center"> <div align="center">
<img src="docs-site/public/screenshots/stats-overview.png" width="800" alt="Stats dashboard with watch time, cards mined, streaks, and tracking snapshot"> <img src="docs-site/public/screenshots/annotations.png" width="800" alt="Annotated subtitles with frequency coloring, JLPT underlines, and N+1 targets">
</div> </div>
**Integrations** — AniList episode tracking, Jellyfin remote playback, Jimaku subtitle downloads, alass/ffsubsync, and an annotated websocket feed for external clients. <br>
### Immersion Dashboard
Local stats dashboard — watch time, anime library, vocabulary growth, mining throughput, session history, and trends. All stored locally, no third-party tracking.
<div align="center"> <div align="center">
<img src="docs-site/public/screenshots/texthooker.png" width="800" alt="Texthooker page with annotated subtitle lines and frequency highlighting"> <img src="docs-site/public/screenshots/stats-overview.png" width="800" alt="Stats dashboard showing watch time, cards mined, streaks, and tracking data">
</div> </div>
<br>
### Integrations
<table>
<tr>
<td><b>AniList</b></td>
<td>Automatic episode tracking and progress sync</td>
</tr>
<tr>
<td><b>Jellyfin</b></td>
<td>Browse and launch media from your Jellyfin server</td>
</tr>
<tr>
<td><b>Jimaku</b></td>
<td>Search and download Japanese subtitles</td>
</tr>
<tr>
<td><b>alass / ffsubsync</b></td>
<td>Automatic subtitle retiming</td>
</tr>
<tr>
<td><b>WebSocket</b></td>
<td>Annotated subtitle feed for external clients (texthooker pages, custom tools)</td>
</tr>
</table>
<div align="center">
<img src="docs-site/public/screenshots/texthooker.png" width="800" alt="Texthooker page receiving annotated subtitle lines via WebSocket">
</div>
<br>
---
## Requirements
| | Required | Optional |
| -------------- | --------------------------------------- | -------------------------------------- |
| **Player** | [`mpv`](https://mpv.io) with IPC socket | — |
| **Processing** | `ffmpeg`, `mecab` + `mecab-ipadic` | `guessit` (AniSkip) |
| **Media** | — | `yt-dlp`, `chafa`, `ffmpegthumbnailer` |
| **Selection** | — | `fzf` / `rofi` |
> [!NOTE]
> [`bun`](https://bun.sh) is required if building from source or using the CLI wrapper: `subminer`. Pre-built releases (AppImage, DMG, installer) do not require it.
**Platform-specific:**
| Linux | macOS | Windows |
| ----------------------------------- | ------------------------ | ------------- |
| `hyprctl` or `xdotool` + `xwininfo` | Accessibility permission | No extra deps |
<details>
<summary><b>Arch Linux</b></summary>
```bash
paru -S --needed mpv ffmpeg mecab-git mecab-ipadic
# Optional
paru -S --needed yt-dlp fzf rofi chafa ffmpegthumbnailer xdotool xorg-xwininfo
# X11 / XWAYLAND
paru -S --needed xdotool xorg-xwininfo
```
</details>
<details>
<summary><b>macOS</b></summary>
```bash
brew install mpv ffmpeg mecab mecab-ipadic
# Optional
brew install yt-dlp fzf rofi chafa ffmpegthumbnailer
```
Grant Accessibility permission to SubMiner in **System Settings > Privacy & Security > Accessibility**.
</details>
<details>
<summary><b>Windows</b></summary>
Install [`mpv`](https://mpv.io/installation/) and [`ffmpeg`](https://ffmpeg.org/download.html) and ensure both are on your `PATH`.
For MeCab, install [MeCab for Windows](https://taku910.github.io/mecab/#download) with the UTF-8 dictionary.
</details>
--- ---
## Quick Start ## Quick Start
### Install ### 1. Install
<details> <details>
<summary><b>Arch Linux (AUR)</b></summary> <summary><b>Arch Linux (AUR)</b></summary>
@@ -88,53 +187,62 @@ wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer -O ~
</details> </details>
<details> <details>
<summary><b>macOS / Windows / From source</b></summary> <summary><b>macOS</b></summary>
**macOS**Download the latest DMG/ZIP from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest) and drag `SubMiner.app` into `/Applications`. Download the latest DMG or ZIP from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest) and drag `SubMiner.app` into `/Applications`.
**Windows** — Download the latest installer or portable `.zip` from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest). Keep `mpv` on `PATH`.
**From source** — See [docs.subminer.moe/installation#from-source](https://docs.subminer.moe/installation#from-source).
</details> </details>
### First Launch <details>
<summary><b>Windows</b></summary>
Run `SubMiner.AppImage` (Linux), `SubMiner.app` (macOS), or `SubMiner.exe` (Windows). On first launch, SubMiner starts in the tray, creates a default config, and opens a setup popup where you can install the mpv plugin and configure Yomitan dictionaries. Download the latest installer or portable `.zip` from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest). Make sure `mpv` is on your `PATH`.
### Mine </details>
<details>
<summary><b>From source</b></summary>
See the [build-from-source guide](https://docs.subminer.moe/installation#from-source).
</details>
### 2. First Launch
Run the app. On first launch SubMiner starts in the system tray, creates a default config, and opens a setup popup to install the mpv plugin and configure Yomitan dictionaries.
### 3. Mine
```bash ```bash
subminer video.mkv # auto-starts overlay + resumes playback subminer video.mkv # play video with overlay
subminer --start video.mkv # explicit overlay start (if plugin auto_start=no) subminer --start video.mkv # explicit overlay start
subminer stats # open the immersion dashboard subminer stats # open immersion dashboard
subminer stats -b # keep the stats daemon running in background subminer stats -b # stats daemon in background
subminer stats -s # stop the dedicated stats daemon
subminer stats cleanup # repair/prune stored stats vocabulary rows
``` ```
--- ---
## Requirements
| Required | Optional |
| ------------------------------------------------------ | ----------------------------- |
| [`mpv`](https://mpv.io) with IPC socket | `yt-dlp` |
| `ffmpeg` | `guessit` (AniSkip detection) |
| `mecab` + `mecab-ipadic` | `fzf` / `rofi` |
| [`bun`](https://bun.sh) (source builds, Linux wrapper) | `chafa`, `ffmpegthumbnailer` |
| Linux: `hyprctl` or `xdotool` + `xwininfo` | |
| macOS: Accessibility permission | |
Windows uses native window tracking and does not need the Linux compositor tools.
## Documentation ## Documentation
Full guides on configuration, Anki, Jellyfin, immersion tracking, and more at **[docs.subminer.moe](https://docs.subminer.moe)**. Full guides on configuration, Anki setup, Jellyfin, immersion tracking, and more: **[docs.subminer.moe](https://docs.subminer.moe)**
---
## Acknowledgments ## Acknowledgments
Built on [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner), [Renji's Texthooker Page](https://github.com/Renji-XD/texthooker-ui), [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script), and [Bee's Character Dictionary](https://github.com/bee-san/Japanese_Character_Name_Dictionary). Subtitles from [Jimaku.cc](https://jimaku.cc). Lookups via [Yomitan](https://github.com/yomidevs/yomitan). JLPT tags from [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab). SubMiner builds on the work of these open-source projects:
| Project | Role |
| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script) | Inspiration for the mining workflow |
| [asbplayer](https://github.com/killergerbah/asbplayer) | Inspiration for subtitle sidebar and logic for YouTube subtitle parsing |
| [Bee's Character Dictionary](https://github.com/bee-san/Japanese_Character_Name_Dictionary) | Character name recognition in subtitles |
| [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner) | Inspiration for Electron overlay with Yomitan integration |
| [jellyfin-mpv-shim](https://github.com/jellyfin/jellyfin-mpv-shim) | Jellyfin integration |
| [Jimaku.cc](https://jimaku.cc) | Japanese subtitle search and downloads |
| [Renji's Texthooker Page](https://github.com/Renji-XD/texthooker-ui) | Base for the WebSocket texthooker integration |
| [Yomitan](https://github.com/yomidevs/yomitan) | Dictionary engine powering all lookups and the morphological parser |
| [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab) | JLPT level tags for vocabulary |
## License ## License

View File

@@ -1,7 +1,7 @@
--- ---
id: TASK-194 id: TASK-194
title: Redesign YouTube subtitle acquisition around download-first track selection title: App-owned YouTube subtitle picker flow
status: To Do status: Done
assignee: [] assignee: []
created_date: '2026-03-18 07:52' created_date: '2026-03-18 07:52'
labels: [] labels: []
@@ -18,17 +18,16 @@ priority: medium
## Description ## Description
<!-- SECTION:DESCRIPTION:BEGIN --> <!-- SECTION:DESCRIPTION:BEGIN -->
Replace the current YouTube subtitle-generation-first flow with a download-first flow that enumerates available YouTube subtitle tracks, prompts for primary and secondary track selection before playback, downloads selected tracks into external subtitle files for mpv, and preserves generation as an explicit mode and as fallback behavior in auto mode. Keep the existing SubMiner tokenization and annotation pipeline as the downstream consumer of downloaded subtitle files. Replace the YouTube subtitle-generation-first flow with an app-owned picker flow that boots mpv paused, opens an overlay track picker, downloads selected subtitles into external subtitle files, and preserves generation as an explicit mode. Keep the existing SubMiner tokenization and annotation pipeline as the downstream consumer of downloaded subtitle files.
<!-- SECTION:DESCRIPTION:END --> <!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria ## Acceptance Criteria
<!-- AC:BEGIN --> <!-- AC:BEGIN -->
- [ ] #1 Launcher and config expose YouTube subtitle acquisition modes `download`, `generate`, and `auto`, with `download` as the default for launcher YouTube playback. - [x] #1 Launcher and app expose YouTube subtitle acquisition modes `download` and `generate`, with `download` as the default.
- [ ] #2 YouTube playback enumerates available subtitle tracks before mpv launch and presents a selection UI that supports primary and secondary subtitle choices. - [x] #2 YouTube playback boots mpv paused and presents an overlay selection UI for primary and secondary subtitle choices.
- [ ] #3 Selected YouTube subtitle tracks are downloaded to external subtitle files and loaded into mpv before playback starts when download mode succeeds. - [x] #3 Selected YouTube subtitle tracks are downloaded to external subtitle files and loaded into mpv before playback resumes.
- [ ] #4 `auto` mode attempts download-first for the selected tracks and falls back to generation only when required tracks cannot be downloaded or download fails. - [x] #4 `generate` mode preserves the existing subtitle generation path as an explicit opt-in behavior.
- [ ] #5 `generate` mode preserves the existing whisper/AI generation path as an explicit opt-in behavior. - [x] #5 Downloaded YouTube subtitle files integrate with the existing SubMiner subtitle/tokenization/annotation pipeline without regressing current overlay behavior.
- [ ] #6 Downloaded YouTube subtitle files integrate with the existing SubMiner subtitle/tokenization/annotation pipeline without regressing current overlay behavior. - [x] #6 Tests cover mode selection, subtitle-track enumeration/selection flow, and the paused bootstrap plus app handoff path.
- [ ] #7 Tests cover mode selection, subtitle-track enumeration/selection flow, download-first success path, and fallback behavior for auto mode. - [x] #7 User-facing config and launcher docs are updated to describe the new modes and default behavior.
- [ ] #8 User-facing config and launcher docs are updated to describe the new modes and default behavior.
<!-- AC:END --> <!-- AC:END -->

View File

@@ -29,7 +29,8 @@ In both modes, the enrichment workflow is the same:
4. Fills the translation field from the secondary subtitle or AI. 4. Fills the translation field from the secondary subtitle or AI.
5. Writes metadata to the miscInfo field. 5. Writes metadata to the miscInfo field.
Polling mode uses the query `"deck:<your-deck>" added:1` to find recently added cards. If no deck is configured, it searches all decks. Polling mode uses the query `"deck:<ankiConnect.deck>" added:1` to find recently added cards. If no deck is configured, it searches all decks.
Known-word sync scope is controlled by `ankiConnect.knownWords.decks` (object map), with `ankiConnect.deck` used as legacy fallback.
### Proxy Mode Setup (Yomitan / Texthooker) ### Proxy Mode Setup (Yomitan / Texthooker)

View File

@@ -2,10 +2,14 @@
## v0.8.0 (2026-03-22) ## v0.8.0 (2026-03-22)
- Added a configurable subtitle sidebar feature (`subtitleSidebar`) with overlay/embedded rendering, click-to-seek cue list, and hot-reloadable visibility and behavior controls. - Added a configurable subtitle sidebar feature (`subtitleSidebar`) with overlay/embedded rendering, click-to-seek cue list, and hot-reloadable visibility and behavior controls.
- Added release docs updates for sidebar configuration, including options, sample config, and toggle shortcut behavior. - Added a rendered sidebar modal with cue list display, click-to-seek, active-cue highlighting, and embedded layout support.
- Synced sidebar and overlay subtitle states during playback transitions via IPC-backed snapshot plumbing. - Added sidebar snapshot plumbing between main and renderer for overlay/sidebar synchronization.
- Fixed sidebar cue tracking to remain stable across timing edge cases and stale subtitle refreshes. - Added sidebar configuration options for visibility and behavior (enabled, layout, toggle key, autoOpen, pauseOnHover, autoScroll) plus typography and sizing controls.
- Improved sidebar resume/start behavior by jumping directly to the first resolved active cue. - Documented `subtitleSidebar` configuration and behavior in user-facing docs (configuration.md, shortcuts.md, config.example.jsonc).
- Updated subtitle prefetch/rendering flow to keep overlay and sidebar state in sync through media transitions.
- Kept sidebar cue tracking stable across playback transitions and timing edge cases.
- Fixed sidebar startup/resume positioning to jump directly to the first resolved active cue.
- Prevented stale subtitle refreshes from regressing active-cue state.
## v0.7.0 (2026-03-19) ## v0.7.0 (2026-03-19)
- Added a full local immersion dashboard release line with Overview, Library, Trends, Vocabulary, and Sessions drill-down views backed by SQLite tracking data. - Added a full local immersion dashboard release line with Overview, Library, Trends, Vocabulary, and Sessions drill-down views backed by SQLite tracking data.

View File

@@ -17,6 +17,11 @@ For most users, start with this minimal configuration:
"ankiConnect": { "ankiConnect": {
"enabled": true, "enabled": true,
"deck": "YourDeckName", "deck": "YourDeckName",
"knownWords": {
"decks": {
"YourDeckName": ["Word", "Word Reading", "Expression"]
}
},
"fields": { "fields": {
"sentence": "Sentence", "sentence": "Sentence",
"audio": "Audio", "audio": "Audio",
@@ -26,6 +31,8 @@ For most users, start with this minimal configuration:
} }
``` ```
`ankiConnect.deck` is still accepted for backward-compatible polling scope and legacy known-word fallback behavior. For known-word cache scope, prefer `ankiConnect.knownWords.decks` with deck-to-fields mapping.
Then customize as needed using the sections below. Then customize as needed using the sections below.
## Configuration File ## Configuration File
@@ -348,7 +355,8 @@ Configure the parsed-subtitle sidebar modal.
```json ```json
{ {
"subtitleSidebar": { "subtitleSidebar": {
"enabled": true, "enabled": false,
"autoOpen": false,
"layout": "overlay", "layout": "overlay",
"toggleKey": "Backslash", "toggleKey": "Backslash",
"pauseVideoOnHover": false, "pauseVideoOnHover": false,
@@ -362,12 +370,13 @@ Configure the parsed-subtitle sidebar modal.
| Option | Values | Description | | Option | Values | Description |
| --------------------------- | ---------------- | -------------------------------------------------------------------------------- | | --------------------------- | ---------------- | -------------------------------------------------------------------------------- |
| `enabled` | boolean | Enable subtitle sidebar support (`false` by default) | | `enabled` | boolean | Enable subtitle sidebar support (`false` by default) |
| `autoOpen` | boolean | Open sidebar automatically on overlay startup (`false` by default) |
| `layout` | string | `"overlay"` floats over mpv; `"embedded"` reserves right-side player space to mimic browser-like layout | | `layout` | string | `"overlay"` floats over mpv; `"embedded"` reserves right-side player space to mimic browser-like layout |
| `toggleKey` | string | `KeyboardEvent.code` used to open/close the sidebar (default: `"Backslash"`) | | `toggleKey` | string | `KeyboardEvent.code` used to open/close the sidebar (default: `"Backslash"`) |
| `pauseVideoOnHover` | boolean | Pause playback while hovering the sidebar cue list | | `pauseVideoOnHover` | boolean | Pause playback while hovering the sidebar cue list |
| `autoScroll` | boolean | Keep the active cue in view while playback advances | | `autoScroll` | boolean | Keep the active cue in view while playback advances |
| `maxWidth` | number | Maximum sidebar width in CSS pixels (default: `420`) | | `maxWidth` | number | Maximum sidebar width in CSS pixels (default: `420`) |
| `opacity` | number | Sidebar opacity between `0` and `1` (default: `0.78`) | | `opacity` | number | Sidebar opacity between `0` and `1` (default: `0.95`) |
| `backgroundColor` | string | Sidebar shell background color | | `backgroundColor` | string | Sidebar shell background color |
| `textColor` | hex color | Default cue text color | | `textColor` | hex color | Default cue text color |
| `fontFamily` | string | CSS `font-family` value applied to sidebar cue text | | `fontFamily` | string | CSS `font-family` value applied to sidebar cue text |
@@ -751,6 +760,8 @@ Anki and YouTube subtitle cleanup both read this provider, then apply feature-lo
| `apiKey` | string | Static API key for the shared provider | | `apiKey` | string | Static API key for the shared provider |
| `apiKeyCommand` | string | Shell command used to resolve the API key | | `apiKeyCommand` | string | Shell command used to resolve the API key |
| `baseUrl` | string (URL) | OpenAI-compatible base URL | | `baseUrl` | string (URL) | OpenAI-compatible base URL |
| `model` | string | Optional model override for shared provider workflows |
| `systemPrompt` | string | Optional system prompt override for shared provider workflows |
| `requestTimeoutMs` | integer milliseconds | Shared request timeout (default: `15000`) | | `requestTimeoutMs` | integer milliseconds | Shared request timeout (default: `15000`) |
SubMiner uses the shared provider in two places: SubMiner uses the shared provider in two places:
@@ -840,8 +851,8 @@ This example is intentionally compact. The option table below documents availabl
| `proxy.port` | number | Bind port for local AnkiConnect proxy (default: `8766`) | | `proxy.port` | number | Bind port for local AnkiConnect proxy (default: `8766`) |
| `proxy.upstreamUrl` | string (URL) | Upstream AnkiConnect URL that proxy forwards to (default: `http://127.0.0.1:8765`) | | `proxy.upstreamUrl` | string (URL) | Upstream AnkiConnect URL that proxy forwards to (default: `http://127.0.0.1:8765`) |
| `tags` | array of strings | Tags automatically added to cards mined/updated by SubMiner (default: `['SubMiner']`; set `[]` to disable automatic tagging). | | `tags` | array of strings | Tags automatically added to cards mined/updated by SubMiner (default: `['SubMiner']`; set `[]` to disable automatic tagging). |
| `deck` | string | Anki deck to monitor for new cards | | `ankiConnect.deck` | string | Legacy Anki polling/compatibility scope. Newer known-word cache scoping should use `ankiConnect.knownWords.decks`. |
| `ankiConnect.knownWords.decks` | array of strings | Decks used for known-word cache lookups. When omitted/empty, falls back to `ankiConnect.deck`. | | `ankiConnect.knownWords.decks` | object | Deck→fields mapping for known-word cache queries (for example `{ "Kaishi 1.5k": ["Word", "Word Reading"] }`). |
| `fields.word` | string | Card field for mined word / expression text (default: `Expression`) | | `fields.word` | string | Card field for mined word / expression text (default: `Expression`) |
| `fields.audio` | string | Card field for audio files (default: `ExpressionAudio`) | | `fields.audio` | string | Card field for audio files (default: `ExpressionAudio`) |
| `fields.image` | string | Card field for images (default: `Picture`) | | `fields.image` | string | Card field for images (default: `Picture`) |
@@ -862,6 +873,7 @@ This example is intentionally compact. The option table below documents availabl
| `media.animatedMaxWidth` | number (px) | Max width for animated AVIF (default: `640`) | | `media.animatedMaxWidth` | number (px) | Max width for animated AVIF (default: `640`) |
| `media.animatedMaxHeight` | number (px) | Optional max height for animated AVIF. Unset keeps source aspect-constrained height. | | `media.animatedMaxHeight` | number (px) | Optional max height for animated AVIF. Unset keeps source aspect-constrained height. |
| `media.animatedCrf` | number (0-63) | CRF quality for AVIF; lower = higher quality (default: `35`) | | `media.animatedCrf` | number (0-63) | CRF quality for AVIF; lower = higher quality (default: `35`) |
| `media.syncAnimatedImageToWordAudio` | `true`, `false` | Whether animated AVIF includes an opening frame synced to sentence word-audio timing (default: `true`). |
| `media.audioPadding` | number (seconds) | Padding around audio clip timing (default: `0.5`) | | `media.audioPadding` | number (seconds) | Padding around audio clip timing (default: `0.5`) |
| `media.fallbackDuration` | number (seconds) | Default duration if timing unavailable (default: `3.0`) | | `media.fallbackDuration` | number (seconds) | Default duration if timing unavailable (default: `3.0`) |
| `media.maxMediaDuration` | number (seconds) | Max duration for generated media from multi-line copy (default: `30`, `0` to disable) | | `media.maxMediaDuration` | number (seconds) | Max duration for generated media from multi-line copy (default: `30`, `0` to disable) |
@@ -870,10 +882,11 @@ This example is intentionally compact. The option table below documents availabl
| `behavior.mediaInsertMode` | `"append"`, `"prepend"` | Where to insert new media when overwrite is off (default: `"append"`) | | `behavior.mediaInsertMode` | `"append"`, `"prepend"` | Where to insert new media when overwrite is off (default: `"append"`) |
| `behavior.highlightWord` | `true`, `false` | Highlight the word in sentence context (default: `true`) | | `behavior.highlightWord` | `true`, `false` | Highlight the word in sentence context (default: `true`) |
| `ankiConnect.knownWords.highlightEnabled` | `true`, `false` | Enable fast local highlighting for words already known in Anki (default: `false`) | | `ankiConnect.knownWords.highlightEnabled` | `true`, `false` | Enable fast local highlighting for words already known in Anki (default: `false`) |
| `ankiConnect.knownWords.addMinedWordsImmediately` | `true`, `false` | Add words from successful mines into the local known-word cache immediately (default: `true`) |
| `ankiConnect.knownWords.color` | hex color string | Text color for tokens already found in the local known-word cache (default: `"#a6da95"`). | | `ankiConnect.knownWords.color` | hex color string | Text color for tokens already found in the local known-word cache (default: `"#a6da95"`). |
| `ankiConnect.knownWords.matchMode` | `"headword"`, `"surface"` | Matching strategy for known-word highlighting (default: `"headword"`). `headword` uses token headwords; `surface` uses visible subtitle text. | | `ankiConnect.knownWords.matchMode` | `"headword"`, `"surface"` | Matching strategy for known-word highlighting (default: `"headword"`). `headword` uses token headwords; `surface` uses visible subtitle text. |
| `ankiConnect.knownWords.refreshMinutes` | number | Minutes between known-word cache refreshes (default: `1440`) | | `ankiConnect.knownWords.refreshMinutes` | number | Minutes between known-word cache refreshes (default: `1440`) |
| `ankiConnect.knownWords.decks` | array of strings | Decks used by known-word cache refresh. Leave empty for compatibility with legacy `deck` scope. | | `ankiConnect.knownWords.decks` | object | Deck→fields mapping used for known-word cache query scope (e.g. `{ "Kaishi 1.5k": ["Word", "Word Reading"] }`). |
| `ankiConnect.nPlusOne.nPlusOne` | hex color string | Text color for the single target token to study when exactly one unknown candidate exists in a sentence (default: `"#c6a0f6"`). | | `ankiConnect.nPlusOne.nPlusOne` | hex color string | Text color for the single target token to study when exactly one unknown candidate exists in a sentence (default: `"#c6a0f6"`). |
| `ankiConnect.nPlusOne.minSentenceWords` | number | Minimum number of words required in a sentence before single unknown-word N+1 highlighting can trigger (default: `3`). | | `ankiConnect.nPlusOne.minSentenceWords` | number | Minimum number of words required in a sentence before single unknown-word N+1 highlighting can trigger (default: `3`). |
| `behavior.notificationType` | `"osd"`, `"system"`, `"both"`, `"none"` | Notification type on card update (default: `"osd"`) | | `behavior.notificationType` | `"osd"`, `"system"`, `"both"`, `"none"` | Notification type on card update (default: `"osd"`) |
@@ -919,7 +932,7 @@ Known-word cache policy:
- `ankiConnect.nPlusOne.nPlusOne` sets the color for the single target token when exactly one eligible unknown word exists. - `ankiConnect.nPlusOne.nPlusOne` sets the color for the single target token when exactly one eligible unknown word exists.
- `ankiConnect.nPlusOne.minSentenceWords` sets the minimum token count required in a sentence for N+1 highlighting (default: `3`). - `ankiConnect.nPlusOne.minSentenceWords` sets the minimum token count required in a sentence for N+1 highlighting (default: `3`).
- `ankiConnect.knownWords.color` sets the known-word highlight color for tokens already in Anki. - `ankiConnect.knownWords.color` sets the known-word highlight color for tokens already in Anki.
- `ankiConnect.knownWords.decks` accepts one or more decks. If empty, it uses the legacy single `ankiConnect.deck` value as scope. - `ankiConnect.knownWords.decks` accepts an object keyed by deck name. If omitted or empty, it falls back to the legacy `ankiConnect.deck` single-deck scope.
- Cache state is persisted to `known-words-cache.json` under the app `userData` directory. - Cache state is persisted to `known-words-cache.json` under the app `userData` directory.
- The cache is automatically invalidated when the configured scope changes (for example, when deck changes). - The cache is automatically invalidated when the configured scope changes (for example, when deck changes).
- Cache lookups are in-memory. By default, token headwords are matched against cached `Expression` / `Word` values; set `ankiConnect.knownWords.matchMode` to `"surface"` for raw subtitle text matching. - Cache lookups are in-memory. By default, token headwords are matched against cached `Expression` / `Word` values; set `ankiConnect.knownWords.matchMode` to `"surface"` for raw subtitle text matching.
@@ -1283,7 +1296,7 @@ Configure the local stats UI served from SubMiner and the in-app stats overlay t
{ {
"stats": { "stats": {
"toggleKey": "Backquote", "toggleKey": "Backquote",
"serverPort": 5175, "serverPort": 6969,
"autoStartServer": true, "autoStartServer": true,
"autoOpenBrowser": true "autoOpenBrowser": true
} }
@@ -1293,7 +1306,7 @@ Configure the local stats UI served from SubMiner and the in-app stats overlay t
| Option | Values | Description | | Option | Values | Description |
| ----------------- | ----------------- | --------------------------------------------------------------------------- | | ----------------- | ----------------- | --------------------------------------------------------------------------- |
| `toggleKey` | Electron key code | Overlay-local key code used to toggle the stats overlay. Default `Backquote`. | | `toggleKey` | Electron key code | Overlay-local key code used to toggle the stats overlay. Default `Backquote`. |
| `serverPort` | integer | Localhost port for the browser stats UI. Default `5175`. | | `serverPort` | integer | Localhost port for the browser stats UI. Default `6969`. |
| `autoStartServer` | `true`, `false` | Start the local stats HTTP server automatically once immersion tracking is active. Default `true`. | | `autoStartServer` | `true`, `false` | Start the local stats HTTP server automatically once immersion tracking is active. Default `true`. |
| `autoOpenBrowser` | `true`, `false` | When `subminer stats` starts the server on demand, also open the dashboard in your default browser. Default `true`. | | `autoOpenBrowser` | `true`, `false` | When `subminer stats` starts the server on demand, also open the dashboard in your default browser. Default `true`. |

View File

@@ -28,7 +28,7 @@ The same immersion data powers the stats dashboard.
- Launcher command: run `subminer stats` to start the local stats server on demand and open the dashboard in your browser. - Launcher command: run `subminer stats` to start the local stats server on demand and open the dashboard in your browser.
- Background server: run `subminer stats -b` to start or reuse a dedicated background stats daemon without keeping the launcher attached, and `subminer stats -s` to stop that daemon. - Background server: run `subminer stats -b` to start or reuse a dedicated background stats daemon without keeping the launcher attached, and `subminer stats -s` to stop that daemon.
- Maintenance command: run `subminer stats cleanup` or `subminer stats cleanup -v` to backfill/repair vocabulary metadata (`headword`, `reading`, POS) and purge stale or excluded rows from `imm_words` on demand. - Maintenance command: run `subminer stats cleanup` or `subminer stats cleanup -v` to backfill/repair vocabulary metadata (`headword`, `reading`, POS) and purge stale or excluded rows from `imm_words` on demand.
- Browser page: open `http://127.0.0.1:5175` directly if the local stats server is already running. - Browser page: open `http://127.0.0.1:6969` directly if the local stats server is already running.
### Dashboard Tabs ### Dashboard Tabs
@@ -68,7 +68,7 @@ Stats server config lives under `stats`:
{ {
"stats": { "stats": {
"toggleKey": "Backquote", "toggleKey": "Backquote",
"serverPort": 5175, "serverPort": 6969,
"autoStartServer": true, "autoStartServer": true,
"autoOpenBrowser": true "autoOpenBrowser": true
} }

View File

@@ -6,11 +6,28 @@ This guide walks through the sentence mining loop — from watching a video to c
SubMiner runs as a transparent overlay on top of mpv. As subtitles play, the overlay displays them as interactive text. You hover a word, trigger Yomitan lookup with your configured lookup key/modifier, then create an Anki card with a single action. SubMiner automatically attaches the sentence, audio clip, and screenshot. SubMiner runs as a transparent overlay on top of mpv. As subtitles play, the overlay displays them as interactive text. You hover a word, trigger Yomitan lookup with your configured lookup key/modifier, then create an Anki card with a single action. SubMiner automatically attaches the sentence, audio clip, and screenshot.
```text ```mermaid
Watch video → See subtitle → Hover word + trigger lookup → Yomitan popup → Add to Anki flowchart LR
classDef step fill:#c6a0f6,stroke:#494d64,color:#24273a,stroke-width:1.5px
SubMiner auto-fills: classDef action fill:#8aadf4,stroke:#494d64,color:#24273a,stroke-width:1.5px
sentence, audio, image, translation classDef result fill:#a6da95,stroke:#494d64,color:#24273a,stroke-width:1.5px
classDef enrich fill:#8bd5ca,stroke:#494d64,color:#24273a,stroke-width:1.5px
Watch["Watch Video"]:::step
Sub["Subtitle Appears"]:::step
Hover["Hover Word"]:::action
Lookup["Trigger Lookup"]:::action
Yomi["Yomitan Popup"]:::result
Add["Add to Anki"]:::result
Watch --> Sub --> Hover --> Lookup --> Yomi --> Add
Add --> Enrich["SubMiner Enriches"]:::enrich
Enrich --> S["Sentence"]:::enrich
Enrich --> A["Audio Clip"]:::enrich
Enrich --> I["Screenshot"]:::enrich
Enrich --> T["Translation"]:::enrich
``` ```
## Subtitle Delivery Path (Startup + Runtime) ## Subtitle Delivery Path (Startup + Runtime)
@@ -208,7 +225,7 @@ Enable it in your config:
} }
``` ```
Open the dashboard in the overlay with `stats.toggleKey` (default: `` ` ``), launch it in a browser with `subminer stats`, keep a dedicated background server alive with `subminer stats -b`, stop that background server with `subminer stats -s`, or visit `http://127.0.0.1:5175` directly if the local stats server is already running. The dashboard covers overview totals, anime progress, session detail, and vocabulary drill-down from the same local immersion database. Open the dashboard in the overlay with `stats.toggleKey` (default: `` ` ``), launch it in a browser with `subminer stats`, keep a dedicated background server alive with `subminer stats -b`, stop that background server with `subminer stats -s`, or visit `http://127.0.0.1:6969` directly if the local stats server is already running. The dashboard covers overview totals, anime progress, session detail, and vocabulary drill-down from the same local immersion database.
See [Immersion Tracking](/immersion-tracking) for dashboard details, schema, and retention settings. See [Immersion Tracking](/immersion-tracking) for dashboard details, schema, and retention settings.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -24,7 +24,7 @@ N+1 highlighting identifies sentences where you know every word except one, maki
| --- | --- | --- | | --- | --- | --- |
| `ankiConnect.knownWords.highlightEnabled` | `false` | Enable known-word cache lookups used by N+1 highlighting | | `ankiConnect.knownWords.highlightEnabled` | `false` | Enable known-word cache lookups used by N+1 highlighting |
| `ankiConnect.knownWords.refreshMinutes` | `1440` | Minutes between Anki cache refreshes | | `ankiConnect.knownWords.refreshMinutes` | `1440` | Minutes between Anki cache refreshes |
| `ankiConnect.knownWords.decks` | `[]` | Decks to query (falls back to `ankiConnect.deck`) | | `ankiConnect.knownWords.decks` | `{}` | Deck→fields map for known-word cache queries (legacy fallback: `ankiConnect.deck`) |
| `ankiConnect.knownWords.matchMode` | `"headword"` | `"headword"` (dictionary form) or `"surface"` (raw text) | | `ankiConnect.knownWords.matchMode` | `"headword"` | `"headword"` (dictionary form) or `"surface"` (raw text) |
| `ankiConnect.nPlusOne.minSentenceWords` | `3` | Minimum tokens in a sentence for N+1 to trigger | | `ankiConnect.nPlusOne.minSentenceWords` | `3` | Minimum tokens in a sentence for N+1 to trigger |
| `ankiConnect.nPlusOne.nPlusOne` | `#c6a0f6` | Color for the single unknown target word | | `ankiConnect.nPlusOne.nPlusOne` | `#c6a0f6` | Color for the single unknown target word |