mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-24 12:11:29 -07:00
feat: add app-owned YouTube subtitle flow with absPlayer-style parsing (#31)
* fix: harden preload argv parsing for popup windows * fix: align youtube playback with shared overlay startup * fix: unwrap mpv youtube streams for anki media mining * docs: update docs for youtube subtitle and mining flow * refactor: unify cli and runtime wiring for startup and youtube flow * feat: update subtitle sidebar overlay behavior * chore: add shared log-file source for diagnostics * fix(ci): add changelog fragment for immersion changes * fix: address CodeRabbit review feedback * fix: persist canonical title from youtube metadata * style: format stats library tab * fix: address latest review feedback * style: format stats library files * test: stub launcher youtube deps in CI * test: isolate launcher youtube flow deps * test: stub launcher youtube deps in failing case * test: force x11 backend in launcher ci harness * test: address latest review feedback * fix(launcher): preserve user YouTube ytdl raw options * docs(backlog): update task tracking notes * fix(immersion): special-case youtube media paths in runtime and tracking * feat(stats): improve YouTube media metadata and picker key handling * fix(ci): format stats media library hook * fix: address latest CodeRabbit review items * docs: update youtube release notes and docs * feat: auto-load youtube subtitles before manual picker * fix: restore app-owned youtube subtitle flow * docs: update youtube playback docs and config copy * refactor: remove legacy youtube launcher mode plumbing * fix: refine youtube subtitle startup binding * docs: clarify youtube subtitle startup behavior * fix: address PR #31 latest review follow-ups * fix: address PR #31 follow-up review comments * test: harden youtube picker test harness * udpate backlog * fix: add timeout to youtube metadata probe * docs: refresh youtube and stats docs * update backlog * update backlog * chore: release v0.9.0
This commit is contained in:
@@ -29,7 +29,8 @@ In both modes, the enrichment workflow is the same:
|
||||
4. Fills the translation field from the secondary subtitle or AI.
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## v0.9.0 (2026-03-23)
|
||||
- Added an app-owned YouTube subtitle flow with absPlayer-style timedtext parsing that auto-loads the default primary subtitle plus a best-effort secondary at startup and resumes once the primary is ready.
|
||||
- Added a manual YouTube subtitle picker on `Ctrl+Alt+C` so subtitle selection can be retried on demand during active YouTube playback.
|
||||
- Added yt-dlp metadata probing so YouTube playback and immersion tracking record canonical video title and channel metadata.
|
||||
- Disabled conflicting mpv native subtitle auto-selection for the app-owned flow so injected explicit tracks stay authoritative.
|
||||
- Added OSD status updates covering YouTube playback startup, subtitle acquisition, and subtitle loading.
|
||||
- Stopped forcing `--ytdl-raw-options=` before user-provided mpv options so existing YouTube cookie integrations are preserved.
|
||||
- Improved sidebar startup/resume behavior, scroll handling, and overlay/sidebar subtitle synchronization.
|
||||
- Stats Library tab now shows YouTube video title, channel name, and thumbnail for YouTube media entries.
|
||||
- Added a new WebSocket / Texthooker API integration guide covering payload formats, custom client patterns, and mpv plugin automation.
|
||||
- Fixed Anki media mining for mpv YouTube streams so audio and screenshot capture work correctly during YouTube playback sessions.
|
||||
- Fixed YouTube media path handling in immersion tracking so YouTube sessions record correct media references and AniList state transitions do not fire for YouTube media.
|
||||
- Reused existing authoritative YouTube subtitle tracks when present, fell back only for missing sides, and kept native mpv secondary subtitle rendering hidden so the overlay remains the visible secondary subtitle surface.
|
||||
|
||||
## 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 release docs updates for sidebar configuration, including options, sample config, and toggle shortcut behavior.
|
||||
- Synced sidebar and overlay subtitle states during playback transitions via IPC-backed snapshot plumbing.
|
||||
- Fixed sidebar cue tracking to remain stable across timing edge cases and stale subtitle refreshes.
|
||||
- Improved sidebar resume/start behavior by jumping directly to the first resolved active cue.
|
||||
- Added a rendered sidebar modal with cue list display, click-to-seek, active-cue highlighting, and embedded layout support.
|
||||
- Added sidebar snapshot plumbing between main and renderer for overlay/sidebar synchronization.
|
||||
- Added sidebar configuration options for visibility and behavior (enabled, layout, toggle key, autoOpen, pauseOnHover, autoScroll) plus typography and sizing controls.
|
||||
- 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)
|
||||
- Added a full local immersion dashboard release line with Overview, Library, Trends, Vocabulary, and Sessions drill-down views backed by SQLite tracking data.
|
||||
|
||||
@@ -17,6 +17,11 @@ For most users, start with this minimal configuration:
|
||||
"ankiConnect": {
|
||||
"enabled": true,
|
||||
"deck": "YourDeckName",
|
||||
"knownWords": {
|
||||
"decks": {
|
||||
"YourDeckName": ["Word", "Word Reading", "Expression"]
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"sentence": "Sentence",
|
||||
"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.
|
||||
|
||||
## Configuration File
|
||||
@@ -120,7 +127,7 @@ The configuration file includes several main sections:
|
||||
- [**Discord Rich Presence**](#discord-rich-presence) - Optional Discord activity card updates
|
||||
- [**Immersion Tracking**](#immersion-tracking) - Track subtitle sessions and mining activity in SQLite
|
||||
- [**Stats Dashboard**](#stats-dashboard) - Local dashboard and overlay for immersion progress
|
||||
- [**YouTube Subtitle Generation**](#youtube-subtitle-generation) - Launcher defaults for yt-dlp + local whisper fallback
|
||||
- [**YouTube Playback Settings**](#youtube-playback-settings) - Defaults for YouTube subtitle loading
|
||||
|
||||
## Core Settings
|
||||
|
||||
@@ -348,7 +355,8 @@ Configure the parsed-subtitle sidebar modal.
|
||||
```json
|
||||
{
|
||||
"subtitleSidebar": {
|
||||
"enabled": true,
|
||||
"enabled": false,
|
||||
"autoOpen": false,
|
||||
"layout": "overlay",
|
||||
"toggleKey": "Backslash",
|
||||
"pauseVideoOnHover": false,
|
||||
@@ -362,12 +370,13 @@ Configure the parsed-subtitle sidebar modal.
|
||||
| Option | Values | Description |
|
||||
| --------------------------- | ---------------- | -------------------------------------------------------------------------------- |
|
||||
| `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 |
|
||||
| `toggleKey` | string | `KeyboardEvent.code` used to open/close the sidebar (default: `"Backslash"`) |
|
||||
| `pauseVideoOnHover` | boolean | Pause playback while hovering the sidebar cue list |
|
||||
| `autoScroll` | boolean | Keep the active cue in view while playback advances |
|
||||
| `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 |
|
||||
| `textColor` | hex color | Default cue text color |
|
||||
| `fontFamily` | string | CSS `font-family` value applied to sidebar cue text |
|
||||
@@ -460,6 +469,7 @@ See `config.example.jsonc` for detailed configuration options and more examples.
|
||||
| `Space` | `["cycle", "pause"]` | Toggle pause |
|
||||
| `KeyJ` | `["cycle", "sid"]` | Cycle primary subtitle track |
|
||||
| `Shift+KeyJ` | `["cycle", "secondary-sid"]` | Cycle secondary subtitle track |
|
||||
| `Ctrl+Alt+KeyC` | `["__youtube-picker-open"]` | Open the manual YouTube subtitle picker |
|
||||
| `ArrowRight` | `["seek", 5]` | Seek forward 5 seconds |
|
||||
| `ArrowLeft` | `["seek", -5]` | Seek backward 5 seconds |
|
||||
| `ArrowUp` | `["seek", 60]` | Seek forward 60 seconds |
|
||||
@@ -731,7 +741,7 @@ Palette controls:
|
||||
### Shared AI Provider
|
||||
|
||||
Shared OpenAI-compatible transport settings live at the top level under `ai`.
|
||||
Anki and YouTube subtitle cleanup both read this provider, then apply feature-local overrides where supported.
|
||||
Anki reads this provider directly. Legacy subtitle fallback keeps the same provider shape for compatibility, then applies feature-local overrides where supported.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -751,12 +761,14 @@ Anki and YouTube subtitle cleanup both read this provider, then apply feature-lo
|
||||
| `apiKey` | string | Static API key for the shared provider |
|
||||
| `apiKeyCommand` | string | Shell command used to resolve the API key |
|
||||
| `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`) |
|
||||
|
||||
SubMiner uses the shared provider in two places:
|
||||
SubMiner uses the shared provider for:
|
||||
|
||||
- Anki translation/enrichment when `ankiConnect.ai.enabled` is `true`
|
||||
- YouTube whisper subtitle post-processing when `youtubeSubgen.fixWithAi` is `true`
|
||||
- Legacy subtitle fallback compatibility when `youtubeSubgen.fixWithAi` is `true`
|
||||
|
||||
### AnkiConnect
|
||||
|
||||
@@ -840,8 +852,8 @@ This example is intentionally compact. The option table below documents availabl
|
||||
| `proxy.port` | number | Bind port for local AnkiConnect proxy (default: `8766`) |
|
||||
| `proxy.upstreamUrl` | string (URL) | Upstream AnkiConnect URL that proxy forwards to (default: `http://127.0.0.1:8765`) |
|
||||
| `tags` | array of strings | Tags automatically added to cards mined/updated by SubMiner (default: `['SubMiner']`; set `[]` to disable automatic tagging). |
|
||||
| `deck` | string | Anki deck to monitor for new cards |
|
||||
| `ankiConnect.knownWords.decks` | array of strings | Decks used for known-word cache lookups. When omitted/empty, falls back to `ankiConnect.deck`. |
|
||||
| `ankiConnect.deck` | string | Legacy Anki polling/compatibility scope. Newer known-word cache scoping should use `ankiConnect.knownWords.decks`. |
|
||||
| `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.audio` | string | Card field for audio files (default: `ExpressionAudio`) |
|
||||
| `fields.image` | string | Card field for images (default: `Picture`) |
|
||||
@@ -862,6 +874,7 @@ This example is intentionally compact. The option table below documents availabl
|
||||
| `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.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.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) |
|
||||
@@ -870,10 +883,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.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.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.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.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.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"`) |
|
||||
@@ -919,7 +933,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.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.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.
|
||||
- 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.
|
||||
@@ -1263,6 +1277,14 @@ Enable or disable local immersion analytics stored in SQLite for mined subtitles
|
||||
| `retention.monthlyRollupsDays` | integer (`0`-`36500`) | Monthly rollup retention window. Default `0` (keep all). |
|
||||
| `retention.vacuumIntervalDays` | integer (`0`-`3650`) | Minimum spacing between `VACUUM` passes. `0` disables vacuum. Default `0` (disabled). |
|
||||
|
||||
You can also disable immersion tracking for a single session using:
|
||||
|
||||
```bash
|
||||
SUBMINER_DISABLE_IMMERSION_TRACKING=1 subminer
|
||||
```
|
||||
|
||||
When this is set, SubMiner skips immersion-tracker startup and does not initialize or read the immersion SQLite database for that session.
|
||||
|
||||
Default behavior keeps raw events, telemetry, sessions, and rollups forever while still maintaining lifetime summary tables and daily/monthly rollups for faster reads. If you later want bounded retention, switch `retentionMode` or set explicit `retention.*` values.
|
||||
|
||||
When `dbPath` is blank or omitted, SubMiner writes telemetry and session summaries to the default app-data location:
|
||||
@@ -1283,7 +1305,7 @@ Configure the local stats UI served from SubMiner and the in-app stats overlay t
|
||||
{
|
||||
"stats": {
|
||||
"toggleKey": "Backquote",
|
||||
"serverPort": 5175,
|
||||
"serverPort": 6969,
|
||||
"autoStartServer": true,
|
||||
"autoOpenBrowser": true
|
||||
}
|
||||
@@ -1293,7 +1315,7 @@ Configure the local stats UI served from SubMiner and the in-app stats overlay t
|
||||
| Option | Values | Description |
|
||||
| ----------------- | ----------------- | --------------------------------------------------------------------------- |
|
||||
| `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`. |
|
||||
| `autoOpenBrowser` | `true`, `false` | When `subminer stats` starts the server on demand, also open the dashboard in your default browser. Default `true`. |
|
||||
|
||||
@@ -1304,22 +1326,13 @@ Usage notes:
|
||||
- The dashboard reads from the same immersion-tracking database, so keep `immersionTracking.enabled` on if you want data to appear.
|
||||
- The UI includes Overview, Library, Trends, Vocabulary, and Sessions tabs.
|
||||
|
||||
### YouTube Subtitle Generation
|
||||
### YouTube Playback Settings
|
||||
|
||||
Set defaults used by the `subminer` launcher for YouTube subtitle generation:
|
||||
Set defaults used by the `subminer` launcher for YouTube subtitle loading:
|
||||
|
||||
```json
|
||||
{
|
||||
"youtubeSubgen": {
|
||||
"whisperBin": "/path/to/whisper-cli",
|
||||
"whisperModel": "/path/to/ggml-model.bin",
|
||||
"whisperVadModel": "/path/to/ggml-vad.bin",
|
||||
"whisperThreads": 4,
|
||||
"fixWithAi": false,
|
||||
"ai": {
|
||||
"model": "openai/gpt-4o-mini",
|
||||
"systemPrompt": "Fix subtitle mistakes only."
|
||||
},
|
||||
"primarySubLanguages": ["ja", "jpn"]
|
||||
}
|
||||
}
|
||||
@@ -1327,27 +1340,22 @@ Set defaults used by the `subminer` launcher for YouTube subtitle generation:
|
||||
|
||||
| Option | Values | Description |
|
||||
| --------------------- | -------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| `whisperBin` | string path | Path to `whisper.cpp` CLI binary used as fallback transcription engine |
|
||||
| `whisperModel` | string path | Path to whisper model used by fallback transcription |
|
||||
| `whisperVadModel` | string path | Optional whisper VAD model path |
|
||||
| `whisperThreads` | integer | Thread count passed to whisper runs |
|
||||
| `fixWithAi` | `true`, `false` | Run shared AI post-processing on whisper-generated subtitles |
|
||||
| `ai.model` | string | Optional model override for YouTube AI subtitle cleanup |
|
||||
| `ai.systemPrompt` | string | Optional system prompt override for YouTube AI subtitle cleanup |
|
||||
| `primarySubLanguages` | string[] | Primary subtitle language priority for YouTube subtitle generation (default `["ja", "jpn"]`) |
|
||||
| `primarySubLanguages` | string[] | Primary subtitle language priority for YouTube auto-loading (default `["ja", "jpn"]`) |
|
||||
|
||||
Launcher behavior:
|
||||
Current launcher behavior:
|
||||
|
||||
- For YouTube URLs, subtitle generation now runs before mpv launch.
|
||||
- SubMiner probes manual/native YouTube subtitle tracks first.
|
||||
- Missing tracks fall back to local `whisper.cpp`.
|
||||
- English secondary subtitles can use whisper translate fallback when no manual track exists.
|
||||
- If `fixWithAi` is enabled, only whisper-generated `.srt` output is post-processed with the shared top-level `ai` provider.
|
||||
- For YouTube URLs, SubMiner probes subtitle tracks with yt-dlp after mpv bootstrap and binds auto-selected tracks before normal playback resumes.
|
||||
- If YouTube/mpv already exposes an authoritative matching subtitle track, SubMiner reuses it; otherwise it downloads and injects only the missing side.
|
||||
- SubMiner loads the primary subtitle plus a best-effort secondary subtitle.
|
||||
- Playback waits only for primary subtitle readiness; secondary failures do not block playback.
|
||||
- English secondary subtitles are selected from `secondarySub.secondarySubLanguages` when primary language matches are unavailable.
|
||||
- Native mpv secondary subtitle rendering stays hidden during this flow so the SubMiner overlay remains the visible secondary subtitle surface.
|
||||
- If primary subtitle loading fails, use `Ctrl+Alt+C` to open the subtitle modal and pick a track.
|
||||
|
||||
Language targets are derived from subtitle config:
|
||||
|
||||
- primary track: `youtubeSubgen.primarySubLanguages` (falls back to `["ja","jpn"]`)
|
||||
- secondary track: `secondarySub.secondarySubLanguages` (falls back to English when empty)
|
||||
- Subtitle files are generated or downloaded before mpv starts; the older launcher mode switch has been removed.
|
||||
- Tracks are resolved and loaded before mpv starts; the older launcher mode switch has been removed.
|
||||
|
||||
Precedence for launcher defaults is: CLI flag > environment variable > `config.jsonc` > built-in default.
|
||||
|
||||
@@ -231,13 +231,13 @@ Run `make help` for a full list of targets. Key ones:
|
||||
| `SUBMINER_ROFI_THEME` | Override rofi theme path for launcher picker |
|
||||
| `SUBMINER_LOG_LEVEL` | Override app logger level (`debug`, `info`, `warn`, `error`) |
|
||||
| `SUBMINER_MPV_LOG` | Override mpv/app shared log file path |
|
||||
| `SUBMINER_WHISPER_BIN` | Override `youtubeSubgen.whisperBin` for launcher |
|
||||
| `SUBMINER_WHISPER_MODEL` | Override `youtubeSubgen.whisperModel` for launcher |
|
||||
| `SUBMINER_WHISPER_VAD_MODEL` | Override `youtubeSubgen.whisperVadModel` for launcher |
|
||||
| `SUBMINER_WHISPER_THREADS` | Override `youtubeSubgen.whisperThreads` for launcher |
|
||||
| `SUBMINER_YT_SUBGEN_OUT_DIR` | Override generated subtitle output directory |
|
||||
| `SUBMINER_YT_SUBGEN_AUDIO_FORMAT` | Override extraction format used for whisper fallback |
|
||||
| `SUBMINER_YT_SUBGEN_KEEP_TEMP` | Set to `1` to keep temporary subtitle-generation workspace |
|
||||
| `SUBMINER_WHISPER_BIN` | Override legacy `youtubeSubgen.whisperBin` fallback compatibility path |
|
||||
| `SUBMINER_WHISPER_MODEL` | Override legacy `youtubeSubgen.whisperModel` fallback compatibility path |
|
||||
| `SUBMINER_WHISPER_VAD_MODEL` | Override legacy `youtubeSubgen.whisperVadModel` fallback compatibility path |
|
||||
| `SUBMINER_WHISPER_THREADS` | Override legacy `youtubeSubgen.whisperThreads` fallback compatibility value |
|
||||
| `SUBMINER_YT_SUBGEN_OUT_DIR` | Override legacy fallback subtitle output directory |
|
||||
| `SUBMINER_YT_SUBGEN_AUDIO_FORMAT` | Override extraction format used by legacy fallback subtitle path |
|
||||
| `SUBMINER_YT_SUBGEN_KEEP_TEMP` | Set to `1` to keep legacy fallback subtitle workspace |
|
||||
| `SUBMINER_JIMAKU_API_KEY` | Override Jimaku API key for launcher subtitle downloads |
|
||||
| `SUBMINER_JIMAKU_API_KEY_COMMAND` | Command used to resolve Jimaku API key at runtime |
|
||||
| `SUBMINER_JIMAKU_API_BASE_URL` | Override Jimaku API base URL |
|
||||
|
||||
@@ -20,7 +20,7 @@ function extractReleaseHeadings(content: string, count: number): string[] {
|
||||
test('docs reflect current launcher and release surfaces', () => {
|
||||
expect(usageContents).not.toContain('--mode preprocess');
|
||||
expect(usageContents).not.toContain('"automatic" (default)');
|
||||
expect(usageContents).toContain('before mpv starts');
|
||||
expect(usageContents).toContain('during startup while mpv is paused');
|
||||
|
||||
expect(installationContents).toContain('bun run build:appimage');
|
||||
expect(installationContents).toContain('bun run build:win');
|
||||
|
||||
@@ -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.
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
@@ -42,6 +42,8 @@ Recent sessions, streak calendar, watch-time history, and a tracking snapshot wi
|
||||
|
||||
Cover-art library with search and sorting, per-series progress, episode drill-down, and direct links into mined cards.
|
||||
|
||||
When YouTube channel metadata is available, the Library tab groups videos by creator/channel and treats each tracked video as an episode-like entry inside that channel section.
|
||||
|
||||

|
||||
|
||||
#### Trends
|
||||
@@ -68,7 +70,7 @@ Stats server config lives under `stats`:
|
||||
{
|
||||
"stats": {
|
||||
"toggleKey": "Backquote",
|
||||
"serverPort": 5175,
|
||||
"serverPort": 6969,
|
||||
"autoStartServer": true,
|
||||
"autoOpenBrowser": true
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ features:
|
||||
- icon:
|
||||
src: /assets/video.svg
|
||||
alt: Video playback icon
|
||||
title: YouTube & Whisper
|
||||
details: Play YouTube URLs or searches with native subtitles, or generate them with whisper.cpp and optional AI cleanup.
|
||||
title: YouTube Playback
|
||||
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:
|
||||
@@ -72,10 +72,10 @@ features:
|
||||
- icon:
|
||||
src: /assets/tokenization.svg
|
||||
alt: Tracking chart icon
|
||||
title: Immersion Tracking
|
||||
details: Logs watch time, words encountered, and cards mined to SQLite, then surfaces the same data in a local stats dashboard with rollups and session drill-down.
|
||||
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.
|
||||
link: /immersion-tracking
|
||||
linkText: Stats details
|
||||
linkText: Dashboard & tracking
|
||||
- icon:
|
||||
src: /assets/cross-platform.svg
|
||||
alt: Cross-platform icon
|
||||
@@ -120,7 +120,7 @@ const demoAssetVersion = '20260223-2';
|
||||
<div class="workflow-step" style="animation-delay: 240ms">
|
||||
<div class="step-number">05</div>
|
||||
<div class="step-title">Track</div>
|
||||
<div class="step-desc">Review immersion history and repeat high-value patterns over time.</div>
|
||||
<div class="step-desc">Open the stats dashboard to review sessions, vocabulary trends, and mine cards from past viewing history.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -58,24 +58,30 @@ subminer --start video.mkv # optional explicit overlay start when plugin au
|
||||
subminer -S video.mkv # same as above via --start-overlay
|
||||
subminer https://youtu.be/... # YouTube playback (requires yt-dlp)
|
||||
subminer ytsearch:"jp news" # YouTube search
|
||||
subminer stats # open immersion dashboard
|
||||
subminer stats -b # start background stats daemon
|
||||
subminer stats -s # stop background stats daemon
|
||||
subminer --setup # Open first-run setup popup
|
||||
```
|
||||
|
||||
## Subcommands
|
||||
|
||||
| Subcommand | Purpose |
|
||||
| -------------------------- | ---------------------------------------------------------- |
|
||||
| `subminer jellyfin` / `jf` | Jellyfin workflows (`-d` discovery, `-p` play, `-l` login) |
|
||||
| `subminer yt` / `youtube` | YouTube shorthand (`-o`, `-m`) |
|
||||
| `subminer doctor` | Dependency + config + socket diagnostics |
|
||||
| `subminer config path` | Print active config file path |
|
||||
| `subminer config show` | Print active config contents |
|
||||
| `subminer mpv status` | Check mpv socket readiness |
|
||||
| `subminer mpv socket` | Print active socket path |
|
||||
| `subminer mpv idle` | Launch detached idle mpv instance |
|
||||
| `subminer dictionary <path>` | Generate character dictionary ZIP from file/dir target |
|
||||
| `subminer texthooker` | Launch texthooker-only mode |
|
||||
| `subminer app` | Pass arguments directly to SubMiner binary |
|
||||
| Subcommand | Purpose |
|
||||
| ---------------------------- | ---------------------------------------------------------- |
|
||||
| `subminer jellyfin` / `jf` | Jellyfin workflows (`-d` discovery, `-p` play, `-l` login) |
|
||||
| `subminer stats` | Start stats server and open immersion dashboard in browser |
|
||||
| `subminer stats -b` | Start or reuse background stats daemon (non-blocking) |
|
||||
| `subminer stats -s` | Stop the background stats daemon |
|
||||
| `subminer stats cleanup` | Backfill vocabulary metadata and prune stale rows |
|
||||
| `subminer doctor` | Dependency + config + socket diagnostics |
|
||||
| `subminer config path` | Print active config file path |
|
||||
| `subminer config show` | Print active config contents |
|
||||
| `subminer mpv status` | Check mpv socket readiness |
|
||||
| `subminer mpv socket` | Print active socket path |
|
||||
| `subminer mpv idle` | Launch detached idle mpv instance |
|
||||
| `subminer dictionary <path>` | Generate character dictionary ZIP from file/dir target |
|
||||
| `subminer texthooker` | Launch texthooker-only mode |
|
||||
| `subminer app` | Pass arguments directly to SubMiner binary |
|
||||
|
||||
Use `subminer <subcommand> -h` for command-specific help.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
```text
|
||||
Watch video → See subtitle → Hover word + trigger lookup → Yomitan popup → Add to Anki
|
||||
↓
|
||||
SubMiner auto-fills:
|
||||
sentence, audio, image, translation
|
||||
```mermaid
|
||||
flowchart LR
|
||||
classDef step fill:#c6a0f6,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef action fill:#8aadf4,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
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)
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
// ==========================================
|
||||
// Secondary Subtitles
|
||||
// Dual subtitle track options.
|
||||
// Used by subminer YouTube subtitle generation as secondary language preferences.
|
||||
// Used by the YouTube subtitle loading flow as secondary language preferences.
|
||||
// Hot-reload: defaultMode updates live while SubMiner is running.
|
||||
// ==========================================
|
||||
"secondarySub": {
|
||||
@@ -414,24 +414,24 @@
|
||||
}, // Jimaku API configuration and defaults.
|
||||
|
||||
// ==========================================
|
||||
// YouTube Subtitle Generation
|
||||
// Defaults for SubMiner YouTube subtitle generation.
|
||||
// YouTube Playback Settings
|
||||
// Defaults for SubMiner YouTube subtitle loading and languages.
|
||||
// ==========================================
|
||||
"youtubeSubgen": {
|
||||
"whisperBin": "", // Path to whisper.cpp CLI used as fallback transcription engine.
|
||||
"whisperModel": "", // Path to whisper model used for fallback transcription.
|
||||
"whisperVadModel": "", // Path to optional whisper VAD model used for subtitle generation.
|
||||
"whisperThreads": 4, // Thread count passed to whisper.cpp subtitle generation runs.
|
||||
"fixWithAi": false, // Use shared AI provider to post-process whisper-generated YouTube subtitles. Values: true | false
|
||||
"whisperBin": "", // Legacy compatibility path kept for external subtitle fallback tools; not used by default.
|
||||
"whisperModel": "", // Legacy compatibility model path kept for external subtitle fallback tooling; not used by default.
|
||||
"whisperVadModel": "", // Legacy compatibility VAD path kept for external subtitle fallback tooling; not used by default.
|
||||
"whisperThreads": 4, // Legacy thread tuning for subtitle fallback tooling; not used by default.
|
||||
"fixWithAi": false, // Legacy subtitle fallback post-processing switch kept for compatibility; use is currently disabled by default. Values: true | false
|
||||
"ai": {
|
||||
"model": "", // Optional model override for YouTube subtitle AI post-processing.
|
||||
"systemPrompt": "" // Optional system prompt override for YouTube subtitle AI post-processing.
|
||||
"model": "", // Optional model override for legacy subtitle fallback post-processing; not used by default.
|
||||
"systemPrompt": "" // Optional system prompt override for legacy subtitle fallback post-processing; not used by default.
|
||||
}, // Ai setting.
|
||||
"primarySubLanguages": [
|
||||
"ja",
|
||||
"jpn"
|
||||
] // Comma-separated primary subtitle language priority used by the launcher.
|
||||
}, // Defaults for SubMiner YouTube subtitle generation.
|
||||
}, // Defaults for SubMiner YouTube subtitle loading and languages.
|
||||
|
||||
// ==========================================
|
||||
// Anilist
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB |
BIN
docs-site/public/screenshots/one-key-mining.png
Normal file
BIN
docs-site/public/screenshots/one-key-mining.png
Normal file
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 |
@@ -67,6 +67,7 @@ Mouse-hover playback behavior is configured separately from shortcuts: `subtitle
|
||||
| `Ctrl/Cmd+Shift+V` | Cycle secondary subtitle mode (hidden → visible → hover) | `shortcuts.toggleSecondarySub` |
|
||||
| `Ctrl/Cmd+Shift+O` | Open runtime options palette | `shortcuts.openRuntimeOptions` |
|
||||
| `Ctrl+Shift+J` | Open Jimaku subtitle search modal | `shortcuts.openJimaku` |
|
||||
| `Ctrl+Alt+C` | Open the manual YouTube subtitle picker | `keybindings` |
|
||||
| `Ctrl+Alt+S` | Open subtitle sync (subsync) modal | `shortcuts.triggerSubsync` |
|
||||
| `\` | Toggle subtitle sidebar | `subtitleSidebar.toggleKey` |
|
||||
| `` ` `` | Toggle stats overlay | `stats.toggleKey` |
|
||||
|
||||
@@ -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.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.nPlusOne.minSentenceWords` | `3` | Minimum tokens in a sentence for N+1 to trigger |
|
||||
| `ankiConnect.nPlusOne.nPlusOne` | `#c6a0f6` | Color for the single unknown target word |
|
||||
|
||||
@@ -29,7 +29,7 @@ SubMiner retries the connection automatically with increasing delays (200 ms, 50
|
||||
- Common spikes come from:
|
||||
- first subtitle parse/tokenization bursts
|
||||
- media generation (`ffmpeg` audio/image and AVIF paths)
|
||||
- media sync and subtitle tooling (`alass`, `ffsubsync`, `whisper` fallback path)
|
||||
- media sync and subtitle tooling (`alass`, `ffsubsync`)
|
||||
- `ankiConnect` enrichment (plus polling overhead when proxy mode is disabled)
|
||||
|
||||
### If playback feels sluggish
|
||||
@@ -57,7 +57,7 @@ SubMiner retries the connection automatically with increasing delays (200 ms, 50
|
||||
|
||||
- disable AI translation when not needed (`ankiConnect.ai.enabled: false`)
|
||||
- if needed, run immersion telemetry with lower duration expectations (`immersionTracking.enabled: false` for constrained sessions)
|
||||
- prefer YouTube `--mode automatic` over `preprocess` on low-resource systems
|
||||
- favor the default lightweight YouTube subtitle startup settings on low-resource systems
|
||||
|
||||
### Practical low-impact profile
|
||||
|
||||
|
||||
@@ -78,8 +78,6 @@ subminer mpv idle # Launch detached idle mpv with SubMiner defau
|
||||
subminer dictionary /path/to/file-or-directory # Generate character dictionary ZIP from target (manual Yomitan import)
|
||||
subminer texthooker # Launch texthooker-only mode
|
||||
subminer app --anilist # Pass args directly to SubMiner binary (example: AniList login flow)
|
||||
subminer yt -o ~/subs https://youtu.be/... # YouTube subcommand: output directory shortcut
|
||||
subminer yt --keep-temp --whisper-bin /path/to/whisper-cli --whisper-model /path/to/model.bin --whisper-vad-model /path/to/ggml-vad.bin https://youtu.be/... # Keep generated subtitle workspace for debugging
|
||||
|
||||
# Direct packaged app control
|
||||
SubMiner.AppImage --background # Start in background (tray + IPC wait, minimal logs)
|
||||
@@ -137,14 +135,13 @@ This flow requires `mpv.exe` to be on `PATH`. If it is installed elsewhere, set
|
||||
### Launcher Subcommands
|
||||
|
||||
- `subminer jellyfin` / `subminer jf`: Jellyfin-focused workflow aliases.
|
||||
- `subminer yt` / `subminer youtube`: YouTube-focused shorthand flags (`-o`, `--keep-temp`, `--whisper-*`).
|
||||
- `subminer doctor`: health checks for core dependencies and runtime paths.
|
||||
- `subminer config`: config helpers (`path`, `show`).
|
||||
- `subminer mpv`: mpv helpers (`status`, `socket`, `idle`).
|
||||
- `subminer dictionary <path>`: generates a Yomitan-importable character dictionary ZIP from a file/directory target.
|
||||
- `subminer texthooker`: texthooker-only shortcut (same behavior as `--texthooker`).
|
||||
- `subminer app` / `subminer bin`: direct passthrough to the SubMiner binary/AppImage.
|
||||
- Subcommand help pages are available (for example `subminer jellyfin -h`, `subminer yt -h`).
|
||||
- Subcommand help pages are available (for example `subminer jellyfin -h`).
|
||||
|
||||
### First-Run Setup
|
||||
|
||||
@@ -174,8 +171,8 @@ AniList character dictionary auto-sync (optional):
|
||||
- SubMiner syncs the currently watched AniList media into a per-media snapshot, then rebuilds one merged `SubMiner Character Dictionary` from the most recently used snapshots.
|
||||
- Rotation limit defaults to 3 recent media snapshots in that merged dictionary (`maxLoaded`).
|
||||
|
||||
Use subcommands for Jellyfin/YouTube command families (`subminer jellyfin ...`, `subminer yt ...`).
|
||||
Top-level launcher flags like `--jellyfin-*` and `--yt-subgen-*` are intentionally rejected.
|
||||
Use subcommands for Jellyfin workflows (`subminer jellyfin ...`).
|
||||
Top-level launcher flags like `--jellyfin-*` are intentionally rejected.
|
||||
|
||||
### MPV Profile Example (mpv.conf)
|
||||
|
||||
@@ -228,26 +225,18 @@ If you also use Yomitan in a browser, configure that browser profile separately;
|
||||
### YouTube Playback
|
||||
|
||||
`subminer` accepts direct URLs (for example, YouTube links) and `ytsearch:` targets.
|
||||
For YouTube playback, SubMiner now generates or downloads subtitle tracks before mpv starts, then launches mpv with the resolved subtitle files attached.
|
||||
For YouTube playback, SubMiner resolves subtitle selection during startup while mpv is paused: it auto-selects the default primary subtitle track plus a best-effort secondary track, then resumes when primary subtitles are ready.
|
||||
|
||||
Notes:
|
||||
|
||||
- Install `yt-dlp` so mpv can resolve YouTube streams and subtitle tracks reliably.
|
||||
- For YouTube URLs, `subminer` now generates any missing subtitles before mpv launch.
|
||||
- It probes manual/native YouTube subtitle tracks first, then falls back to local `whisper.cpp` only for missing tracks.
|
||||
- For YouTube URLs, startup no longer requires opening the picker first; SubMiner loads subtitles and keeps the overlay available for retries.
|
||||
- Press `Ctrl+Alt+C` during active YouTube playback to open the manual YouTube subtitle picker and retry track selection.
|
||||
- For YouTube URLs, `subminer` probes available YouTube subtitle tracks, reuses existing authoritative tracks when available, and downloads only missing sides.
|
||||
- Native mpv secondary subtitle rendering stays hidden so the overlay remains the visible secondary subtitle surface.
|
||||
- Primary subtitle target languages come from `youtubeSubgen.primarySubLanguages` (defaults to `["ja","jpn"]`).
|
||||
- Secondary target languages come from `secondarySub.secondarySubLanguages` (defaults to English if unset).
|
||||
- Whisper translation fallback currently only supports English secondary targets; non-English secondary targets rely on native/manual subtitle availability.
|
||||
- Optional AI cleanup for whisper-generated subtitles is controlled by `youtubeSubgen.fixWithAi` plus the shared top-level `ai` config (with optional `youtubeSubgen.ai` overrides).
|
||||
- Configure defaults in `$XDG_CONFIG_HOME/SubMiner/config.jsonc` (or `~/.config/SubMiner/config.jsonc`) under `youtubeSubgen`, `secondarySub`, and `ai`.
|
||||
- CLI overrides are available through `subminer yt` / `subminer youtube`:
|
||||
- `-o, --out-dir`
|
||||
- `--keep-temp`
|
||||
- `--whisper-bin`
|
||||
- `--whisper-model`
|
||||
- `--whisper-vad-model`
|
||||
- `--whisper-threads`
|
||||
- `--yt-subgen-audio-format`
|
||||
- Configure defaults in `$XDG_CONFIG_HOME/SubMiner/config.jsonc` (or `~/.config/SubMiner/config.jsonc`) under `youtubeSubgen` and `secondarySub`.
|
||||
|
||||
## Controller Support
|
||||
|
||||
|
||||
Reference in New Issue
Block a user