diff --git a/README.md b/README.md
index ae8f009..2520e3c 100644
--- a/README.md
+++ b/README.md
@@ -90,6 +90,7 @@ cp plugin/subminer.conf ~/.config/mpv/script-opts/
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 visible layer).
+Overlay Jimaku shortcut default: `Ctrl+Alt+J` (`shortcuts.openJimaku`).
## Documentation
diff --git a/docs/configuration.md b/docs/configuration.md
index 0573166..f5da729 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -4,7 +4,7 @@ Settings are stored in `~/.config/SubMiner/config.jsonc`
### Configuration File
-See `config.example.jsonc` for a comprehensive example configuration file with all available options, default values, and detailed comments. Only include the options you want to customize in your config file.
+See [config.example.jsonc](/config.example.jsonc) for a comprehensive example configuration file with all available options, default values, and detailed comments. Only include the options you want to customize in your config file.
Generate a fresh default config from the centralized config registry:
@@ -17,7 +17,7 @@ subminer.AppImage --generate-config --backup-overwrite
- `--generate-config` writes a default JSONC config template.
- If the target file exists, SubMiner prompts to create a timestamped backup and overwrite.
- In non-interactive shells, use `--backup-overwrite` to explicitly back up and overwrite.
-- `pnpm run generate:config-example` regenerates repository `config.example.jsonc` from the same centralized defaults.
+- `pnpm run generate:config-example` regenerates both repository `config.example.jsonc` and docs-served `/config.example.jsonc` from the same centralized defaults.
- `make generate-config` builds and runs the same default-config generator via local Electron.
Invalid config values are handled with warn-and-fallback behavior: SubMiner logs the bad key/value and continues with the default for that option.
@@ -164,7 +164,12 @@ When enabled, sentence cards automatically set `IsSentenceCard` to `"x"` and pop
Kiku extends Lapis with **field grouping** — when a duplicate card is detected (same Word/Expression), SubMiner merges the two cards' content into one using Kiku's `data-group-id` HTML structure, organizing each mining instance into separate pages within the note.
-[](https://github.com/user-attachments/assets/bf2476cb-2351-4622-8143-c90e59b19213)
+
+
+Open demo in a new tab
| Mode | Behavior |
@@ -416,6 +421,7 @@ See `config.example.jsonc` for detailed configuration options.
"mineSentenceMultiple": "CommandOrControl+Shift+S",
"markAudioCard": "CommandOrControl+Shift+A",
"openRuntimeOptions": "CommandOrControl+Shift+O",
+ "openJimaku": "Ctrl+Alt+J",
"multiCopyTimeoutMs": 3000
}
}
@@ -436,6 +442,7 @@ See `config.example.jsonc` for detailed configuration options.
| `toggleSecondarySub` | string \| `null` | Accelerator for cycling secondary subtitle mode (default: `"CommandOrControl+Shift+V"`) |
| `markAudioCard` | string \| `null` | Accelerator for marking last card as audio card (default: `"CommandOrControl+Shift+A"`) |
| `openRuntimeOptions` | string \| `null` | Opens runtime options palette for live session-only toggles (default: `"CommandOrControl+Shift+O"`) |
+| `openJimaku` | string \| `null` | Opens the Jimaku search modal (default: `"Ctrl+Alt+J"`) |
**See `config.example.jsonc`** for the complete list of shortcut configuration options.
diff --git a/docs/installation.md b/docs/installation.md
index c642334..3b84f23 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -6,14 +6,14 @@
- Hyprland (uses `hyprctl`)
- X11 (uses `xdotool` and `xwininfo`)
- mpv (with IPC socket support)
-- mecab and mecab-ipadic (Japanese morphological analyzer)
+- mecab and mecab-ipadic (optional fallback Japanese morphological analyzer)
- fuse2 (for AppImage support)
### macOS
- macOS 10.13 or later
- mpv (with IPC socket support)
-- mecab and mecab-ipadic (Japanese morphological analyzer) - optional
+- mecab and mecab-ipadic (optional fallback Japanese morphological analyzer)
- **Accessibility permission** required for window tracking (see [macOS Installation](#macos-installation))
**Optional:**
@@ -154,8 +154,9 @@ binary_path=/Applications/SubMiner.app/Contents/MacOS/subminer
The Lua plugin allows you to control the overlay directly from mpv using keybindings:
-> [!IMPORTANT]
-> `mpv` must be launched with `--input-ipc-server=/tmp/subminer-socket` to allow communication with the application
+::: warning Important
+`mpv` must be launched with `--input-ipc-server=/tmp/subminer-socket` to allow communication with the application.
+:::
```bash
# Copy plugin files to mpv config
@@ -182,6 +183,8 @@ All keybindings use chord sequences starting with `y`:
The menu provides options to start/stop/toggle the visible or invisible overlay layers and open settings. Type to filter or use arrow keys to navigate.
+Jimaku modal shortcut is configured separately in SubMiner overlay shortcuts (`shortcuts.openJimaku`), default `Ctrl+Alt+J`.
+
#### Plugin Configuration
Edit `~/.config/mpv/script-opts/subminer.conf`:
@@ -243,4 +246,3 @@ Launch mpv with:
```bash
mpv --input-ipc-server=\\\\.\\pipe\\subminer-socket video.mkv
```
-
diff --git a/docs/public/config.example.jsonc b/docs/public/config.example.jsonc
new file mode 100644
index 0000000..a1aebff
--- /dev/null
+++ b/docs/public/config.example.jsonc
@@ -0,0 +1,211 @@
+/**
+ * SubMiner Example Configuration File
+ *
+ * This file is auto-generated from src/config/definitions.ts.
+ * Copy to ~/.config/SubMiner/config.jsonc and edit as needed.
+ */
+{
+
+ // ==========================================
+ // Overlay Auto-Start
+ // When overlay connects to mpv, automatically show overlay and hide mpv subtitles.
+ // ==========================================
+ "auto_start_overlay": false,
+
+ // ==========================================
+ // Visible Overlay Subtitle Binding
+ // Control whether visible overlay toggles also toggle MPV subtitle visibility.
+ // When enabled, visible overlay hides MPV subtitles; when disabled, MPV subtitles are left unchanged.
+ // ==========================================
+ "bind_visible_overlay_to_mpv_sub_visibility": true,
+
+ // ==========================================
+ // Texthooker Server
+ // Control whether browser opens automatically for texthooker.
+ // ==========================================
+ "texthooker": {
+ "openBrowser": true
+ },
+
+ // ==========================================
+ // WebSocket Server
+ // Built-in WebSocket server broadcasts subtitle text to connected clients.
+ // Auto mode disables built-in server if mpv_websocket is detected.
+ // ==========================================
+ "websocket": {
+ "enabled": "auto",
+ "port": 6677
+ },
+
+ // ==========================================
+ // AnkiConnect Integration
+ // Automatic Anki updates and media generation options.
+ // ==========================================
+ "ankiConnect": {
+ "enabled": false,
+ "url": "http://127.0.0.1:8765",
+ "pollingRate": 3000,
+ "fields": {
+ "audio": "ExpressionAudio",
+ "image": "Picture",
+ "sentence": "Sentence",
+ "miscInfo": "MiscInfo",
+ "translation": "SelectionText"
+ },
+ "ai": {
+ "enabled": false,
+ "alwaysUseAiTranslation": false,
+ "apiKey": "",
+ "model": "openai/gpt-4o-mini",
+ "baseUrl": "https://openrouter.ai/api",
+ "targetLanguage": "English",
+ "systemPrompt": "You are a translation engine. Return only the translated text with no explanations."
+ },
+ "media": {
+ "generateAudio": true,
+ "generateImage": true,
+ "imageType": "static",
+ "imageFormat": "jpg",
+ "imageQuality": 92,
+ "animatedFps": 10,
+ "animatedMaxWidth": 640,
+ "animatedCrf": 35,
+ "audioPadding": 0.5,
+ "fallbackDuration": 3,
+ "maxMediaDuration": 30
+ },
+ "behavior": {
+ "overwriteAudio": true,
+ "overwriteImage": true,
+ "mediaInsertMode": "append",
+ "highlightWord": true,
+ "notificationType": "osd",
+ "autoUpdateNewCards": true
+ },
+ "metadata": {
+ "pattern": "[SubMiner] %f (%t)"
+ },
+ "isLapis": {
+ "enabled": false,
+ "sentenceCardModel": "Japanese sentences",
+ "sentenceCardSentenceField": "Sentence",
+ "sentenceCardAudioField": "SentenceAudio"
+ },
+ "isKiku": {
+ "enabled": false,
+ "fieldGrouping": "disabled",
+ "deleteDuplicateInAuto": true
+ }
+ },
+
+ // ==========================================
+ // Keyboard Shortcuts
+ // Overlay keyboard shortcuts. Set a shortcut to null to disable.
+ // ==========================================
+ "shortcuts": {
+ "toggleVisibleOverlayGlobal": "Alt+Shift+O",
+ "toggleInvisibleOverlayGlobal": "Alt+Shift+I",
+ "copySubtitle": "CommandOrControl+C",
+ "copySubtitleMultiple": "CommandOrControl+Shift+C",
+ "updateLastCardFromClipboard": "CommandOrControl+V",
+ "triggerFieldGrouping": "CommandOrControl+G",
+ "triggerSubsync": "Ctrl+Alt+S",
+ "mineSentence": "CommandOrControl+S",
+ "mineSentenceMultiple": "CommandOrControl+Shift+S",
+ "multiCopyTimeoutMs": 3000,
+ "toggleSecondarySub": "CommandOrControl+Shift+V",
+ "markAudioCard": "CommandOrControl+Shift+A",
+ "openRuntimeOptions": "CommandOrControl+Shift+O",
+ "openJimaku": "Ctrl+Alt+J"
+ },
+
+ // ==========================================
+ // Invisible Overlay
+ // Startup behavior for the invisible interactive subtitle mining layer.
+ // ==========================================
+ "invisibleOverlay": {
+ "startupVisibility": "platform-default"
+ },
+
+ // ==========================================
+ // Keybindings (MPV Commands)
+ // Extra keybindings that are merged with built-in defaults.
+ // Set command to null to disable a default keybinding.
+ // ==========================================
+ "keybindings": [],
+
+ // ==========================================
+ // Subtitle Appearance
+ // Primary and secondary subtitle styling.
+ // ==========================================
+ "subtitleStyle": {
+ "fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif",
+ "fontSize": 35,
+ "fontColor": "#cad3f5",
+ "fontWeight": "normal",
+ "fontStyle": "normal",
+ "backgroundColor": "rgba(54, 58, 79, 0.5)",
+ "secondary": {
+ "fontSize": 24,
+ "fontColor": "#ffffff",
+ "backgroundColor": "transparent",
+ "fontWeight": "normal",
+ "fontStyle": "normal",
+ "fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif"
+ }
+ },
+
+ // ==========================================
+ // Secondary Subtitles
+ // Dual subtitle track options.
+ // Used by subminer YouTube subtitle generation as secondary language preferences.
+ // ==========================================
+ "secondarySub": {
+ "secondarySubLanguages": [],
+ "autoLoadSecondarySub": false,
+ "defaultMode": "hover"
+ },
+
+ // ==========================================
+ // Auto Subtitle Sync
+ // Subsync engine and executable paths.
+ // ==========================================
+ "subsync": {
+ "defaultMode": "auto",
+ "alass_path": "",
+ "ffsubsync_path": "",
+ "ffmpeg_path": ""
+ },
+
+ // ==========================================
+ // Subtitle Position
+ // Initial vertical subtitle position from the bottom.
+ // ==========================================
+ "subtitlePosition": {
+ "yPercent": 10
+ },
+
+ // ==========================================
+ // Jimaku
+ // Jimaku API configuration and defaults.
+ // ==========================================
+ "jimaku": {
+ "apiBaseUrl": "https://jimaku.cc",
+ "languagePreference": "ja",
+ "maxEntryResults": 10
+ },
+
+ // ==========================================
+ // YouTube Subtitle Generation
+ // Defaults for subminer YouTube subtitle extraction/transcription mode.
+ // ==========================================
+ "youtubeSubgen": {
+ "mode": "automatic",
+ "whisperBin": "",
+ "whisperModel": "",
+ "primarySubLanguages": [
+ "ja",
+ "jpn"
+ ]
+ }
+}
diff --git a/docs/usage.md b/docs/usage.md
index 120d7da..8996fe3 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -7,6 +7,8 @@ There are two ways to use SubMiner:
| **subminer script** | All-in-one solution. Handles video selection, launches MPV with the correct socket, starts the overlay automatically, and cleans up on exit. |
| **MPV plugin** | When you launch MPV yourself or from other tools. Provides in-MPV chord keybindings (e.g. `y-y` for menu) to control visible and invisible overlay layers. Requires `--input-ipc-server=/tmp/subminer-socket`. |
+Jimaku modal shortcut is an overlay shortcut, not an MPV plugin chord: default `Ctrl+Alt+J` via `shortcuts.openJimaku`.
+
You can use both together—install the plugin for on-demand control, but use `subminer` when you want the streamlined workflow.
`subminer` is implemented as a Bun script and runs directly via shebang (no `bun run` needed), for example: `subminer video.mkv`.
@@ -113,7 +115,7 @@ Notes:
| `Right-click` | Toggle MPV pause (outside subtitle area) |
| `Right-click + drag` | Move subtitle position (on subtitle) |
-These keybindings only work when the overlay window has focus. See [Configuration](configuration.md) for customization.
+These keybindings only work when the overlay window has focus. See [Configuration](/configuration) for customization.
### Overlay Chord Shortcuts
@@ -125,7 +127,7 @@ These keybindings only work when the overlay window has focus. See [Configuratio
1. MPV runs with an IPC socket at `/tmp/subminer-socket`
2. The overlay connects and subscribes to subtitle changes
-3. Subtitles are tokenized with MeCab and merged into natural word boundaries
+3. Subtitles are tokenized with Yomitan's internal parser, with MeCab fallback when needed
4. Words are displayed as clickable spans
5. Clicking a word triggers Yomitan popup for dictionary lookup
6. Texthooker server runs at `http://127.0.0.1:5174` for external tools
diff --git a/src/config/config.test.ts b/src/config/config.test.ts
index c5adcfd..f7f19d1 100644
--- a/src/config/config.test.ts
+++ b/src/config/config.test.ts
@@ -43,7 +43,8 @@ test("parses invisible overlay config and new global shortcuts", () => {
`{
"shortcuts": {
"toggleVisibleOverlayGlobal": "Alt+Shift+U",
- "toggleInvisibleOverlayGlobal": "Alt+Shift+I"
+ "toggleInvisibleOverlayGlobal": "Alt+Shift+I",
+ "openJimaku": "Ctrl+Alt+J"
},
"invisibleOverlay": {
"startupVisibility": "hidden"
@@ -60,6 +61,7 @@ test("parses invisible overlay config and new global shortcuts", () => {
const config = service.getConfig();
assert.equal(config.shortcuts.toggleVisibleOverlayGlobal, "Alt+Shift+U");
assert.equal(config.shortcuts.toggleInvisibleOverlayGlobal, "Alt+Shift+I");
+ assert.equal(config.shortcuts.openJimaku, "Ctrl+Alt+J");
assert.equal(config.invisibleOverlay.startupVisibility, "hidden");
assert.equal(config.bind_visible_overlay_to_mpv_sub_visibility, false);
assert.deepEqual(config.youtubeSubgen.primarySubLanguages, ["ja", "jpn", "jp"]);
diff --git a/src/config/definitions.ts b/src/config/definitions.ts
index 8e31e6c..9ddb819 100644
--- a/src/config/definitions.ts
+++ b/src/config/definitions.ts
@@ -152,6 +152,7 @@ export const DEFAULT_CONFIG: ResolvedConfig = {
toggleSecondarySub: "CommandOrControl+Shift+V",
markAudioCard: "CommandOrControl+Shift+A",
openRuntimeOptions: "CommandOrControl+Shift+O",
+ openJimaku: "Ctrl+Alt+J",
},
secondarySub: {
secondarySubLanguages: [],
diff --git a/src/config/service.ts b/src/config/service.ts
index 5977181..db3f3ca 100644
--- a/src/config/service.ts
+++ b/src/config/service.ts
@@ -214,6 +214,7 @@ export class ConfigService {
"toggleSecondarySub",
"markAudioCard",
"openRuntimeOptions",
+ "openJimaku",
] as const;
for (const key of shortcutKeys) {
diff --git a/src/core/services/overlay-shortcut-fallback-runner.ts b/src/core/services/overlay-shortcut-fallback-runner.ts
index 85ef5ce..859bbf0 100644
--- a/src/core/services/overlay-shortcut-fallback-runner.ts
+++ b/src/core/services/overlay-shortcut-fallback-runner.ts
@@ -2,6 +2,7 @@ import { ConfiguredShortcuts } from "../utils/shortcut-config";
export interface OverlayShortcutFallbackHandlers {
openRuntimeOptions: () => void;
+ openJimaku: () => void;
markAudioCard: () => void;
copySubtitleMultiple: (timeoutMs: number) => void;
copySubtitle: () => void;
@@ -34,6 +35,12 @@ export function runOverlayShortcutLocalFallback(
handlers.openRuntimeOptions();
},
},
+ {
+ accelerator: shortcuts.openJimaku,
+ run: () => {
+ handlers.openJimaku();
+ },
+ },
{
accelerator: shortcuts.markAudioCard,
run: () => {
diff --git a/src/core/services/overlay-shortcut-service.ts b/src/core/services/overlay-shortcut-service.ts
index 0ca937c..7bab1ce 100644
--- a/src/core/services/overlay-shortcut-service.ts
+++ b/src/core/services/overlay-shortcut-service.ts
@@ -13,6 +13,7 @@ export interface OverlayShortcutHandlers {
toggleSecondarySub: () => void;
markAudioCard: () => void;
openRuntimeOptions: () => void;
+ openJimaku: () => void;
}
export function registerOverlayShortcutsService(
@@ -118,6 +119,13 @@ export function registerOverlayShortcutsService(
"openRuntimeOptions",
);
}
+ if (shortcuts.openJimaku) {
+ registerOverlayShortcut(
+ shortcuts.openJimaku,
+ () => handlers.openJimaku(),
+ "openJimaku",
+ );
+ }
return registeredAny;
}
@@ -155,4 +163,7 @@ export function unregisterOverlayShortcutsService(
if (shortcuts.openRuntimeOptions) {
globalShortcut.unregister(shortcuts.openRuntimeOptions);
}
+ if (shortcuts.openJimaku) {
+ globalShortcut.unregister(shortcuts.openJimaku);
+ }
}
diff --git a/src/core/utils/shortcut-config.ts b/src/core/utils/shortcut-config.ts
index e65f1be..ff6c918 100644
--- a/src/core/utils/shortcut-config.ts
+++ b/src/core/utils/shortcut-config.ts
@@ -14,6 +14,7 @@ export interface ConfiguredShortcuts {
toggleSecondarySub: string | null | undefined;
markAudioCard: string | null | undefined;
openRuntimeOptions: string | null | undefined;
+ openJimaku: string | null | undefined;
}
export function resolveConfiguredShortcuts(
@@ -78,5 +79,8 @@ export function resolveConfiguredShortcuts(
config.shortcuts?.openRuntimeOptions ??
defaultConfig.shortcuts?.openRuntimeOptions,
),
+ openJimaku: normalizeShortcut(
+ config.shortcuts?.openJimaku ?? defaultConfig.shortcuts?.openJimaku,
+ ),
};
}
diff --git a/src/main.ts b/src/main.ts
index ac0f576..34c74d4 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -2265,6 +2265,9 @@ function tryHandleOverlayShortcutLocalFallback(input: Electron.Input): boolean {
openRuntimeOptions: () => {
openRuntimeOptionsPalette();
},
+ openJimaku: () => {
+ sendToVisibleOverlay("jimaku:open");
+ },
markAudioCard: () => {
markLastCardAsAudioCard().catch((err) => {
console.error("markLastCardAsAudioCard failed:", err);
@@ -2644,6 +2647,9 @@ function registerOverlayShortcuts(): void {
openRuntimeOptions: () => {
openRuntimeOptionsPalette();
},
+ openJimaku: () => {
+ sendToVisibleOverlay("jimaku:open");
+ },
});
}
diff --git a/src/preload.ts b/src/preload.ts
index 71dfa17..2d9c482 100644
--- a/src/preload.ts
+++ b/src/preload.ts
@@ -259,6 +259,11 @@ const electronAPI: ElectronAPI = {
callback();
});
},
+ onOpenJimaku: (callback: () => void) => {
+ ipcRenderer.on("jimaku:open", () => {
+ callback();
+ });
+ },
notifyOverlayModalClosed: (modal: "runtime-options" | "subsync") => {
ipcRenderer.send("overlay:modal-closed", modal);
},
diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts
index aa02489..1284cfc 100644
--- a/src/renderer/renderer.ts
+++ b/src/renderer/renderer.ts
@@ -1953,7 +1953,6 @@ const CHORD_MAP = new Map([
["KeyR", { type: "mpv", command: ["script-message", "subminer-restart"] }],
["KeyC", { type: "mpv", command: ["script-message", "subminer-status"] }],
["KeyY", { type: "mpv", command: ["script-message", "subminer-menu"] }],
- ["KeyJ", { type: "electron", action: () => openJimakuModal() }],
[
"KeyD",
{ type: "electron", action: () => window.electronAPI.toggleDevTools() },
@@ -2398,6 +2397,9 @@ async function init(): Promise {
window.electronAPI.notifyOverlayModalClosed("runtime-options");
});
});
+ window.electronAPI.onOpenJimaku(() => {
+ openJimakuModal();
+ });
window.electronAPI.onSubsyncManualOpen((payload: SubsyncManualPayload) => {
openSubsyncModal(payload);
});
diff --git a/src/types.ts b/src/types.ts
index 77f58f0..f5a8499 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -275,6 +275,7 @@ export interface ShortcutsConfig {
toggleSecondarySub?: string | null;
markAudioCard?: string | null;
openRuntimeOptions?: string | null;
+ openJimaku?: string | null;
}
export type JimakuLanguagePreference = "ja" | "en" | "none";
@@ -606,6 +607,7 @@ export interface ElectronAPI {
callback: (options: RuntimeOptionState[]) => void,
) => void;
onOpenRuntimeOptions: (callback: () => void) => void;
+ onOpenJimaku: (callback: () => void) => void;
notifyOverlayModalClosed: (modal: "runtime-options" | "subsync") => void;
}