Update docs and gitignore changes

This commit is contained in:
2026-02-16 19:26:34 -08:00
parent 48f93f4344
commit 23b78e6c9b
4 changed files with 179 additions and 267 deletions

2
.gitignore vendored
View File

@@ -29,7 +29,7 @@ environment.toml
.vitepress/dist/ .vitepress/dist/
docs/.vitepress/cache/ docs/.vitepress/cache/
docs/.vitepress/dist/ docs/.vitepress/dist/
test/* tests/*
.worktrees/ .worktrees/
.codex/* .codex/*
.agents/* .agents/*

149
README.md
View File

@@ -1,64 +1,37 @@
<div align="center"> <div align="center">
<img src="assets/SubMiner.png" width="169" alt="SubMiner logo"> <img src="assets/SubMiner.png" width="169" alt="SubMiner logo">
<h1>SubMiner</h1> <h1>SubMiner</h1>
<p>Immersion mining overlay for mpv — look up words, mine to Anki, and enrich cards with context without leaving the video.</p>
</div> </div>
An all-in-one immersion mining overlay for MPV with AnkiConnect and dictionary (Yomitan) integration. ---
## What This Project Is For [![Demo](./assets/demo-poster.jpg)](https://github.com/user-attachments/assets/9235a554-ea51-4284-b14b-7bbf3defaf58)
SubMiner is for Japanese learners who watch subtitled content in mpv and want a low-friction mining loop:
- stay inside the video while doing lookups
- mine to Anki quickly without manual copy/paste workflows
- preserve card context (sentence, audio, screenshot, translation, metadata)
- reduce tool switching between player, dictionary, and card workflow
## Project Goals
1. Keep immersion continuous by making lookup and mining happen over mpv subtitles.
2. Preserve card quality with context-rich media and subtitle timing.
3. Support real daily workflows (subtitle management, sync, known-word awareness, keyboard-first controls).
4. Stay configurable with sensible defaults and advanced customization.
5. Evolve quickly and safely with a TypeScript codebase and automated tests that make refactors easier to ship.
## Who It's For
- learners using mpv as their primary immersion player
- users already working with Yomitan and AnkiConnect
- miners who care about long-term card quality, not just quick word capture
SubMiner is likely overkill if you only want lightweight dictionary lookup without card enrichment or integrated workflow tools.
## Features ## Features
- Real-time subtitle display from MPV via IPC socket - **Yomitan Integration** — Hover subtitle words to trigger dictionary lookups in the player
- Yomitan integration for fast, on-screen lookups - **Anki Card Enrichment** — Fills sentence, audio, screenshot, and translation on new cards automatically
- Japanese text tokenization using MeCab with smart word boundary detection - **Dual-Layer Subtitles** — Interactive visible overlay + invisible click-through layer aligned with mpv rendering
- Integrated texthooker-ui server for use with Yomitan - **N+1 Highlighting** — Marks known vocabulary from your Anki deck so you can spot new words at a glance
- Integrated WebSocket server (if [mpv_websocket](https://github.com/kuroahna/mpv_websocket) is not found) to send lines to the texthooker - **Texthooker & WebSocket** — Built-in texthooker page with WebSocket streaming for external tools
- AnkiConnect integration for automatic card creation with media (audio/image) - **Subtitle Download & Sync** — Search Jimaku, sync with alass or ffsubsync — all from the player
- Invisible subtitle position edit mode (`Ctrl/Cmd+Shift+P`, arrow keys to adjust, `Enter`/`Ctrl+S` save, `Esc` cancel) - **Keyboard-Driven** — Mine, copy, cycle display modes, and navigate from configurable shortcuts
- **Japanese Tokenization** — MeCab-powered word boundary detection with smart grouping
## Demo
[![Demo screenshot](./assets/demo-poster.jpg)](https://github.com/user-attachments/assets/9235a554-ea51-4284-b14b-7bbf3defaf58)
## Requirements ## Requirements
- `mpv` with IPC socket support - `mpv` with IPC socket support
- `mecab` and `mecab-ipadic` (recommended for Japanese tokenization) - `mecab` and `mecab-ipadic`
- Linux: Hyprland (`hyprctl`) or X11 (`xdotool` + `xwininfo`) - Linux: Hyprland (`hyprctl`) or X11 (`xdotool` + `xwininfo`)
- macOS: Accessibility permission for window tracking - macOS: Accessibility permission for window tracking
Optional but recommended: `yt-dlp`, `fzf`, `rofi`, `chafa`, `ffmpegthumbnailer`. Optional: `yt-dlp`, `fzf`, `rofi`, `chafa`, `ffmpegthumbnailer`
## Install ## Install
### Linux (AppImage) ### Linux (AppImage)
Download the latest release from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest):
```bash ```bash
wget https://github.com/ksyasuda/SubMiner/releases/download/v0.1.0/SubMiner-0.1.0.AppImage -O ~/.local/bin/SubMiner.AppImage wget https://github.com/ksyasuda/SubMiner/releases/download/v0.1.0/SubMiner-0.1.0.AppImage -O ~/.local/bin/SubMiner.AppImage
chmod +x ~/.local/bin/SubMiner.AppImage chmod +x ~/.local/bin/SubMiner.AppImage
@@ -73,49 +46,34 @@ The `subminer` wrapper uses a [Bun](https://bun.sh) shebang, so `bun` must be on
```bash ```bash
git clone --recurse-submodules https://github.com/ksyasuda/SubMiner.git git clone --recurse-submodules https://github.com/ksyasuda/SubMiner.git
cd SubMiner cd SubMiner
make build make build && make install
make install
``` ```
If you already cloned without submodules: For macOS builds and platform details, see the [installation docs](docs/installation.md).
```bash
cd SubMiner
git submodule update --init --recursive
```
For macOS builds, signing, and platform-specific details, see [docs/installation.md](docs/installation.md).
## Quick Start ## Quick Start
1. Copy and customize [`config.example.jsonc`](config.example.jsonc) to `$XDG_CONFIG_HOME/SubMiner/config.jsonc` (or `~/.config/SubMiner/config.jsonc`). 1. Copy [`config.example.jsonc`](config.example.jsonc) to `~/.config/SubMiner/config.jsonc`
2. Start mpv with IPC enabled: 2. Start mpv with IPC:
```bash
```bash mpv --input-ipc-server=/tmp/subminer-socket video.mkv
mpv --input-ipc-server=/tmp/subminer-socket video.mkv ```
```
3. Launch SubMiner: 3. Launch SubMiner:
```bash
subminer video.mkv
```
```bash ```bash
subminer video.mkv subminer # pick video from cwd (fzf)
# or subminer -R # rofi picker
subminer https://youtu.be/... subminer -d ~/Videos # set source directory
subminer -r -d ~/Anime # recursive search
subminer -p gpu-hq video.mkv # override mpv profile
subminer -T video.mkv # disable texthooker
subminer https://youtu.be/... # YouTube playback
``` ```
## Common Commands ## MPV Plugin
```bash
subminer # pick video from current dir (fzf)
subminer -R # use rofi picker
subminer -d ~/Videos # set source directory
subminer -r -d ~/Anime # recursive search
subminer video.mkv # launch with default mpv profile (subminer)
subminer -p gpu-hq video.mkv # override mpv profile
subminer -T video.mkv # disable texthooker
```
## MPV Plugin (Optional)
```bash ```bash
cp plugin/subminer.lua ~/.config/mpv/scripts/ cp plugin/subminer.lua ~/.config/mpv/scripts/
@@ -123,46 +81,23 @@ cp plugin/subminer.conf ~/.config/mpv/script-opts/
# or: make install-plugin # or: make install-plugin
``` ```
Requires mpv IPC: `--input-ipc-server=/tmp/subminer-socket` Default chord prefix: `y` (`y-y` menu, `y-s` start, `y-S` stop, `y-t` toggle overlay).
Jimaku shortcut: `Ctrl+Shift+J`.
Default chord prefix: `y` (`y-y` menu, `y-s` start, `y-S` stop, `y-t` toggle visible layer).
Overlay Jimaku shortcut default: `Ctrl+Shift+J` (`shortcuts.openJimaku`).
## Documentation ## Documentation
Detailed guides live in [`docs/`](docs/README.md): Full guides at [**docs/**](docs/README.md):
[Installation](docs/installation.md) · [Usage](docs/usage.md) · [Mining Workflow](docs/mining-workflow.md) · [Configuration](docs/configuration.md) · [Anki Integration](docs/anki-integration.md) · [MPV Plugin](docs/mpv-plugin.md) · [Troubleshooting](docs/troubleshooting.md) · [Architecture](docs/architecture.md)
- [Installation](docs/installation.md) — Platform requirements, AppImage/macOS/source installs, mpv plugin ## Acknowledgments
- [Usage](docs/usage.md) — Script vs plugin workflow, keybindings, YouTube playback
- [Mining Workflow](docs/mining-workflow.md) — End-to-end mining guide, overlay layers, card creation
- [Configuration](docs/configuration.md) — Full config reference and option details
- [Anki Integration](docs/anki-integration.md) — AnkiConnect setup, field mapping, media generation
- [MPV Plugin](docs/mpv-plugin.md) — Chord keybindings, subminer.conf options, script messages
- [Troubleshooting](docs/troubleshooting.md) — Common issues and solutions
- [Development](docs/development.md) — Building, testing, contributing
- [Architecture](docs/architecture.md) — Service-oriented design, composition model, and modular renderer layout (`src/renderer/{modals,handlers,utils,...}`)
### Third-Party Components - [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner) — Inspiration for the overlay and Yomitan integration
- [Jimaku.cc](https://jimaku.cc) — Japanese subtitle provider
This project includes the following third-party components: - [mpvacious](https://github.com/Ajatt-Tools/mpvacious), [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script), [autosubsync-mpv](https://github.com/joaquintorres/autosubsync-mpv) — Mining and sync logic ported to TypeScript
- **[Yomitan](https://github.com/yomidevs/yomitan)** - Pop-up dictionary
- **[texthooker-ui](https://github.com/ksyasuda/texthooker-ui/tree/subminer)** - Texthooker Page
- **[yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab)** - JLPT Yomitan Dictionary
- **[Jiten Frequency Dictionary](https://jiten.moe/)** - Frequency Dictionary
### Acknowledgments
- **[GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner)** — Inspiration for the subtitle overlay and Yomitan integration
- **[Jimaku.cc](https://jimaku.cc)** — Japanese subtitle provider
This project cherry-picks features from the following MPV scripts, ported to TypeScript:
- **[mpvacious](https://github.com/Ajatt-Tools/mpvacious)** — Sentence mining, screenshotting, and card updating logic
- **[Anacreon-Script (animecards)](https://github.com/friedrich-de/Anacreon-Script)** — Copy/paste to card update flow
- **[autosubsync-mpv](https://github.com/joaquintorres/autosubsync-mpv)** — Subtitle synchronization
**Third-party:**
[Yomitan](https://github.com/yomidevs/yomitan) · [texthooker-ui](https://github.com/ksyasuda/texthooker-ui/tree/subminer) · [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab) · [Jiten Frequency Dictionary](https://jiten.moe/)
## License ## License
GNU General Public License v3.0. See [LICENSE](LICENSE). [GNU General Public License v3.0](LICENSE)

View File

@@ -84,53 +84,63 @@ src/renderer/
## Flow Diagram ## Flow Diagram
The main process has three layers: `main.ts` delegates to composition modules that wire together domain services. The renderer runs in a separate Electron process, connected through `preload.ts`.
```mermaid ```mermaid
flowchart TD flowchart TD
classDef root fill:#c6a0f6,stroke:#24273a,color:#24273a,stroke-width:2px classDef entry fill:#c6a0f6,stroke:#363a4f,color:#24273a,stroke-width:2px
classDef comp fill:#b7bdf8,stroke:#24273a,color:#24273a,stroke-width:1.5px classDef comp fill:#b7bdf8,stroke:#363a4f,color:#24273a,stroke-width:1.5px
classDef svc fill:#8aadf4,stroke:#24273a,color:#24273a,stroke-width:1.5px classDef svc fill:#8aadf4,stroke:#363a4f,color:#24273a,stroke-width:1.5px
classDef ext fill:#a6da95,stroke:#24273a,color:#24273a,stroke-width:1.5px classDef bridge fill:#f5a97f,stroke:#363a4f,color:#24273a,stroke-width:1.5px
classDef rend fill:#8bd5ca,stroke:#363a4f,color:#24273a,stroke-width:1.5px
classDef ext fill:#a6da95,stroke:#363a4f,color:#24273a,stroke-width:1.5px
Main["src/main.ts"]:::root Main["main.ts"]:::entry
subgraph Composition["Composition Modules"] subgraph Comp["Composition — src/main/"]
Startup["Startup & Lifecycle"]:::comp Startup["Startup & Lifecycle<br/>startup · app-lifecycle<br/>startup-lifecycle · state"]:::comp
IpcCli["IPC & CLI Wiring"]:::comp Wiring["Runtime Wiring<br/>ipc-runtime · cli-runtime<br/>overlay-runtime · subsync-runtime"]:::comp
Overlay["Overlay & Shortcuts"]:::comp
Subsync["Subsync"]:::comp
end end
subgraph Services["Domain Services"] subgraph Svc["Services — src/core/services/"]
OverlaySvc["Overlay Services"]:::svc Mpv["MPV Stack<br/>transport · protocol<br/>state · properties"]:::svc
MpvSvc["MPV Stack"]:::svc Overlay["Overlay<br/>manager · window<br/>visibility · bridge"]:::svc
MiningSvc["Mining & Subtitles"]:::svc Mining["Mining & Subtitles<br/>mining · field-grouping<br/>subtitle-ws · tokenizer"]:::svc
ShortcutIpc["Shortcuts & IPC"]:::svc Integrations["Integrations<br/>jimaku · subsync<br/>texthooker · yomitan"]:::svc
end end
subgraph External["External Boundaries"] Bridge(["preload.ts — Electron IPC"]):::bridge
Config["Config & CLI"]:::ext
Trackers["Window Trackers"]:::ext subgraph Rend["Renderer — src/renderer/"]
Integrations["Jimaku & Subsync"]:::ext Orchestration["renderer.ts<br/>orchestration · IPC wiring"]:::rend
UI["subtitle-render · positioning<br/>handlers · modals"]:::rend
end end
Main --> Startup subgraph Ext["External Systems"]
Main --> IpcCli mpv["mpv"]:::ext
Main --> Overlay Anki["AnkiConnect"]:::ext
Main --> Subsync Jimaku["Jimaku API"]:::ext
Tracker["Window Tracker"]:::ext
end
Startup --> OverlaySvc Main -->|delegates| Comp
IpcCli --> ShortcutIpc Startup -->|initializes| Svc
Overlay --> OverlaySvc Wiring -->|dispatches to| Svc
Overlay --> MpvSvc
Subsync --> Integrations
OverlaySvc --> Trackers Overlay <--> Bridge
MpvSvc --> MiningSvc Mining <--> Bridge
ShortcutIpc --> Config Bridge <--> Orchestration
Orchestration --> UI
style Composition fill:#363a4f,stroke:#494d64,color:#cad3f5 Mpv <-->|JSON socket| mpv
style Services fill:#363a4f,stroke:#494d64,color:#cad3f5 Mining -->|HTTP| Anki
style External fill:#363a4f,stroke:#494d64,color:#cad3f5 Integrations -->|HTTP| Jimaku
Overlay --> Tracker
style Comp fill:#363a4f,stroke:#494d64,color:#cad3f5
style Svc fill:#363a4f,stroke:#494d64,color:#cad3f5
style Rend fill:#363a4f,stroke:#494d64,color:#cad3f5
style Ext fill:#363a4f,stroke:#494d64,color:#cad3f5
``` ```
## Composition Pattern ## Composition Pattern
@@ -154,43 +164,53 @@ The composition root (`src/main.ts`) delegates to focused modules in `src/main/`
This keeps side effects explicit and makes behavior easy to unit-test with fakes. This keeps side effects explicit and makes behavior easy to unit-test with fakes.
## Lifecycle Model ## Program Lifecycle
- **Startup:** - **Startup:** `startup.ts` parses CLI args and detects the compositor backend. If `--generate-config` is passed, it writes the template and exits. Otherwise `app-lifecycle.ts` acquires the single-instance lock and registers Electron lifecycle hooks.
- `src/main/startup.ts` (`startup-service`) handles initial argv/env/backend setup and decides generate-config flow vs app lifecycle start. - **Initialization:** Once `app.whenReady()` fires, `startup-lifecycle.ts` loads config, resolves keybindings, creates the mpv client, initializes the MeCab tokenizer, starts the window tracker, and applies WebSocket policy — then creates the overlay window and establishes the IPC bridge.
- `src/main/app-lifecycle.ts` (`app-lifecycle-service`) handles Electron single-instance + lifecycle event registration. - **Runtime:** Event-driven. mpv property changes, IPC messages, CLI commands, and keyboard shortcuts all route through the composition layer to domain services, which update state and broadcast to the renderer.
- `src/main/startup-lifecycle.ts` performs ready-time initialization (config load, websocket policy, tokenizer/tracker setup, overlay auto-init decisions). - **Shutdown:** Electron's `will-quit` triggers service teardown — closes the mpv socket, unregisters shortcuts, stops WebSocket and texthooker servers, destroys the window tracker, and cleans up Anki state.
- **Runtime:**
- CLI/shortcut/IPC events map to service calls through `src/main/cli-runtime.ts`, `src/main/ipc-runtime.ts`, and `src/main/overlay-runtime.ts`.
- Overlay and MPV state sync through dedicated services.
- Runtime options and mining flows are coordinated via service boundaries.
- **Shutdown:**
- `app-lifecycle-service` registers cleanup hooks (`will-quit`) while teardown behavior stays delegated to focused services from `src/main/*` composition modules.
```mermaid ```mermaid
flowchart TD flowchart TD
classDef phase fill:#b7bdf8,stroke:#24273a,color:#24273a,stroke-width:1.5px classDef start fill:#c6a0f6,stroke:#363a4f,color:#24273a,stroke-width:2px
classDef decision fill:#f5a97f,stroke:#24273a,color:#24273a,stroke-width:1.5px classDef phase fill:#b7bdf8,stroke:#363a4f,color:#24273a,stroke-width:1.5px
classDef runtime fill:#8aadf4,stroke:#24273a,color:#24273a,stroke-width:1.5px classDef decision fill:#f5a97f,stroke:#363a4f,color:#24273a,stroke-width:1.5px
classDef shutdown fill:#a6da95,stroke:#24273a,color:#24273a,stroke-width:1.5px classDef init fill:#8aadf4,stroke:#363a4f,color:#24273a,stroke-width:1.5px
classDef runtime fill:#8bd5ca,stroke:#363a4f,color:#24273a,stroke-width:1.5px
classDef shutdown fill:#ed8796,stroke:#363a4f,color:#24273a,stroke-width:1.5px
Args["CLI args / env"]:::phase --> Startup["src/main/startup.ts"]:::phase CLI["CLI args & environment"]:::start
CLI --> Parse["startup.ts<br/>Parse argv · detect backend · resolve config"]:::phase
Parse --> GenCheck{"--generate-config?"}:::decision
GenCheck -->|yes| GenExit["Write config template & exit"]:::phase
GenCheck -->|no| Lifecycle["app-lifecycle.ts<br/>Acquire single-instance lock<br/>Register Electron lifecycle hooks"]:::phase
Lifecycle -->|"app.whenReady()"| Ready["startup-lifecycle.ts"]:::phase
Startup --> Decision{"generate-config?"}:::decision Ready --> Init
subgraph Init["Initialization"]
direction LR
Config["Load config<br/>resolve keybindings"]:::init
Runtime["Create mpv client<br/>init MeCab tokenizer"]:::init
Platform["Start window tracker<br/>WebSocket policy"]:::init
end
Decision -->|yes| WriteConfig["Write config + exit"]:::phase Init --> Create["Create overlay window<br/>Establish IPC bridge<br/>Load Yomitan extension"]:::phase
Decision -->|no| AppLifecycle["src/main/app-lifecycle.ts"]:::phase
AppLifecycle --> Ready["src/main/startup-lifecycle.ts\nConfig · WebSocket · Tracker · Tokenizer · State"]:::phase Create --> Loop
subgraph Loop["Runtime — event-driven"]
direction LR
Events["mpv · IPC · CLI<br/>shortcut events"]:::runtime
Dispatch["Route to service<br/>via composition layer"]:::runtime
State["Update state<br/>broadcast to renderer"]:::runtime
Events --> Dispatch --> State
end
Ready --> Runtime["Runtime Modules\nipc · cli · overlay · subsync"]:::runtime Loop -->|"app close"| Quit["Electron will-quit"]:::shutdown
Quit --> Teardown["Close mpv socket · unregister shortcuts<br/>Stop WebSocket & texthooker<br/>Destroy tracker · clean Anki state"]:::shutdown
Runtime --> Overlay["Overlay & Mining"]:::runtime style Init fill:#363a4f,stroke:#494d64,color:#cad3f5
Runtime --> Subtitle["Subtitle Processing"]:::runtime style Loop fill:#363a4f,stroke:#494d64,color:#cad3f5
Runtime --> SubsyncInt["Subsync & Jimaku"]:::runtime
Runtime --> WillQuit["Electron will-quit"]:::shutdown
WillQuit --> Cleanup["Service Teardown"]:::shutdown
``` ```
## Why This Design ## Why This Design

View File

@@ -6,8 +6,8 @@ titleTemplate: Immersion Mining Workflow for MPV
hero: hero:
name: SubMiner name: SubMiner
text: Built for Immersion Mining text: Immersion Mining for MPV
tagline: A self-contained MPV overlay for Japanese study. Look up words, mine cards, and enrich Anki without breaking playback flow. tagline: Look up words, mine to Anki, and enrich cards with context — all without leaving the video.
image: image:
src: /assets/SubMiner.png src: /assets/SubMiner.png
alt: SubMiner logo alt: SubMiner logo
@@ -18,80 +18,80 @@ hero:
- theme: alt - theme: alt
text: Mining Workflow text: Mining Workflow
link: /mining-workflow link: /mining-workflow
- theme: alt
text: Is This For Me?
link: "#who-this-is-for"
features: features:
- icon: - icon:
src: /assets/mpv.svg src: /assets/mpv.svg
alt: mpv icon alt: mpv icon
title: Built for mpv title: Built for mpv
details: Connects directly to mpv over IPC tracks subtitles in real time, observes playback properties, and renders a self-contained overlay with everything bundled in a single application. details: Connects via IPC to track subtitles in real time and render a self-contained overlay everything bundled in a single application.
- icon: - icon:
src: /assets/yomitan-icon.svg src: /assets/yomitan-icon.svg
alt: Yomitan logo alt: Yomitan logo
title: Yomitan Integration title: Yomitan Integration
details: Hover over any word in the subtitles to trigger Yomitan dictionary lookups — get instant definitions without leaving the video player. details: Hover over any word in the subtitle overlay to trigger dictionary lookups — instant definitions without leaving the player.
- icon: - icon:
src: /assets/anki-card.svg src: /assets/anki-card.svg
alt: Anki card icon alt: Anki card icon
title: Anki Card Enrichment title: Anki Card Enrichment
details: Add a word from Yomitan and SubMiner automatically updates the card with the sentence, audio clip, screenshot, and translation — no extra steps needed. details: Add a word from Yomitan and SubMiner fills in the sentence, audio clip, screenshot, and translation automatically.
- icon: - icon:
src: /assets/dual-layer.svg src: /assets/dual-layer.svg
alt: Dual layer icon alt: Dual layer icon
title: Dual-Layer Subtitle System title: Dual-Layer Subtitles
details: Visible overlay with styled, interactive subtitles — plus an invisible layer that aligns with mpv's own subtitle rendering for seamless click-through lookup. details: Interactive visible overlay plus an invisible layer aligned with mpv's own rendering for seamless click-through lookup.
- icon: - icon:
src: /assets/highlight.svg src: /assets/highlight.svg
alt: Highlight icon alt: Highlight icon
title: N+1 Word Highlighting title: N+1 Highlighting
details: Highlights words you already know from your Anki deck, making it easy to spot new vocabulary and identify true N+1 sentences during immersion. details: Marks words you already know from your Anki deck so you can spot new vocabulary and identify N+1 sentences at a glance.
- icon: - icon:
src: /assets/texthooker.svg src: /assets/texthooker.svg
alt: Texthooker icon alt: Texthooker icon
title: Texthooker & WebSocket title: Texthooker & WebSocket
details: Built-in texthooker page that receives subtitles over WebSocket — use it as a clipboard inserter for Yomitan or connect external tools for real-time subtitle streaming. details: Built-in texthooker page that receives subtitles over WebSocket — use it as a clipboard inserter or connect external tools.
- icon: - icon:
src: /assets/subtitle-download.svg src: /assets/subtitle-download.svg
alt: Subtitle download icon alt: Subtitle download icon
title: Subtitle Download & Sync title: Subtitle Download & Sync
details: Search and download Japanese subtitles from Jimaku, then sync them to the audio with alass or ffsubsync — all from within the player. details: Search and download Japanese subtitles from Jimaku, then sync to audio with alass or ffsubsync — all from within the player.
- icon: - icon:
src: /assets/keyboard.svg src: /assets/keyboard.svg
alt: Keyboard icon alt: Keyboard icon
title: Keyboard-Driven Workflow title: Keyboard-Driven
details: Mine sentences, copy subtitles, cycle display modes, and trigger field grouping — all from configurable keyboard shortcuts without touching the mouse. details: Mine sentences, copy subtitles, cycle display modes, and trigger field grouping — all from configurable shortcuts.
--- ---
<style> <style>
.demo-section { .demo-section {
max-width: 960px; max-width: 960px;
margin: 2rem auto 0; margin: 0 auto;
padding: 0 24px; padding: 0 24px;
} }
.demo-section h2 { .demo-section h2 {
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 600; font-weight: 600;
margin-bottom: 0.75rem; margin-bottom: 0.5rem;
letter-spacing: -0.01em;
} }
.demo-section p { .demo-section p {
color: var(--vp-c-text-2); color: var(--vp-c-text-2);
margin-bottom: 1rem; margin-bottom: 1.25rem;
line-height: 1.6;
} }
.demo-section video { .demo-section video {
width: 100%; width: 100%;
border-radius: 8px; border-radius: 12px;
border: 1px solid var(--vp-c-divider); border: 1px solid var(--vp-c-divider);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
} }
.workflow-section { .workflow-section {
max-width: 960px; max-width: 960px;
margin: 3rem auto 0; margin: 4rem auto 0;
padding: 0 24px 3rem; padding: 0 24px 3rem;
} }
@@ -99,106 +99,52 @@ features:
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 600; font-weight: 600;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
letter-spacing: -0.01em;
} }
.workflow-steps { .workflow-steps {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-template-columns: repeat(4, 1fr);
gap: 1rem; gap: 1px;
background: var(--vp-c-divider);
border-radius: 12px;
overflow: hidden;
}
@media (max-width: 768px) {
.workflow-steps {
grid-template-columns: 1fr;
}
} }
.workflow-step { .workflow-step {
padding: 1rem; padding: 1.25rem 1.5rem;
border-radius: 8px;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft); background: var(--vp-c-bg-soft);
} }
.workflow-step .step-number { .workflow-step .step-number {
font-size: 0.8rem; display: inline-block;
font-size: 0.7rem;
font-weight: 700; font-weight: 700;
letter-spacing: 0.05em;
color: var(--vp-c-brand-1); color: var(--vp-c-brand-1);
margin-bottom: 0.25rem; margin-bottom: 0.5rem;
font-variant-numeric: tabular-nums;
} }
.workflow-step .step-title { .workflow-step .step-title {
font-weight: 600; font-weight: 600;
margin-bottom: 0.25rem; font-size: 1rem;
margin-bottom: 0.35rem;
} }
.workflow-step .step-desc { .workflow-step .step-desc {
font-size: 0.875rem; font-size: 0.85rem;
color: var(--vp-c-text-2); color: var(--vp-c-text-2);
line-height: 1.5;
} }
</style> </style>
<div class="demo-section">
## What SubMiner Is For
SubMiner is for people who learn Japanese by watching subtitled content in mpv and want a low-friction mining loop:
- stay inside the video while looking up words
- send mined content to Anki quickly
- keep media context (audio, screenshot, timestamp, subtitle context) attached to each card
- reduce tool switching between player, dictionary, and card workflow
</div>
<div class="workflow-section">
## Project Goals
<div class="workflow-steps">
<div class="workflow-step">
<div class="step-title">1. Keep Immersion Continuous</div>
<div class="step-desc">Minimize context switching by making lookup and mining happen directly over mpv subtitles.</div>
</div>
<div class="workflow-step">
<div class="step-title">2. Preserve Card Quality</div>
<div class="step-desc">Attach sentence context, audio, image, and translation so mined cards stay reviewable and useful long-term.</div>
</div>
<div class="workflow-step">
<div class="step-title">3. Support Real Workflows</div>
<div class="step-desc">Handle day-to-day immersion needs: subtitle management, syncing, known-word awareness, and keyboard-first controls.</div>
</div>
<div class="workflow-step">
<div class="step-title">4. Stay Configurable</div>
<div class="step-desc">Offer defaults that work out of the box, while still letting advanced users shape behavior around their note type and setup.</div>
</div>
<div class="workflow-step">
<div class="step-title">5. Evolve Safely</div>
<div class="step-desc">Use a modular TypeScript codebase and automated tests so features can ship faster without breaking core mining behavior.</div>
</div>
</div>
</div>
<div class="demo-section">
## See It in Action
SubMiner sits as a transparent overlay on top of mpv. Subtitles appear as interactive, clickable text — click a word to look it up with Yomitan, then add it to Anki with one click.
<video controls playsinline preload="metadata" poster="/assets/demo-poster.jpg">
<source :src="'/assets/card-mine.webm'" type="video/webm" />
Your browser does not support the video tag.
</video>
</div>
<div class="workflow-section">
## Who This Is For
- learners using mpv as their main immersion player
- users who already rely on Yomitan + AnkiConnect
- miners who care about preserving context on cards, not just raw words
SubMiner is likely overkill if you only want lightweight lookup without card enrichment, overlay controls, or integrated workflow tooling.
</div>
<div class="workflow-section"> <div class="workflow-section">
## How It Works ## How It Works
@@ -212,7 +158,7 @@ SubMiner is likely overkill if you only want lightweight lookup without card enr
<div class="workflow-step"> <div class="workflow-step">
<div class="step-number">02</div> <div class="step-number">02</div>
<div class="step-title">Look Up</div> <div class="step-title">Look Up</div>
<div class="step-desc">Hover over a word in the subtitle overlay and hold Shift to trigger a Yomitan dictionary lookup.</div> <div class="step-desc">Hover over a word in the subtitle overlay and hold Shift to trigger a Yomitan lookup.</div>
</div> </div>
<div class="workflow-step"> <div class="workflow-step">
<div class="step-number">03</div> <div class="step-number">03</div>
@@ -227,3 +173,14 @@ SubMiner is likely overkill if you only want lightweight lookup without card enr
</div> </div>
</div> </div>
<div class="demo-section">
## See It in Action
<video controls playsinline preload="metadata" poster="/assets/demo-poster.jpg">
<source :src="'/assets/card-mine.webm'" type="video/webm" />
Your browser does not support the video tag.
</video>
</div>