mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 00:55:16 -07:00
feat(character-dictionary): add manager modal and scope name matching to current media (#86)
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- **Character Dictionary:** Loaded entries are now scoped to the current AniList media for subtitle name matching and inline portraits. Added a character dictionary manager at `Ctrl/Cmd+D`; AniList overrides now live inside that manager instead of using a separate default shortcut.
|
||||
|
||||
## v0.14.0 (2026-05-12)
|
||||
|
||||
SubMiner no longer requires a globally-installed mpv plugin. The bundled plugin is injected at runtime only when SubMiner launches mpv — through the `subminer` launcher, the app's managed launch, or the packaged Windows SubMiner mpv shortcut. When you open mpv on its own, SubMiner is not involved and the plugin is never loaded. If you have a legacy global SubMiner plugin under mpv's `scripts` directory, first-run setup detects it and prompts you to remove it before playback starts.
|
||||
|
||||
@@ -14,7 +14,7 @@ The feature has three stages: **snapshot**, **merge**, and **match**.
|
||||
|
||||
2. **Merge** — SubMiner maintains a most-recently-used list of media IDs (default: 3). Snapshots from those titles are merged into a single Yomitan ZIP — `character-dictionaries/merged.zip` — which is always named "SubMiner Character Dictionary" so Yomitan treats it as a single stable dictionary across rebuilds.
|
||||
|
||||
3. **Match** — During subtitle rendering, Yomitan scans subtitle text against all loaded dictionaries including the character dictionary. Tokens that match a character entry are flagged with `isNameMatch` and highlighted in the overlay with a distinct color.
|
||||
3. **Match** — During subtitle rendering, Yomitan scans subtitle text against all loaded dictionaries including the character dictionary. SubMiner only accepts character entries for the current AniList media when that media ID is known, then flags matching tokens with `isNameMatch` and highlights them in the overlay with a distinct color.
|
||||
|
||||
## Enabling the Feature
|
||||
|
||||
@@ -89,9 +89,10 @@ Name matching runs inside Yomitan's scanning pipeline during subtitle tokenizati
|
||||
|
||||
1. Yomitan receives subtitle text and scans for dictionary matches.
|
||||
2. Entries from "SubMiner Character Dictionary" are checked with exact primary-source matching — the token must match the entry's `originalText` with `isPrimary: true` and `matchType: 'exact'`.
|
||||
3. Matched tokens are flagged `isNameMatch: true` and forwarded to the renderer.
|
||||
4. If `subtitleStyle.nameMatchEnabled` is enabled, the renderer applies the name-match highlight color (default: `#f5bde6`).
|
||||
5. If `subtitleStyle.nameMatchImagesEnabled` is enabled, the renderer also injects a small circular AniList portrait from the cached snapshot image data.
|
||||
3. When the current AniList media ID is known, entries whose embedded media ID belongs to a different title are ignored for name matching and inline portraits.
|
||||
4. Matched tokens are flagged `isNameMatch: true` and forwarded to the renderer.
|
||||
5. If `subtitleStyle.nameMatchEnabled` is enabled, the renderer applies the name-match highlight color (default: `#f5bde6`).
|
||||
6. If `subtitleStyle.nameMatchImagesEnabled` is enabled, the renderer also injects a small circular AniList portrait from the cached snapshot image data.
|
||||
|
||||
Older snapshot schema versions are regenerated automatically. Current-version snapshots are normally reused, but when `subtitleStyle.nameMatchImagesEnabled` is enabled SubMiner also checks whether the cached snapshot contains usable character portrait data. If it does not, the snapshot is refreshed so the merged dictionary can include images.
|
||||
|
||||
@@ -178,7 +179,7 @@ SubMiner uses `guessit` to infer the anime title from the active filename before
|
||||
|
||||
Use the in-app selector or CLI to pin the correct AniList media for the whole series:
|
||||
|
||||
- In-app: open the selector with `Ctrl/Cmd+Alt+A` or `--open-character-dictionary`, edit the prefilled title if needed, then search and choose the correct result.
|
||||
- In-app: open the manager with `Ctrl/Cmd+D`, use the **Override** tab/button, edit the prefilled title if needed, then search and choose the correct result. The CLI flag `--open-character-dictionary` still opens the selector directly.
|
||||
- CLI: `--dictionary-candidates` still lists matches for the current filename guess.
|
||||
|
||||
```bash
|
||||
@@ -198,6 +199,16 @@ subminer app --open-character-dictionary
|
||||
|
||||
Manual selections are stored in `character-dictionaries/anilist-overrides.json` using a series key derived from the episode's parent directory plus the filename guess. Later episodes in the same directory use the selected AniList ID automatically, while separate season directories can keep separate overrides and character dictionaries. When the override replaces a previous wrong match, SubMiner removes that stale media ID from the merged dictionary's active set and rebuilds/imports the merged character dictionary.
|
||||
|
||||
## Managing Loaded Entries
|
||||
|
||||
Open the manager with `Ctrl/Cmd+D` (`shortcuts.openCharacterDictionaryManager`). The manager shows the merged dictionary's active MRU entries, marks the current anime, and lets you adjust eviction priority for the other loaded entries.
|
||||
|
||||
- **Remove** drops a non-current entry from the active merged dictionary and rebuilds/imports once.
|
||||
- **Up/Down** changes MRU order for future eviction without rebuilding.
|
||||
- **Override** opens the AniList selector for that entry's title so you can replace a saved loaded entry.
|
||||
|
||||
The current anime cannot be removed while you are watching it; it stays loaded until playback changes.
|
||||
|
||||
## File Structure
|
||||
|
||||
All character dictionary data lives under `{userData}/character-dictionaries/`:
|
||||
@@ -215,7 +226,7 @@ character-dictionaries/
|
||||
m170942-va67890.jpg # Voice actor portrait
|
||||
```
|
||||
|
||||
**Snapshot format** (v16): each snapshot contains the media ID, title, entry count, timestamp, an array of Yomitan term entries, and base64-encoded images.
|
||||
**Snapshot format** (v17): each snapshot contains the media ID, title, entry count, timestamp, an array of Yomitan term entries, and base64-encoded images.
|
||||
|
||||
**ZIP structure** follows the Yomitan dictionary format:
|
||||
|
||||
@@ -264,7 +275,7 @@ If you work with visual novels or want a standalone dictionary generator indepen
|
||||
- **Names not highlighting:** Confirm `anilist.characterDictionary.enabled` is `true` and `subtitleStyle.nameMatchEnabled` is `true`. Check that the current media has an AniList entry — SubMiner needs a media ID to fetch characters.
|
||||
- **Inline portraits missing:** Confirm `subtitleStyle.nameMatchImagesEnabled` is `true`. On the next character dictionary sync, SubMiner refreshes current-version snapshots that do not contain usable cached character portrait data. Portraits still require AniList to return an image and the image download to succeed.
|
||||
- **Sync seems stuck:** The auto-sync debounces for 800ms after media changes and throttles image downloads at 250ms per image. Large casts (50+ characters) take longer. Check the status bar for the current sync phase.
|
||||
- **Wrong characters showing:** Open the in-app character dictionary selector (`--open-character-dictionary`), edit the search title, and select the right AniList entry. You can also run `--dictionary-candidates`, then save the correct media with `--dictionary-select --dictionary-anilist-id <id>`. This replaces stale wrong-title entries for that series. If names are only from an older unrelated show, they'll rotate out once you watch enough new titles to push it past `maxLoaded`.
|
||||
- **Wrong characters showing:** Open the in-app character dictionary manager (`Ctrl/Cmd+D`) to remove/reorder loaded titles, then use **Override** to correct the active AniList match. You can also run `--dictionary-candidates`, then save the correct media with `--dictionary-select --dictionary-anilist-id <id>`. SubMiner ignores character entries from other loaded titles for subtitle name matching and inline portraits once the current media ID is known.
|
||||
- **Yomitan import fails:** SubMiner waits up to 7 seconds for Yomitan to be ready for mutations. If Yomitan is still loading dictionaries or performing another import, the operation may time out. Restarting the overlay typically resolves this.
|
||||
- **Portraits missing:** Images are downloaded from AniList CDN during snapshot generation. If the network was unavailable during the initial sync, delete the snapshot file from `character-dictionaries/snapshots/` and let it regenerate.
|
||||
|
||||
|
||||
@@ -618,7 +618,7 @@ See `config.example.jsonc` for detailed configuration options.
|
||||
"mineSentence": "CommandOrControl+S",
|
||||
"mineSentenceMultiple": "CommandOrControl+Shift+S",
|
||||
"markAudioCard": "CommandOrControl+Shift+A",
|
||||
"openCharacterDictionary": "CommandOrControl+Alt+A",
|
||||
"openCharacterDictionaryManager": "CommandOrControl+D",
|
||||
"openRuntimeOptions": "CommandOrControl+Shift+O",
|
||||
"openSessionHelp": "CommandOrControl+Slash",
|
||||
"openControllerSelect": "Alt+C",
|
||||
@@ -643,7 +643,7 @@ See `config.example.jsonc` for detailed configuration options.
|
||||
| `multiCopyTimeoutMs` | number | Timeout in ms for multi-copy/mine digit input (default: `3000`) |
|
||||
| `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"`) |
|
||||
| `openCharacterDictionary` | string \| `null` | Opens the character dictionary AniList selector (default: `"CommandOrControl+Alt+A"`) |
|
||||
| `openCharacterDictionaryManager` | string \| `null` | Opens the loaded character dictionary manager (default: `"CommandOrControl+D"`) |
|
||||
| `openRuntimeOptions` | string \| `null` | Opens runtime options palette for live session-only toggles (default: `"CommandOrControl+Shift+O"`) |
|
||||
| `openSessionHelp` | string \| `null` | Opens the in-overlay session help modal (default: `"CommandOrControl+Slash"`) |
|
||||
| `openControllerSelect` | string \| `null` | Opens the controller config/remap modal (default: `"Alt+C"`) |
|
||||
@@ -787,7 +787,7 @@ When `behavior.autoUpdateNewCards` is set to `false`, new cards are detected but
|
||||
| `Ctrl+Shift+S` | Enter multi-mine mode. Press `1-9` to create a sentence card from that many recent lines, or `Esc` to cancel |
|
||||
| `Ctrl+Shift+V` | Cycle secondary subtitle display mode (hidden → visible → hover) |
|
||||
| `Ctrl+Shift+A` | Mark the last added Anki card as an audio card (sets IsAudioCard, SentenceAudio, Sentence, Picture) |
|
||||
| `Ctrl+Alt+A` | Open character dictionary AniList selector |
|
||||
| `Ctrl+D` | Open loaded character dictionary manager |
|
||||
| `Ctrl+Shift+O` | Open runtime options palette (session-only live toggles) |
|
||||
| `Ctrl/Cmd+A` | Append clipboard video path to MPV playlist (fixed, not currently configurable) |
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
"multiCopyTimeoutMs": 3000, // Timeout for multi-copy/mine modes.
|
||||
"toggleSecondarySub": "CommandOrControl+Shift+V", // Accelerator that toggles the secondary subtitle bar visibility.
|
||||
"markAudioCard": "CommandOrControl+Shift+A", // Accelerator that marks the last mined card as an audio card.
|
||||
"openCharacterDictionary": "CommandOrControl+Alt+A", // Accelerator that opens the character dictionary modal.
|
||||
"openCharacterDictionaryManager": "CommandOrControl+D", // Accelerator that opens the character dictionary manager modal.
|
||||
"openRuntimeOptions": "CommandOrControl+Shift+O", // Accelerator that opens the runtime options modal.
|
||||
"openJimaku": "Ctrl+Shift+J", // Accelerator that opens the Jimaku subtitle search modal.
|
||||
"openSessionHelp": "CommandOrControl+Slash", // Accelerator that opens the session help / keybinding cheatsheet.
|
||||
|
||||
+12
-12
@@ -75,17 +75,17 @@ Mouse-hover playback behavior is configured separately from shortcuts: `subtitle
|
||||
|
||||
## Subtitle & Feature Shortcuts
|
||||
|
||||
| Shortcut | Action | Config key |
|
||||
| ------------------ | -------------------------------------------------------- | ----------------------------------- |
|
||||
| `Ctrl/Cmd+Shift+V` | Cycle secondary subtitle mode (hidden → visible → hover) | `shortcuts.toggleSecondarySub` |
|
||||
| `Ctrl/Cmd+Alt+A` | Open character dictionary AniList selector | `shortcuts.openCharacterDictionary` |
|
||||
| `Ctrl/Cmd+Shift+O` | Open runtime options palette | `shortcuts.openRuntimeOptions` |
|
||||
| `Ctrl/Cmd+/` | Open session help modal | `shortcuts.openSessionHelp` |
|
||||
| `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` |
|
||||
| Shortcut | Action | Config key |
|
||||
| ------------------ | -------------------------------------------------------- | ----------------------------------------------- |
|
||||
| `Ctrl/Cmd+Shift+V` | Cycle secondary subtitle mode (hidden → visible → hover) | `shortcuts.toggleSecondarySub` |
|
||||
| `Ctrl/Cmd+D` | Open loaded character dictionary manager | `shortcuts.openCharacterDictionaryManager` |
|
||||
| `Ctrl/Cmd+Shift+O` | Open runtime options palette | `shortcuts.openRuntimeOptions` |
|
||||
| `Ctrl/Cmd+/` | Open session help modal | `shortcuts.openSessionHelp` |
|
||||
| `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` |
|
||||
|
||||
The stats toggle is handled inside the focused visible overlay window. It is configurable through the top-level `stats.toggleKey` setting and defaults to `Backquote`.
|
||||
|
||||
@@ -131,7 +131,7 @@ When the overlay has focus, press `y` then `d` to toggle DevTools (debugging hel
|
||||
|
||||
## Customizing Shortcuts
|
||||
|
||||
All `shortcuts.*` keys accept [Electron accelerator strings](https://www.electronjs.org/docs/latest/tutorial/keyboard-shortcuts), for example `"CommandOrControl+Alt+A"`. Use `null` to disable a shortcut.
|
||||
All `shortcuts.*` keys accept [Electron accelerator strings](https://www.electronjs.org/docs/latest/tutorial/keyboard-shortcuts), for example `"CommandOrControl+D"`. Use `null` to disable a shortcut.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ Set `refreshMinutes` to `1440` (24 hours) for daily sync if your Anki collection
|
||||
|
||||
## Character-Name Highlighting
|
||||
|
||||
Character-name matches are built from the active merged SubMiner character dictionary, which auto-syncs character data from AniList for your recently-watched titles. Matching names are highlighted in subtitles and become available for hover-driven Yomitan character profiles — portraits, roles, voice actors, and biographical detail.
|
||||
Character-name matches are built from the active merged SubMiner character dictionary, which auto-syncs character data from AniList for your recently-watched titles. When the current AniList media ID is known, SubMiner ignores loaded entries from other titles for subtitle name matching and inline portraits. Matching names are highlighted in subtitles and become available for hover-driven Yomitan character profiles — portraits, roles, voice actors, and biographical detail.
|
||||
|
||||
**How it works:**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user