mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
Update docs and gitignore changes
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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
149
README.md
@@ -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
|
[](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
|
|
||||||
|
|
||||||
[](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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
153
docs/index.md
153
docs/index.md
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user