Compare commits

..
71 Commits
Author SHA1 Message Date
sudacode 44d0d2e9c5 test(hachidori): expect the hachidori tag in the parser Anki check 2026-10-19 08:40:56 -07:00
sudacode 96c87ae003 fix(hachidori): keep the hachidori tag alongside SubMiner's Anki tags
- Untouched default templates now get configured tags plus `hachidori` instead of replacing it
- Tags edited in Hachidori settings are left as written
- Update Anki integration docs and changelog fragment
2026-10-18 23:43:25 -07:00
sudacode 4842359e39 fix(hachidori): finish character dictionary imports without keep-alive w
- Close lingering sockets on the one-shot dictionary ZIP server so imports report completion as soon as the archive is read
- Run test:dictionary:electron in the Linux CI E2E job
- Note that annotations and lookups pause during Hachidori imports
- Keep only dictionaryBackend in the Dictionary Lookup settings section
- Move the requestHachidoriSharing import below the license header
2026-10-18 22:14:44 -07:00
sudacode 9e53ecb27b fix(hachidori): drop busy-commit retry now that the engine queues imports
Upstream dfc5e0c (#578) queues dictionary imports behind a running change and
removed ENGINE_BUSY_CODE, so a commit no longer replies engine-mutating.
2026-10-18 11:21:46 -07:00
sudacode e45d302b5d build(hachidori): bump fork to upstream dfc5e0c 2026-10-18 11:05:24 -07:00
sudacode f08d9d1d53 feat(subtitles): generate Jellyfin subtitles and widen halfwidth kana
- Whisper subtitle generation now works for Jellyfin playback. It reads the item's original file through toJellyfinOriginalStreamUrl and writes to a reused cache folder. For transcoded playback it uses Jellyfin's audio index
- generateJapaneseSubtitles accepts http(s) streams when an output path is set; subprocess errors redact URL tokens
- Add widenHalfwidthKana/narrowFullwidthKana and widen subtitle text at ingest (mpv sub-text, parsed cues, live primary text), so halfwidth and fullwidth duplicates merge into one cue
- Stats queries widen older stored lines, sentence search matches katakana in either width, and stats mining widens sentence/word
- Update Jellyfin, subtitle generation, and architecture docs; add changelog fragments
2026-10-13 22:30:50 -07:00
sudacode 2aedb3a1fa build(hachidori): bump fork to upstream 0.3.1 (0a00a38) 2026-10-12 22:37:58 -07:00
sudacode 8809ed38b2 Merge remote-tracking branch 'origin/main' into add-hachidori-backend
# Conflicts:
#	src/main.ts
2026-10-12 22:14:30 -07:00
sudacode c18644392d fix(overlay): stop Hyprland/Sway tracker polls from blocking the main thread (#289) 2026-10-11 01:02:02 -07:00
sudacode 9c351a8603 fix(overlay): poll cheap playback state in the open subtitle sidebar (#290) 2026-10-11 01:02:02 -07:00
sudacode 6c295586cb fix(hachidori): close an open pane after its attention is released 2026-10-11 00:54:01 -07:00
sudacode 0523beef2b fix(hachidori): treat only an open pane as an open popup for lookup controls 2026-10-11 00:44:07 -07:00
sudacode 06fc578830 Merge remote-tracking branch 'origin/main' into add-hachidori-backend
# Conflicts:
#	src/renderer/handlers/keyboard.test.ts
2026-10-11 00:35:19 -07:00
sudacode 994483ccd6 build(hachidori): bump fork to a52694c (LF checkouts on Windows) 2026-10-10 22:39:29 -07:00
sudacode 6350ee928d build(hachidori): bump fork to 15babde
- Update vendor/hachidori submodule from 343e861 to 15babde
- Remove stale generated release/release-notes.md from v0.20.1
2026-10-10 21:21:46 -07:00
sudacode 4d1adebf11 refactor(hachidori): drop the Docker management upload for character dictionaries
Every supported host now takes dictionary ZIPs over the link, so remove
the port 8780 upload, its hachidori.externalHostManagementUrl override and
the hachidori config section. A linked host without linked-import-v1 now
reports the update or manual-import fix.
2026-10-05 09:50:52 -07:00
sudacode e1c4d6ca94 feat(hachidori): send character dictionaries over the link to Hachidori hosts
Hachidori 0.2.3 accepts dictionary ZIPs as chunked upload requests: an
unlinked install imports them itself and a linked one forwards them to its
host. The parser bridge now imports through that path with replace, retrying
a busy commit with the same upload. Linked hosts without linked-import-v1
(hachidori-docker, older Hachidori) keep the management API upload.
2026-10-05 09:10:33 -07:00
sudacode e25d472d82 build(hachidori): bump fork to upstream 0.2.3 (991c48c) 2026-10-05 09:03:39 -07:00
sudacode 20f151482a Merge remote-tracking branch 'origin/main' into add-hachidori-backend
# Conflicts:
#	launcher/main.test.ts
#	src/cli/help.test.ts
#	src/core/services/__tests__/stats-server.test.ts
#	src/core/services/cli-command.test.ts
#	src/core/services/tokenizer/yomitan-parser-runtime.test.ts
#	src/main/overlay-runtime.test.ts
#	src/main/runtime/character-dictionary-auto-sync.test.ts
#	src/main/runtime/cli-command-context-deps.test.ts
#	src/main/runtime/cli-command-context-factory.test.ts
#	src/main/runtime/cli-command-context-main-deps.test.ts
#	src/main/runtime/composers/cli-startup-composer.test.ts
#	src/main/runtime/stats-server-runtime.test.ts
#	src/main/runtime/tray-main-actions.test.ts
#	src/main/runtime/tray-main-deps.test.ts
#	src/main/runtime/tray-runtime-handlers.test.ts
#	src/main/runtime/tray-runtime.test.ts
#	src/main/runtime/yomitan-anki-server-sync.test.ts
#	src/preload.ts
#	src/renderer/handlers/mouse.test.ts
#	src/renderer/yomitan-popup.test.ts
2026-10-05 09:01:11 -07:00
sudacode 170dbbb74c fix(overlay): treat Hachidori popup host as a lookup target for keys
- Match the Hachidori shadow-root host alongside the Yomitan popup host when routing keyboard events
- Update the keyboard test stub to use HACHIDORI_HOST_SELECTOR
2026-10-04 00:59:04 -07:00
sudacode 37d3f5e03e build(hachidori): bump fork to upstream 9e1dfa3 2026-10-04 00:22:41 -07:00
sudacode 55c751830e build(hachidori): bump fork to upstream 0.2.2 (2a063cd) 2026-10-04 00:04:31 -07:00
sudacode 3091d7b2a7 fix(overlay): forward imported mpv input while Hachidori only claims attention
Hachidori's popup-shown event also fires for any press that may start a
selection, so treating it as an open popup blocked imported mpv mouse
buttons and routed every key to the popup keybind handler. Once Hachidori
is the reader, forwarding, popup key routing and the controller lookup
state now require an unhidden popup pane. Wheel events over an in-document
dictionary popup stay with the popup.

Bump vendor/hachidori so presses outside a text box no longer start a
glyph drag that prevented the press.
2026-10-01 23:56:14 -07:00
sudacode 4423dd56af Merge remote-tracking branch 'origin/main' into add-hachidori-backend 2026-10-01 21:48:59 -07:00
sudacode a6df21bdc2 feat(overlay): forward mpv mouse and wheel bindings through overlay
- Accept WHEEL_UP/DOWN/LEFT/RIGHT (with modifiers) in keybindings, the plugin, and the settings key editor
- Forward mpv's MBTN_* and WHEEL_* bindings from the overlay so double-click fullscreen, wheel volume, and similar inputs work on Hyprland; SubMiner bindings and right-click pause still win
- Notify the overlay via mpv-input-bindings:changed when mpv's key set changes, fixing dead imported keys when the overlay loads before mpv connects
- Sync README requirements/quick start with the installation guide and fix the Arch MeCab install command (AUR mecab-git)
2026-10-01 01:16:44 -07:00
sudacode e5a6e3b178 Merge remote-tracking branch 'origin/main' into add-hachidori-backend 2026-09-30 20:52:27 -07:00
sudacode 152d2c0626 Merge remote-tracking branch 'origin/main' into add-hachidori-backend 2026-09-30 19:50:36 -07:00
sudacode bbb0e9b6a5 Merge remote-tracking branch 'origin/main' into add-hachidori-backend 2026-09-30 01:37:19 -07:00
sudacode e09a416422 Merge remote-tracking branch 'origin/main' into add-hachidori-backend 2026-09-29 23:00:07 -07:00
sudacode 42f86b0748 perf(subtitle-prefetch): sweep overlap spans instead of rescanning cues
- Track active cues with a single sorted start/end sweep across boundaries
- Join simultaneous cues in cue-list order to match mpv
- Skip empty or inverted cues
- Add test for multi-cue overlap spans with unsorted cue input
2026-09-29 22:17:10 -07:00
sudacode e69cd5ea06 Merge remote-tracking branch 'origin/main' into add-hachidori-backend 2026-09-29 20:31:17 -07:00
sudacode 0cff52f8e4 Merge remote-tracking branch 'origin/main' into add-hachidori-backend 2026-09-29 20:27:55 -07:00
sudacode b52692d826 feat(hachidori): derive Docker management URL from the linked host
- Default character dictionary uploads to the linked host on port 8780; externalHostManagementUrl is now an optional override
- Warn when the override points at a different machine than the link
- Browser/app hosts report where to import the merged ZIP manually
- Name the management origin when the upload request cannot connect
- Update config descriptions, generated examples, docs, and changelog fragment
2026-09-29 20:22:07 -07:00
sudacode 4ab5e37bc5 build(hachidori): bump fork to 32e5346 2026-09-29 19:03:07 -07:00
sudacode f00b830054 Merge remote-tracking branch 'origin/main' into add-hachidori-backend 2026-09-29 18:49:49 -07:00
sudacode ec73157f37 build(deps): revert fast-uri override to 3.1.7
- Pin the fast-uri override in package.json and bun.lock back to 3.1.7 from 3.1.8
2026-09-28 22:37:29 -07:00
sudacode 45212c7659 build(hachidori): bump fork to upstream 6faca95
Upstream dropped Firefox, so the overlay copy now turns off custom JavaScript
from its plain capability flag. The Anki audio fixture follows the removal of
media capture options.
2026-09-28 21:42:37 -07:00
sudacode b710822db2 build(deps): bump fast-uri override to 3.1.8 for audit advisories 2026-09-28 21:18:38 -07:00
sudacode b347fdf21a feat(youtube): add YouTube browser window and Whisper subtitle source
- Add `subminer youtube`/`yt` and tray "Browse YouTube" window that plays or queues picked videos in mpv
- Add `youtube.subtitleSource` (`youtube` | `whisper`) to transcribe YouTube audio via subtitle generation
- Allow the subtitle generation modal on YouTube videos
- Pick the real Japanese track over machine-translated ones and fall back to yt-dlp on HTTP 429
- Remove legacy launcher youtubeSubgen/whisper/AI args and `--youtube-mode`
- Fix EPIPE crash when the launching terminal is closed with Ctrl+C
- Thin media timing review handles with orange original-boundary bars
- Update docs and change fragments
2026-09-28 20:46:11 -07:00
sudacode a4325740b7 fix(subtitle-prefetch): reuse startup runs and prefetch overlap lines
- Warm combined text for overlapping subtitle cues.
- Preserve valid in-flight tokenization across startup restarts while rejecting invalidated cache results.
2026-09-28 01:17:59 -07:00
sudacode 4648c5e5a1 chore(hachidori): update submodule to upstream 1a12326
Merge 8 upstream Hachidori commits into the subminer fork, including the
Google Docs experimental lookup, monochrome image palette colours and Anki
deferred-audio fixes.
2026-09-25 23:02:23 -07:00
sudacode 72d0b19d94 Merge remote-tracking branch 'origin/main' into add-hachidori-backend 2026-09-24 21:33:27 -07:00
sudacode c627095e6e Merge remote-tracking branch 'origin/main' into add-hachidori-backend 2026-09-24 20:43:02 -07:00
sudacode dea1136170 Merge remote-tracking branch 'origin/main' into add-hachidori-backend
# Conflicts:
#	docs-site/anki-integration.md
#	docs-site/immersion-tracking.md
#	docs-site/shortcuts.md
#	docs-site/usage.md
2026-09-24 17:26:30 -07:00
sudacode 874d2c9f1a fix(hachidori): preserve setup completion while host connects
- Reopen setup only when a reachable host has no dictionaries
2026-09-24 14:02:27 -07:00
sudacode 52fc1b63e9 fix(launcher): validate login shell paths before execution
- Fall back to absolute default shells when configured values are relative
- Reject invalid shells before spawning them
2026-09-24 01:31:27 -07:00
sudacode da5074761f fix(launcher): load macOS login shell PATH for GUI launches 2026-09-24 01:10:15 -07:00
sudacode f7230db90c chore(release): prepare v0.20.0
- Build v0.20.0 changelog and release notes from changes/ fragments
- Remove package-size JSON reports and previous-release size comparisons from CI
- Download AUR release assets via curl with retries and skip publish on failure
- Update release docs, verification lanes, and workflow tests to match
2026-09-24 00:01:03 -07:00
sudacode 92ef305e8c chore(hachidori): update submodule to fork subminer 441971b
Pull in the fork fix that looks up popup definition text only when the
pointer is on a glyph, so hovering beside a short glossary row no longer
opens a nested popup for the next row's word.
2026-09-23 22:59:10 -07:00
sudacode 504faa51e0 fix(anki): honor overlay pause that races timing review setup
- Track overlay pauses per review request so a pause arriving while setup reads mpv's pause state still cancels the playback restore
- Ignore cancelPlaybackResume when no review request is in flight
- Add a test covering the pause-read race and reset between requests
2026-09-23 20:08:01 -07:00
sudacode c10174f9cd fix(dictionary): pin Hachidori extension ID with a static manifest key
Electron derives an unpacked extension's ID from its directory, so a changed
userData path gave Hachidori a new storage origin and an empty library. The
staged copy now carries a fixed key, like the bundled Yomitan.
2026-09-23 09:15:33 -07:00
sudacode 33e694bc61 fix(anki): keep playback paused after overlay pause during timing review
- Treat overlay pause requests during an open media timing review as final: drop held resumes and the review's own playback restore
- Add cancelPlaybackResume to the media timing review runtime
- Test both paused and playing starting states
2026-09-23 01:36:11 -07:00
sudacode 430120cc63 fix(anki): hold playback paused while media timing review is open
- Defer overlay resume requests (popup closed, hover left) during an active review and apply them when the review closes
- Add deferPlaybackResume to the media timing review runtime and gate sendRendererMpvCommand on it
- Document the paused-playback behavior and add a changelog fragment
2026-09-23 00:32:35 -07:00
sudacode 294e3a777e chore(hachidori): update submodule to upstream d68089e
Merge 104 upstream Hachidori commits into the subminer fork, including
rebuilt engine artifacts and upstream glyph hover hit-testing.
2026-09-22 22:42:05 -07:00
sudacode c673b71053 fix(dictionary): restore Anki proxy marker when Hachidori sync fails
- Revert the proxy marker to its previous value when every settings write attempt fails, so it no longer claims a server that was never written
- Add a test for the all-writes-fail path
- Retry Windows managed runtime cleanup in the test while bun.exe stays locked after exit
2026-09-22 22:08:51 -07:00
sudacode d31a28c54f fix(dictionary): handle Hachidori extension load failures gracefully
- Catch errors from the Hachidori extension runtime and log them
- Clear the extension and session state and return null instead of rejecting
2026-09-22 21:54:49 -07:00
sudacode d100789232 fix(anki): regenerate sentence furigana when mined context changes
- Regenerate SentenceFurigana from the final sentence after timing-review expansion or stats-dashboard word mining
- Add a Yomitan parseText-based generator with a 10s timeout and escaped, highlighted output
- Clear stale furigana on failure so templates fall back to Sentence
- Keep existing furigana formatting when the sentence is unchanged
- Document the behavior in the anki-integration and immersion-tracking docs
2026-09-22 21:46:06 -07:00
sudacode 9a5fb02cbd fix(dictionary): sync Hachidori deck to ankiConnect.deck for polling
- Always set the first Hachidori Anki template deck to ankiConnect.deck so polling mode enriches Hachidori cards; other custom template settings stay intact
- Treat right-clicks retargeted to the Hachidori popup shadow host as overlay interactions so they no longer raise mpv or toggle pause
- Update anki-integration docs and changelog fragment
2026-09-22 21:43:53 -07:00
sudacode 2ba820f41a fix(dictionary): keep Hachidori lookup highlight on in SubMiner builds
Hachidori's overlay-mode first-install options turn off "Highlight the
word on the page" because GSM-style hosts screenshot the see-through
viewport. SubMiner captures media from mpv, and without the highlight
the subtitle sidebar gave no sign of which word was looked up or where
the match ended. The staged copy now seeds the highlight on; the fork
source is unchanged.
2026-09-22 21:06:19 -07:00
sudacode c39dbb5a8d fix(overlay): keep Hachidori attention from pausing playback
Hachidori's hachidori-popup-shown/hidden pair means "the reader needs
mouse events", which includes a left press anywhere on the overlay that
may start a selection. SubMiner treated it as popup visibility, so a
click paused and resumed mpv, and holding the button kept it paused.
The host element only exists after the first lookup, so the first
attempt (gate on an open popup pane when a host exists) still paused
on clicks before any lookup.

Once a Hachidori event has been seen, popup auto-pause now requires an
unhidden popup pane in the host's shadow root, and rechecks after each
successful lookup so drag-select popups still pause.
2026-09-22 21:06:19 -07:00
sudacode 2958591ec9 fix(dictionary): open Hachidori hover popups only over subtitle glyphs
Pin the Hachidori fork fix that stops the caret snapping into the line box
from looking up a subtitle's first word when the pointer is beside the line,
which kept the popup open and playback paused.
2026-09-22 20:39:35 -07:00
sudacode c9880efee3 test(anilist): apply sqlite pragmas in cover art fetcher tests
- Call applyPragmas before ensureSchema so test databases match the production setup
2026-09-22 19:30:31 -07:00
sudacode f225ac3c89 fix(dictionary): block --yomitan when an external profile is configured
- Keep read-only external-profile mode while Hachidori is active by checking the configured externalProfilePath
- Hachidori settings remain available
- Smoke check asserts the external-profile block, with a --bundled-yomitan path for the bundled case
- Update usage docs and changelog fragment
2026-09-22 19:03:39 -07:00
sudacode cacaa2b371 feat(overlay): add subtitle selection modal and Jellyfin 12 fixes
- Add an optional subtitle selection modal for primary/secondary mpv tracks (subtitleSelection.enabled, g-s sequence shortcut) with key-sequence conflict handling
- Authenticate Jellyfin URLs with the ApiKey query, answer remote keep-alives, clear now-playing on stop, and restore episode titles in Anki misc info
- Honor the configured mpv executable when Jellyfin starts playback via a shared mpv-process launcher
- Bump electron-builder to 26.16.1
- Condense and reconcile changelog fragments; update config example and docs
2026-09-22 18:51:59 -07:00
sudacode 9fe4c7eb33 fix(anki): preserve managed proxy state when settings sync fails 2026-09-22 15:49:24 -07:00
sudacode ab5e50adac fix(dictionary): complete Hachidori imports and mining integration 2026-09-22 15:01:23 -07:00
sudacode 15c74ef502 fix(dictionary): update Hachidori overlay sharing and test fixes 2026-09-22 14:26:32 -07:00
sudacode f0d3c95b4c fix(dictionary): pin Hachidori stats mining fix 2026-09-22 14:04:09 -07:00
sudacode d7e1d7edc6 refactor: configure Hachidori in the SubMiner build 2026-09-22 12:26:37 -07:00
sudacode 5354fdf7b9 refactor(dictionary): use Hachidori fork submodule and upstream API 2026-09-22 01:22:14 -07:00
sudacode d9fdc7ef6d feat(dictionary): add Hachidori backend support
- Add backend selection, setup gating, Anki integration, and external host support
- Add launcher flags, documentation, packaging, and focused tests
- Open on-demand overlay modals on the first attempt
2026-09-22 00:21:19 -07:00
37 changed files with 139 additions and 352 deletions

No files matched your search

-45
View File
@@ -1,50 +1,5 @@
# Changelog
## v0.21.0 (2026-10-24)
### Added
- **Hachidori Dictionary Backend**:
- Hachidori is now bundled as an alternative to Yomitan. Select it with `dictionaryBackend`, then restart SubMiner.
- It works with subtitle scanning, popups, frequency highlighting, character dictionaries, Anki mining, and stats dashboard mining.
- First-run setup builds Hachidori's Anki template from your SubMiner settings. It can also link an external Hachidori app, browser, or Docker host.
- The tray and dictionary-settings shortcut open settings for the active backend. Use `--hachidori` or `--yomitan` to open a specific one.
- **Jellyfin Subtitle Generation**: You can now generate Japanese subtitles while playing a Jellyfin episode, with both direct play and transcoded playback.
### Fixed
- **Jellyfin**:
- `subminer jellyfin -p` now lists libraries and shows image previews when SubMiner is already running. Real errors are shown instead of an empty list.
- Watch progress from terminal playback is reported to Jellyfin even with cast discovery off.
- When `subminer jellyfin -p` starts SubMiner, the app shows its tray icon and quits when mpv closes.
- Casting on macOS now finds mpv when SubMiner is opened from Finder, the Dock, or Spotlight.
- **Halfwidth Katakana**: Halfwidth katakana in subtitles now displays as fullwidth, including in mined cards and older stats lines. Stats sentence search matches katakana in either width.
- **Media Timing Review**: The review opens on the first attempt instead of after a delay. Playback stays paused while it is open.
- **Subtitle Annotations**: Lines shown while two cues overlap now get annotations right away. Prefetching no longer restarts during startup.
- **Keyboard-Only Mode**: `h`, `l`, and the arrow keys now work for typing in modal text fields instead of seeking mpv.
- **Stats Dashboard Mining**: Cards mined from the stats dashboard keep the selected history line and media.
- **Hyprland and Sway**: The app no longer freezes when `hyprctl` or `swaymsg` is slow or hangs.
- **Subtitle Sidebar**: The open sidebar does much less background work during playback.
- **macOS Launcher Status**: The command-line launcher no longer shows "Not on PATH" when SubMiner is opened from Finder or the Dock.
- **Security Updates**: Updated Electron, undici, and follow-redirects to address security advisories.
### Docs
- **Hachidori Setup**: Documented backend selection, Hachidori dictionary and Anki setup, settings shortcuts, and external host linking.
- **Jellyfin Casting**: Explained when SubMiner joins the cast menu at startup, and how casting finds mpv on each platform. Added a troubleshooting entry for casts that never start mpv.
- **Jimaku Key**: Removed environment variables that never had any effect. Set the key with `jimaku.apiKey` or `jimaku.apiKeyCommand`.
<details>
<summary>Internal changes</summary>
### Internal
- **End-to-End Harness**: Added a headless e2e harness (`bun run test:e2e`, `bun run e2e`) with real mpv, a fixture dictionary, and a fake AnkiConnect.
- **Hachidori Fork**: The bundled Hachidori integration lives in a pinned fork submodule.
- **Dead Code Removal**:
- Removed the unused launcher Jimaku client, the mpv plugin hover-highlight module, and the unused character dictionary config keys.
- Removed 16 unused overlay IPC methods. SubMiner no longer forwards every ASS line to the overlay.
- Typecheck now runs with `noUnusedLocals`.
- **Setup Launcher Label**: First-run setup now labels a missing launcher "Not set up" instead of "Not installed".
</details>
## v0.20.1 (2026-10-03)
### Added
+1 -1
View File
@@ -201,7 +201,7 @@ The AppImage is all you need. First-run setup can install the optional `subminer
Download the latest DMG from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest) and drag `SubMiner.app` into `/Applications`.
Then enable SubMiner under **System Settings > Privacy & Security > Accessibility** (named **Device Control and Data Access** on macOS 27) so the overlay can track the mpv window. Without it, SubMiner falls back to a basic window lookup that cannot tell when mpv is minimized. If macOS blocks the first launch, right-click the app and choose **Open**.
Then enable SubMiner under **System Settings > Privacy & Security > Accessibility**, or the overlay cannot follow the mpv window. If macOS blocks the first launch, right-click the app and choose **Open**.
</details>
+6 -7
View File
@@ -19,12 +19,12 @@
"@electron/asar": "3.4.1",
"@types/node": "^24.10.0",
"@types/ws": "^8.18.1",
"electron": "43.8.3",
"electron": "43.7.7",
"electron-builder": "26.16.1",
"esbuild": "^0.25.12",
"prettier": "^3.8.1",
"typescript": "^5.9.3",
"undici": "7.30.1",
"undici": "7.29.1",
},
},
},
@@ -37,7 +37,6 @@
"brace-expansion": "5.0.11",
"electron-builder-squirrel-windows": "26.16.1",
"fast-uri": "3.1.7",
"follow-redirects": "1.16.1",
"form-data": "4.0.6",
"ip-address": "10.2.0",
"js-yaml": "4.3.2",
@@ -46,7 +45,7 @@
"picomatch": "4.0.4",
"tar": "7.5.21",
"tmp": "0.2.7",
"undici": "7.30.1",
"undici": "7.29.1",
},
"packages": {
"@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
@@ -305,7 +304,7 @@
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
"electron": ["electron@43.8.3", "", { "dependencies": { "@electron-internal/extract-zip": "^1.0.1", "@electron/get": "^5.0.0", "@types/node": "^24.9.0" }, "bin": { "electron": "cli.js", "install-electron": "install.js" } }, "sha512-KgvSm3bHXz96hSXzqxyRjLeli80rIdszbDQFKlRSfckmGazTPCH/CxUN31Dp9c5rFKTQSNf9gPJRWl1yWNB3pA=="],
"electron": ["electron@43.7.7", "", { "dependencies": { "@electron-internal/extract-zip": "^1.0.1", "@electron/get": "^5.0.0", "@types/node": "^24.9.0" }, "bin": { "electron": "cli.js", "install-electron": "install.js" } }, "sha512-y0pg+KhWLAkW2Ts2njkFFNFl3KtB1FmbUp9Te9i+R2Og2v1fcI/I6MADJ7jvnzB5UKdt+U+0EUoCnVL79xMPMA=="],
"electron-builder": ["electron-builder@26.16.1", "", { "dependencies": { "app-builder-lib": "26.16.1", "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.16.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "./cli.js", "install-app-deps": "./install-app-deps.js" } }, "sha512-LrLK65QX5PUYYODXqp23FKrV7CILTtVY7mrJckNknO9jLNSMiqFkKbSMiDRw4CjOADMPVDdWLxY4mezOZWswxg=="],
@@ -351,7 +350,7 @@
"filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="],
"follow-redirects": ["follow-redirects@1.16.1", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg=="],
"follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="],
"form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="],
@@ -617,7 +616,7 @@
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici": ["undici@7.30.1", "", {}, "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg=="],
"undici": ["undici@7.29.1", "", {}, "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+4
View File
@@ -0,0 +1,4 @@
type: internal
area: testing
- Added a headless end-to-end harness (`bun run test:e2e`, `bun run e2e`) that boots the built app with a real mpv, a synthetic clip, a fixture Yomitan dictionary, and a fake AnkiConnect on an offscreen display.
@@ -0,0 +1,4 @@
type: fixed
area: anki
- Media timing review now keeps playback paused while it is open when the dictionary popup or subtitle hover pause ends, as with Hachidori popups closing when the review appears. Playback resumes after the review closes unless a dictionary popup is still open.
+15
View File
@@ -0,0 +1,15 @@
type: added
area: dictionary
- Added a bundled Hachidori dictionary backend alongside the default Yomitan backend. Select it with `dictionaryBackend` and restart SubMiner.
- The tray and dictionary-settings shortcut follow the selected backend. `--hachidori` opens Hachidori settings, while `--yomitan` opens Yomitan settings unless a read-only external Yomitan profile is configured. This restriction also applies with Hachidori active, without blocking Hachidori settings.
- Hachidori integrates with subtitle scanning, popup controls, lookup tracking, character dictionaries, and Anki media enrichment, with separate dictionaries and settings for each backend. Character dictionaries reach a linked host over the link (Hachidori 0.2.3 or later, or a current Docker host), retry busy imports, and replace the previous dictionary only after a successful import. Older hosts report where to import the dictionary manually.
- Hachidori auto-populates its first Anki template from SubMiner's deck, tags (kept alongside its own `hachidori` tag), and field mappings, detects an unambiguous matching note type, and preserves existing custom templates apart from the deck, which follows `ankiConnect.deck` so polling mode enriches Hachidori cards. Anki discovery retries after an unavailable connection.
- Hachidori saves downloadable word audio before sending a note through SubMiner's Anki proxy, so freshly mined animated cards include the word-audio delay.
- Both bundled dictionary backends reload their background code on startup so extension updates take effect while preserving installed dictionaries and settings. Failed Yomitan connection-setting updates can be retried without manually resetting the managed Anki endpoint.
- First-run setup remembers each backend that finished it, including when reopened for legacy plugin cleanup, so switching back does not repeat setup, and the launcher gates playback on the backend the running app started with. Current incomplete or cancelled setup takes precedence over stale completion history. A running Yomitan session keeps using its external profile until it restarts into Hachidori.
- Stats dashboard mining and deck lookup use the selected backend, with the Anki proxy enabled or disabled. Dashboard cards retain the selected history line and media instead of being processed again with the current mpv subtitle, with both Yomitan and Hachidori. Stats cards carry a `SubMiner::Stats` tag so polling also preserves their context. Hachidori word mining supplies dictionary aliases, IDs, and frequency metadata to its native Anki renderer. Settings labels for popup pause and the dictionary deck no longer name Yomitan, and the backend selector sits with the other dictionary settings.
- Hachidori scans, dictionary counts, and settings reads wait for the dictionary engine to finish loading or importing instead of caching empty results.
- Character dictionary imports report completion as soon as the archive is read instead of waiting out the temporary server's keep-alive timeout.
- First-run setup can link an external Hachidori dictionary host in an app, browser, or Docker container, verify its library, and unlink back to local dictionaries. The optional host controls are collapsed by default and explain which apps or containers must stay running. Unresponsive connection checks time out so setup remains usable. Anki mining, media enrichment, and custom toolbar buttons stay in SubMiner, including when editing buttons while linked.
- Hachidori frequency highlighting reuses dictionary-entry ranks and fills missing ranks through its existing API. Entries without a matching definition may remain unranked. The bundled integration is maintained in a pinned fork submodule using upstream HoshiDicts and WASM binaries.
+4
View File
@@ -0,0 +1,4 @@
type: docs
area: dictionary
- Documented backend selection, separate Hachidori dictionary and Anki setup, the settings flags and shortcuts, and external host linking with local Anki mining and the running requirements for each host type.
+7
View File
@@ -0,0 +1,7 @@
type: fixed
area: jellyfin
- Fixed `subminer jellyfin -p` reporting "No Jellyfin libraries found." when SubMiner was already running (for example right after `subminer jellyfin` login). The app now hands library, item, and preview-auth results to the launcher directly instead of through log files, so picker image previews work again and real errors (expired session, unreachable server) are shown instead of an empty list.
- The rofi "Jellyfin Search (optional)" step after picking a library is now a compact input box with a "Type to search, or press Enter to browse all" hint instead of a blank list.
- Playing from the terminal (`subminer jellyfin -p`) now reports watch progress to Jellyfin even when cast discovery is off, so Jellyfin remembers where you stopped and marks finished episodes as played. Previously progress was only sent while the cast connection was active.
- When `subminer jellyfin -p` has to start SubMiner itself, the app now shows its tray icon and quits when you close mpv, the same as playing a local file. Before, it had no tray icon and kept running hidden after playback. A SubMiner that was already running is still reused and left running.
+4
View File
@@ -0,0 +1,4 @@
type: docs
area: jellyfin
- Corrected the Jellyfin docs: SubMiner only joins the cast menu at startup when `startupWarmups.jellyfinRemoteSession` is on (off by default) along with `jellyfin.remoteControlAutoConnect`. Otherwise tick **Jellyfin Discovery** in the tray.
+4
View File
@@ -0,0 +1,4 @@
type: added
area: subtitles
- Japanese subtitle generation now works while playing a Jellyfin episode. SubMiner reads the original file from the server, saves the subtitles to a cache folder, and loads them into mpv. Direct-play and transcoded playback are both supported.
+4
View File
@@ -0,0 +1,4 @@
type: fixed
area: overlay
- Keyboard-only mode no longer swallows `h`, `l`, and the left/right arrows while a modal is open or a text field has focus, so typing into fields like the Jimaku or TsukiHime title works and no longer seeks mpv. Pressing `Ctrl/Cmd+Shift+Y` (or the controller toggle) inside a modal no longer turns on a hidden keyboard-only mode in the modal window.
+4
View File
@@ -0,0 +1,4 @@
type: fixed
area: setup
- Fixed the command-line launcher showing "Not on PATH" on macOS when SubMiner was opened from Finder or the Dock, even though the launcher directory was already on your terminal PATH.
+4
View File
@@ -0,0 +1,4 @@
type: fixed
area: overlay
- Overlay modals such as the media timing review open on the first attempt when their dedicated window is created on demand, instead of after a four-second retry.
+5
View File
@@ -0,0 +1,5 @@
type: fixed
area: annotations
- Subtitle lines shown while two cues overlap (common in Jimaku and Jellyfin SRTs, and usually the long lines) are now prefetched, so their annotations appear immediately instead of after a delay.
- Subtitle prefetching no longer restarts each time the same subtitle file is re-selected during startup, and a line tokenized before a restart or seek is kept instead of discarded.
+4
View File
@@ -0,0 +1,4 @@
type: docs
area: launcher
- Removed `SUBMINER_JIMAKU_API_KEY`, `SUBMINER_JIMAKU_API_KEY_COMMAND`, and `SUBMINER_JIMAKU_API_BASE_URL` from the development docs' environment variable table. The launcher never used them; set the Jimaku key with `jimaku.apiKey` / `jimaku.apiKeyCommand` in the config instead.
+5
View File
@@ -0,0 +1,5 @@
type: internal
area: cleanup
- Removed dead code: the unused launcher Jimaku client (the `SUBMINER_JIMAKU_*` env vars it read were documented but had no effect), the mpv plugin's hover-highlight module (no sender since Overlay 2.0; custom mpv scripts that still send `subminer-hover-token` or `yomipv-hover-token` are now ignored), the never-read `anilist.characterDictionary.refreshTtlHours` / `evictionPolicy` config keys, unused root dependencies (eslint, geist fonts), and a committed draft plan doc.
- Typecheck now runs with `noUnusedLocals`; removed the unused locals, imports, and the Windows overlay focus-handoff values they fed, which were computed but never used.
+4
View File
@@ -0,0 +1,4 @@
type: internal
area: overlay
- Removed 16 overlay preload/IPC methods that nothing called, plus their main-process handlers. SubMiner no longer observes mpv's `sub-text/ass` or sends every ASS line to overlay windows that never listened.
+4
View File
@@ -0,0 +1,4 @@
type: fixed
area: setup
- First-run setup now labels a missing command-line launcher "Not set up" instead of "Not installed", so the AppImage catalog test no longer reads the setup window as an error screen.
+4
View File
@@ -0,0 +1,4 @@
type: fixed
area: overlay
- The open subtitle sidebar no longer re-fetches the whole subtitle list and re-queries mpv about 12 times a second. It polls only the current time and line, and reloads the list when the subtitle track, file, or parsed cues change (plus a slow ~2 s fallback), cutting idle sidebar IPC and renderer work during playback.
+5
View File
@@ -0,0 +1,5 @@
type: fixed
area: stats
- Subtitle lines recorded before halfwidth katakana widening now show fullwidth katakana in the stats dashboard (word/kanji occurrences, sentence search), and cards mined from stats history get the fullwidth sentence and word.
- Sentence search matches katakana in either width, so searching アンタ also finds older lines stored as アンタ.
+5
View File
@@ -0,0 +1,5 @@
type: fixed
area: overlay
- Hyprland and Sway window tracking no longer freezes the app when `hyprctl` or `swaymsg` is slow or hangs: polls now run in the background with a timeout and no longer pile up.
- The Hyprland/Sway trackers read each mpv process's command line once instead of spawning `ps` on every poll.
+4
View File
@@ -0,0 +1,4 @@
type: fixed
area: overlay
- Halfwidth katakana in subtitles (common in broadcast-derived Japanese subs, e.g. アンタ, ダキニ) now displays as normal fullwidth katakana, so lines no longer mix squashed and normal-width text. Mined sentences and stats get the fullwidth form too.
+2 -2
View File
@@ -741,10 +741,10 @@
// autoStartSubMiner starts SubMiner in the background; auto_start_overlay only controls visible overlay display.
// Set mpv.launchMode to choose normal, maximized, or fullscreen SubMiner-managed mpv playback.
// Set mpv.profile to pass an mpv profile to managed mpv launches; leave it blank to pass none.
// Leave mpv.executablePath blank to auto-discover mpv from SUBMINER_MPV_PATH, PATH, or (macOS) Homebrew and /Applications/mpv.app.
// Leave mpv.executablePath blank to auto-discover mpv.exe from SUBMINER_MPV_PATH or PATH.
// ==========================================
"mpv": {
"executablePath": "", // Optional absolute path to the mpv binary SubMiner starts itself (Windows launches, Jellyfin casting, timing preview). Leave empty to auto-discover from SUBMINER_MPV_PATH, PATH, and on macOS the Homebrew and /Applications/mpv.app locations.
"executablePath": "", // Optional absolute path to mpv.exe for Windows launch flows. Leave empty to auto-discover from SUBMINER_MPV_PATH or PATH.
"launchMode": "normal", // Default window state for SubMiner-managed mpv launches. Values: normal | maximized | fullscreen
"profile": "", // Optional mpv profile name passed to SubMiner-managed mpv launches. Leave empty to pass no profile.
"socketPath": "/tmp/subminer-socket", // mpv IPC socket path used by SubMiner-managed playback and the bundled mpv plugin.
+3 -53
View File
@@ -1,56 +1,6 @@
# Changelog
## v0.21.0 (2026-10-24)
**Added**
- **Hachidori Dictionary Backend**:
- Hachidori is now bundled as an alternative to Yomitan. Select it with `dictionaryBackend`, then restart SubMiner.
- It works with subtitle scanning, popups, frequency highlighting, character dictionaries, Anki mining, and stats dashboard mining.
- First-run setup builds Hachidori's Anki template from your SubMiner settings. It can also link an external Hachidori app, browser, or Docker host.
- The tray and dictionary-settings shortcut open settings for the active backend. Use `--hachidori` or `--yomitan` to open a specific one.
- **Jellyfin Subtitle Generation**: You can now generate Japanese subtitles while playing a Jellyfin episode, with both direct play and transcoded playback.
**Fixed**
- **Jellyfin**:
- `subminer jellyfin -p` now lists libraries and shows image previews when SubMiner is already running. Real errors are shown instead of an empty list.
- Watch progress from terminal playback is reported to Jellyfin even with cast discovery off.
- When `subminer jellyfin -p` starts SubMiner, the app shows its tray icon and quits when mpv closes.
- Casting on macOS now finds mpv when SubMiner is opened from Finder, the Dock, or Spotlight.
- **Halfwidth Katakana**: Halfwidth katakana in subtitles now displays as fullwidth, including in mined cards and older stats lines. Stats sentence search matches katakana in either width.
- **Media Timing Review**: The review opens on the first attempt instead of after a delay. Playback stays paused while it is open.
- **Subtitle Annotations**: Lines shown while two cues overlap now get annotations right away. Prefetching no longer restarts during startup.
- **Keyboard-Only Mode**: `h`, `l`, and the arrow keys now work for typing in modal text fields instead of seeking mpv.
- **Stats Dashboard Mining**: Cards mined from the stats dashboard keep the selected history line and media.
- **Hyprland and Sway**: The app no longer freezes when `hyprctl` or `swaymsg` is slow or hangs.
- **Subtitle Sidebar**: The open sidebar does much less background work during playback.
- **macOS Launcher Status**: The command-line launcher no longer shows "Not on PATH" when SubMiner is opened from Finder or the Dock.
- **Security Updates**: Updated Electron, undici, and follow-redirects to address security advisories.
**Docs**
- **Hachidori Setup**: Documented backend selection, Hachidori dictionary and Anki setup, settings shortcuts, and external host linking.
- **Jellyfin Casting**: Explained when SubMiner joins the cast menu at startup, and how casting finds mpv on each platform. Added a troubleshooting entry for casts that never start mpv.
- **Jimaku Key**: Removed environment variables that never had any effect. Set the key with `jimaku.apiKey` or `jimaku.apiKeyCommand`.
<details>
<summary>Internal changes</summary>
**Internal**
- **End-to-End Harness**: Added a headless e2e harness (`bun run test:e2e`, `bun run e2e`) with real mpv, a fixture dictionary, and a fake AnkiConnect.
- **Hachidori Fork**: The bundled Hachidori integration lives in a pinned fork submodule.
- **Dead Code Removal**:
- Removed the unused launcher Jimaku client, the mpv plugin hover-highlight module, and the unused character dictionary config keys.
- Removed 16 unused overlay IPC methods. SubMiner no longer forwards every ASS line to the overlay.
- Typecheck now runs with `noUnusedLocals`.
- **Setup Launcher Label**: First-run setup now labels a missing launcher "Not set up" instead of "Not installed".
</details>
## Previous Versions
<details>
<summary>v0.20.x</summary>
<h2>v0.20.1 (2026-10-03)</h2>
## v0.20.1 (2026-10-03)
**Added**
- **YouTube Browser Window**:
@@ -100,7 +50,7 @@
</details>
<h2>v0.20.0 (2026-09-23)</h2>
## v0.20.0 (2026-09-23)
**Added**
- **Japanese Subtitle Generation**:
@@ -221,7 +171,7 @@
</details>
</details>
## Previous Versions
<details>
<summary>v0.19.x</summary>
+12 -12
View File
@@ -585,18 +585,18 @@ A local web dashboard at `http://127.0.0.1:<serverPort>`, also available as an o
Settings for mpv instances that SubMiner starts, and for the bundled mpv plugin. See [mpv plugin](/mpv-plugin).
| Key | Default | What it does |
| ---------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------- |
| `mpv.executablePath` | `""` | Full path to mpv. Empty auto-discovers it ([lookup order](/jellyfin-integration#casting-from-jellyfin)) |
| `mpv.launchMode` | `"normal"` | Window state: `normal`, `maximized`, or `fullscreen` |
| `mpv.profile` | `""` | mpv profile to pass. Combined with a launcher `--profile` if both are set |
| `mpv.socketPath` | platform-specific | mpv IPC socket. See the warning under [Config file](#configuration-file) |
| `mpv.backend` | `"auto"` | Window tracking: `auto`, `hyprland`, `sway`, `x11`, `macos`, `windows` |
| `mpv.autoStartSubMiner` | `true` | Start SubMiner in the background when mpv loads a file |
| `mpv.pauseUntilOverlayReady` | `true` | Keep mpv paused until subtitles are ready, up to 30 seconds |
| `mpv.subminerBinaryPath` | `""` | SubMiner app path for the plugin. Empty uses the detected path |
| `mpv.aniskipEnabled` | `true` | Detect intros with AniSkip and show a skip prompt |
| `mpv.aniskipButtonKey` | `"TAB"` | mpv key that skips the intro while the prompt is shown |
| Key | Default | What it does |
| ---------------------------- | ----------------- | --------------------------------------------------------------------------- |
| `mpv.executablePath` | `""` | Path to `mpv.exe` on Windows. Empty checks `SUBMINER_MPV_PATH`, then `PATH` |
| `mpv.launchMode` | `"normal"` | Window state: `normal`, `maximized`, or `fullscreen` |
| `mpv.profile` | `""` | mpv profile to pass. Combined with a launcher `--profile` if both are set |
| `mpv.socketPath` | platform-specific | mpv IPC socket. See the warning under [Config file](#configuration-file) |
| `mpv.backend` | `"auto"` | Window tracking: `auto`, `hyprland`, `sway`, `x11`, `macos`, `windows` |
| `mpv.autoStartSubMiner` | `true` | Start SubMiner in the background when mpv loads a file |
| `mpv.pauseUntilOverlayReady` | `true` | Keep mpv paused until subtitles are ready, up to 30 seconds |
| `mpv.subminerBinaryPath` | `""` | SubMiner app path for the plugin. Empty uses the detected path |
| `mpv.aniskipEnabled` | `true` | Detect intros with AniSkip and show a skip prompt |
| `mpv.aniskipButtonKey` | `"TAB"` | mpv key that skips the intro while the prompt is shown |
### YouTube playback settings
+1 -1
View File
@@ -187,7 +187,7 @@ First-run setup can install the `subminer` command for you.
xattr -d com.apple.quarantine /Applications/SubMiner.app
```
3. Open **System Settings > Privacy & Security > Accessibility** and enable SubMiner (add it if it is missing). On macOS 27 this list is named **Device Control and Data Access**. SubMiner uses it to track the mpv window. Without it, SubMiner falls back to a basic window lookup that cannot tell when mpv is minimized.
3. Open **System Settings > Privacy & Security > Accessibility** and enable SubMiner (add it if it is missing). The overlay cannot follow the mpv window without this.
First-run setup can install the `subminer` command for you.
+1 -1
View File
@@ -25,7 +25,7 @@ SubMiner selects a Japanese subtitle track automatically and resets mpv's subtit
If an episode has no Japanese subtitles, you can [generate them with Whisper](/subtitle-generation#generating-from-the-overlay) while it plays.
Casting finds mpv through `mpv.executablePath`, then `SUBMINER_MPV_PATH`, then `PATH`. On macOS it then checks `/opt/homebrew/bin/mpv`, `/usr/local/bin/mpv`, and `/Applications/mpv.app`, because an app opened from Finder, the Dock, or Spotlight does not get your shell's `PATH`. If mpv lives somewhere else, set `mpv.executablePath` to its full path (`command -v mpv` prints it). An invalid `mpv.executablePath` stops mpv from starting.
On Windows, casting finds mpv through `mpv.executablePath`, then `SUBMINER_MPV_PATH`, then `PATH`. An invalid `mpv.executablePath` stops mpv from starting.
## Playing from the terminal
+2 -2
View File
@@ -741,10 +741,10 @@
// autoStartSubMiner starts SubMiner in the background; auto_start_overlay only controls visible overlay display.
// Set mpv.launchMode to choose normal, maximized, or fullscreen SubMiner-managed mpv playback.
// Set mpv.profile to pass an mpv profile to managed mpv launches; leave it blank to pass none.
// Leave mpv.executablePath blank to auto-discover mpv from SUBMINER_MPV_PATH, PATH, or (macOS) Homebrew and /Applications/mpv.app.
// Leave mpv.executablePath blank to auto-discover mpv.exe from SUBMINER_MPV_PATH or PATH.
// ==========================================
"mpv": {
"executablePath": "", // Optional absolute path to the mpv binary SubMiner starts itself (Windows launches, Jellyfin casting, timing preview). Leave empty to auto-discover from SUBMINER_MPV_PATH, PATH, and on macOS the Homebrew and /Applications/mpv.app locations.
"executablePath": "", // Optional absolute path to mpv.exe for Windows launch flows. Leave empty to auto-discover from SUBMINER_MPV_PATH or PATH.
"launchMode": "normal", // Default window state for SubMiner-managed mpv launches. Values: normal | maximized | fullscreen
"profile": "", // Optional mpv profile name passed to SubMiner-managed mpv launches. Leave empty to pass no profile.
"socketPath": "/tmp/subminer-socket", // mpv IPC socket path used by SubMiner-managed playback and the bundled mpv plugin.
+2 -9
View File
@@ -39,18 +39,11 @@ SubMiner reads subtitles from mpv over an IPC socket (a named pipe on Windows).
SubMiner reconnects on its own if mpv restarts.
## Casting from Jellyfin does nothing
When `connect ENOENT /tmp/subminer-socket` follows a cast in the app log, SubMiner could not start mpv, so the socket never appeared. Look for `Failed handling Jellyfin remote Play event` or `Failed to launch mpv for Jellyfin remote playback` just before it.
- Check that `mpv --version` works in a terminal.
- If it does, SubMiner is not finding mpv where it is installed. Set `mpv.executablePath` to the output of `command -v mpv`. See [Casting from Jellyfin](/jellyfin-integration#casting-from-jellyfin) for where SubMiner looks.
## Overlay does not appear
- Confirm SubMiner is running (`SubMiner.AppImage --start`, or check for the process).
- Linux: Hyprland and Sway work natively. Any other compositor needs mpv and SubMiner under X11 or Xwayland, with `xdotool`, `xprop`, and `xwininfo` installed. See [KDE Plasma and other Wayland compositors](#kde-plasma-and-other-wayland-compositors).
- macOS: grant Accessibility permission. See [macOS](#macos).
- macOS: grant Accessibility permission in System Settings > Privacy & Security > Accessibility.
## Overlay is on the wrong monitor or position
@@ -262,5 +255,5 @@ mpv --gpu-context=x11vk,x11egl,x11 video.mkv
## macOS
- Grant Accessibility permission under System Settings > Privacy & Security > Accessibility (named **Device Control and Data Access** on macOS 27). Without it, SubMiner falls back to a basic window lookup that cannot tell when mpv is minimized.
- Accessibility permission is required for window tracking: System Settings > Privacy & Security > Accessibility.
- If Gatekeeper blocks the app, right-click it and choose Open, or run `xattr -d com.apple.quarantine /path/to/SubMiner.app`.
+4 -5
View File
@@ -2,7 +2,7 @@
"name": "subminer",
"productName": "SubMiner",
"desktopName": "SubMiner.desktop",
"version": "0.21.0",
"version": "0.20.1",
"description": "All-in-one sentence mining overlay with AnkiConnect and dictionary integration",
"packageManager": "bun@1.3.5",
"main": "dist/main-entry.js",
@@ -93,7 +93,6 @@
"brace-expansion": "5.0.11",
"electron-builder-squirrel-windows": "26.16.1",
"fast-uri": "3.1.7",
"follow-redirects": "1.16.1",
"form-data": "4.0.6",
"ip-address": "10.2.0",
"js-yaml": "4.3.2",
@@ -102,7 +101,7 @@
"picomatch": "4.0.4",
"tar": "7.5.21",
"tmp": "0.2.7",
"undici": "7.30.1"
"undici": "7.29.1"
},
"keywords": [
"anki",
@@ -131,12 +130,12 @@
"@electron/asar": "3.4.1",
"@types/node": "^24.10.0",
"@types/ws": "^8.18.1",
"electron": "43.8.3",
"electron": "43.7.7",
"electron-builder": "26.16.1",
"esbuild": "^0.25.12",
"prettier": "^3.8.1",
"typescript": "^5.9.3",
"undici": "7.30.1"
"undici": "7.29.1"
},
"build": {
"appId": "com.sudacode.SubMiner",
-60
View File
@@ -1,60 +0,0 @@
## Highlights
### Added
- **Hachidori Dictionary Backend**: Bundled Hachidori backend as an alternative to Yomitan. Select it with `dictionaryBackend` and restart SubMiner.
- Works with subtitle scanning, popups, frequency highlighting, character dictionaries, Anki mining, and stats dashboard mining.
- First-run setup builds Hachidori's Anki template from your existing SubMiner deck, tags, and field mappings.
- Can link an external Hachidori app, browser, or Docker host while Anki mining stays in SubMiner.
- The tray and dictionary-settings shortcut open the selected backend's settings. `--hachidori` and `--yomitan` open a specific one.
- **Jellyfin Subtitle Generation**: Japanese subtitle generation now works while playing Jellyfin episodes, with both direct-play and transcoded playback.
### Fixed
- **Media Timing Review**: Opens on the first try and keeps playback paused while open, even when a dictionary popup closes.
- **Jellyfin Terminal Playback**:
- `subminer jellyfin -p` lists libraries when SubMiner is already running, and picker image previews work again.
- Real errors, like an expired session or an unreachable server, now appear instead of an empty list.
- Watch progress and played status reach Jellyfin even when cast discovery is off.
- When it starts SubMiner itself, SubMiner now shows a tray icon and quits when mpv closes.
- The rofi search step is now a compact input box.
- **macOS Casting**: Jellyfin casting finds mpv when SubMiner is opened from Finder, the Dock, or Spotlight. `mpv.executablePath` and `SUBMINER_MPV_PATH` are now honored on macOS and Linux.
- **macOS Launcher PATH**: The command-line launcher no longer shows "Not on PATH" when SubMiner is opened from Finder or the Dock.
- **Keyboard-Only Mode**: `h`, `l`, and the arrow keys type normally in modals and text fields instead of seeking mpv. Toggling the mode inside a modal no longer turns on a hidden keyboard-only mode.
- **Halfwidth Katakana**: Halfwidth katakana in subtitles now displays as fullwidth, including in mined cards and the stats dashboard. Stats sentence search matches katakana in either width.
- **Subtitle Annotations**: Lines shown while two cues overlap now get annotations right away. Prefetching no longer restarts during startup.
- **Subtitle Sidebar**: The open sidebar does far less work during playback.
- **Hyprland and Sway**: A slow or hung `hyprctl` or `swaymsg` no longer freezes the app.
- **Stats Mining**: Cards mined from the stats dashboard keep the selected history line and media.
- **Security Updates**: Updated Electron, undici, and follow-redirects to fix new security advisories.
### Docs
- **Hachidori Setup**: Covers backend selection, Hachidori dictionary and Anki setup, settings flags and shortcuts, and linking an external host.
- **Jellyfin**: The docs now say SubMiner joins the cast menu at startup only when both `startupWarmups.jellyfinRemoteSession` and `jellyfin.remoteControlAutoConnect` are on. Otherwise, enable **Jellyfin Discovery** in the tray.
- **Casting and mpv**: Added how casting finds mpv on each platform, a troubleshooting entry for casts that never start mpv, and a note on the macOS 27 Accessibility list rename.
- **Jimaku Environment Variables**: Removed the unused `SUBMINER_JIMAKU_*` variables from the docs. Set the key with `jimaku.apiKey` or `jimaku.apiKeyCommand` instead.
## What's Changed
- feat(dictionary): add Hachidori backend support by @ksyasuda in #266
- fix(jellyfin): make terminal play work with a running app and report progress by @ksyasuda in #288
- fix(overlay): stop Hyprland/Sway tracker polls from blocking the main thread by @ksyasuda in #289
- fix(overlay): poll cheap playback state in the open subtitle sidebar by @ksyasuda in #290
- fix(overlay): keep keyboard mode from swallowing modal and text-field keys by @ksyasuda in #291
- refactor: remove dead code and enable noUnusedLocals by @ksyasuda in #292
- feat(subtitles): generate Japanese subtitles during Jellyfin playback by @ksyasuda in #294
- fix(subtitles): widen halfwidth katakana where subtitle text enters the app by @ksyasuda in #295
- fix(jellyfin): find mpv when SubMiner is launched from Finder by @ksyasuda in #298
## Installation
See the README and docs/installation guide for full setup steps.
## Assets
- Linux: `SubMiner.AppImage`
- macOS: `SubMiner-*.dmg` and `SubMiner-*.zip`
- Windows: `SubMiner-*.exe` and `SubMiner-*-win.zip`
- Optional extras: `subminer-assets.tar.gz`, the `subminer` launcher, and the Windows `subminer.cmd` launcher
- Bun corresponding source: `bun-v1.3.5-source.tar.gz` and its `.sha256` file
Both launcher downloads use Bun included with the SubMiner app. Download `subminer` on Linux or macOS and `subminer.cmd` on Windows.
The app bundles an unmodified Bun 1.3.5 runtime. Bun is MIT licensed and statically links JavaScriptCore (LGPL 2.0) and TinyCC (LGPL 2.1). License texts and third-party notices ship inside the app under `resources/bun/licenses`, and the source archive above contains the matching Bun, WebKit, and dependency sources for relinking.
@@ -554,7 +554,7 @@ export function buildIntegrationConfigOptionRegistry(
kind: 'string',
defaultValue: defaultConfig.mpv.executablePath,
description:
'Optional absolute path to the mpv binary SubMiner starts itself (Windows launches, Jellyfin casting, timing preview). Leave empty to auto-discover from SUBMINER_MPV_PATH, PATH, and on macOS the Homebrew and /Applications/mpv.app locations.',
'Optional absolute path to mpv.exe for Windows launch flows. Leave empty to auto-discover from SUBMINER_MPV_PATH or PATH.',
},
{
path: 'mpv.launchMode',
+1 -1
View File
@@ -223,7 +223,7 @@ const INTEGRATION_TEMPLATE_SECTIONS: ConfigTemplateSection[] = [
'autoStartSubMiner starts SubMiner in the background; auto_start_overlay only controls visible overlay display.',
'Set mpv.launchMode to choose normal, maximized, or fullscreen SubMiner-managed mpv playback.',
'Set mpv.profile to pass an mpv profile to managed mpv launches; leave it blank to pass none.',
'Leave mpv.executablePath blank to auto-discover mpv from SUBMINER_MPV_PATH, PATH, or (macOS) Homebrew and /Applications/mpv.app.',
'Leave mpv.executablePath blank to auto-discover mpv.exe from SUBMINER_MPV_PATH or PATH.',
],
key: 'mpv',
},
+3 -10
View File
@@ -402,11 +402,7 @@ import {
getConfiguredWindowsMpvPathStatus,
launchWindowsMpv,
} from './main/runtime/windows-mpv-launch';
import {
findMpvExecutablePath,
resolveMpvExecutablePath,
spawnMpvProcess,
} from './main/runtime/mpv-process';
import { resolveMpvExecutablePath, spawnMpvProcess } from './main/runtime/mpv-process';
import { createWaitForMpvConnectedHandler } from './main/runtime/jellyfin-remote-connection';
import {
DEFAULT_JELLYFIN_CLIENT_NAME,
@@ -3083,11 +3079,8 @@ const mediaTimingReviewRuntime = createMediaTimingReviewRuntime({
getMpvClient: () => appState.mpvClient,
getCurrentMediaPath: () =>
appState.currentMediaPath?.trim() || appState.mpvClient?.currentVideoPath?.trim() || null,
// An invalid configured path is passed through so the preview fails instead of using PATH mpv.
getMpvExecutablePath: () => {
const configuredPath = configService.getConfig().mpv.executablePath;
return findMpvExecutablePath(configuredPath) || configuredPath;
},
getMpvExecutablePath: () =>
configService.getConfig().mpv.executablePath || process.env.SUBMINER_MPV_PATH?.trim() || '',
createPreviewSession: () => new MediaTimingPreviewSession(),
generateWaveform: (options) => generateSpeechWaveform(options),
generateFrame: (options) => mediaTimingFrameExtractor.generate(options),
-75
View File
@@ -5,11 +5,9 @@ import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import {
createPosixMpvPathDeps,
createWindowsMpvPathDeps,
getConfiguredWindowsMpvPathStatus,
resolveMpvExecutablePath,
resolvePosixMpvPath,
resolveWindowsMpvPath,
spawnMpvProcess,
} from './mpv-process';
@@ -97,79 +95,6 @@ test('resolveWindowsMpvPath returns blank when nothing resolves', () => {
assert.equal(resolveWindowsMpvPath(missing.deps), '');
});
// launchd's default PATH, which apps opened from Finder, the Dock, or Spotlight inherit.
const LAUNCHD_PATH = '/usr/bin:/bin:/usr/sbin:/sbin';
function makePosixDeps(options: {
platform: NodeJS.Platform;
executables?: string[];
env?: Record<string, string>;
}) {
const executables = new Set(options.executables ?? []);
return createPosixMpvPathDeps({
platform: options.platform,
getEnv: (name) => options.env?.[name],
isExecutable: (candidate) => executables.has(candidate),
});
}
test('resolvePosixMpvPath prefers a configured executable and does not fall back when it is invalid', () => {
const deps = makePosixDeps({
platform: 'darwin',
executables: ['/cfg/mpv', '/opt/homebrew/bin/mpv'],
env: { PATH: LAUNCHD_PATH },
});
assert.equal(resolvePosixMpvPath(deps, ' /cfg/mpv '), '/cfg/mpv');
assert.equal(resolvePosixMpvPath(deps, '/missing/mpv'), '');
});
test('resolvePosixMpvPath uses SUBMINER_MPV_PATH before searching PATH', () => {
const deps = makePosixDeps({
platform: 'linux',
executables: ['/env/mpv', '/usr/bin/mpv'],
env: { SUBMINER_MPV_PATH: ' /env/mpv ', PATH: '/usr/bin' },
});
assert.equal(resolvePosixMpvPath(deps), '/env/mpv');
});
test('resolvePosixMpvPath takes the first PATH entry that has mpv', () => {
const deps = makePosixDeps({
platform: 'darwin',
executables: ['/second/mpv', '/third/mpv', '/opt/homebrew/bin/mpv'],
env: { SUBMINER_MPV_PATH: '/stale/mpv', PATH: '/first::/second:/third' },
});
assert.equal(resolvePosixMpvPath(deps), '/second/mpv');
});
test('resolvePosixMpvPath finds Homebrew mpv on macOS when the app only has the launchd PATH', () => {
const homebrew = makePosixDeps({
platform: 'darwin',
executables: ['/opt/homebrew/bin/mpv'],
env: { PATH: LAUNCHD_PATH },
});
const appBundle = makePosixDeps({
platform: 'darwin',
executables: ['/Applications/mpv.app/Contents/MacOS/mpv'],
env: { PATH: LAUNCHD_PATH },
});
assert.equal(resolvePosixMpvPath(homebrew), '/opt/homebrew/bin/mpv');
assert.equal(resolvePosixMpvPath(appBundle), '/Applications/mpv.app/Contents/MacOS/mpv');
});
test('resolvePosixMpvPath only probes macOS install locations on macOS', () => {
const deps = makePosixDeps({
platform: 'linux',
executables: ['/opt/homebrew/bin/mpv'],
env: { PATH: LAUNCHD_PATH },
});
assert.equal(resolvePosixMpvPath(deps), '');
});
const X11_SPAWN_CASES = [
{
name: 'forces the X11 backend for an unsupported Wayland session',
+5 -66
View File
@@ -1,5 +1,4 @@
import fs from 'node:fs';
import path from 'node:path';
import { spawn, spawnSync } from 'node:child_process';
import {
MPV_X11_BACKEND_ARGS,
@@ -99,74 +98,14 @@ export function spawnMpvProcess(
});
}
export interface PosixMpvPathDeps {
platform: NodeJS.Platform;
getEnv: (name: string) => string | undefined;
isExecutable: (candidate: string) => boolean;
}
// Apps opened from Finder, the Dock, or Spotlight inherit launchd's minimal PATH
// (/usr/bin:/bin:/usr/sbin:/sbin), so Homebrew and app-bundle installs are probed directly.
const MACOS_MPV_FALLBACK_PATHS = [
'/opt/homebrew/bin/mpv',
'/usr/local/bin/mpv',
'/Applications/mpv.app/Contents/MacOS/mpv',
];
function isExecutableFile(candidate: string): boolean {
try {
fs.accessSync(candidate, fs.constants.X_OK);
return fs.statSync(candidate).isFile();
} catch {
return false;
}
}
export function createPosixMpvPathDeps(
overrides: Partial<PosixMpvPathDeps> = {},
): PosixMpvPathDeps {
return {
platform: overrides.platform ?? process.platform,
getEnv: overrides.getEnv ?? ((name) => process.env[name]),
isExecutable: overrides.isExecutable ?? isExecutableFile,
};
}
// Resolution order: mpv.executablePath, SUBMINER_MPV_PATH, PATH, then macOS install
// locations. A configured path that is not executable resolves to '' instead of falling back.
export function resolvePosixMpvPath(deps: PosixMpvPathDeps, configuredMpvPath = ''): string {
const configPath = configuredMpvPath.trim();
if (configPath) {
return deps.isExecutable(configPath) ? configPath : '';
}
const envPath = deps.getEnv('SUBMINER_MPV_PATH')?.trim();
if (envPath && deps.isExecutable(envPath)) {
return envPath;
}
const pathCandidates = (deps.getEnv('PATH') ?? '')
.split(path.posix.delimiter)
.filter((dir) => dir.length > 0)
.map((dir) => path.posix.join(dir, 'mpv'));
const candidates =
deps.platform === 'darwin' ? [...pathCandidates, ...MACOS_MPV_FALLBACK_PATHS] : pathCandidates;
return candidates.find((candidate) => deps.isExecutable(candidate)) ?? '';
}
// Returns the mpv binary SubMiner should spawn itself, or '' when none is found.
export function findMpvExecutablePath(configuredMpvPath = ''): string {
return process.platform === 'win32'
? resolveWindowsMpvPath(createWindowsMpvPathDeps(), configuredMpvPath)
: resolvePosixMpvPath(createPosixMpvPathDeps(), configuredMpvPath);
}
export function resolveMpvExecutablePath(configuredMpvPath = ''): string {
const executablePath = findMpvExecutablePath(configuredMpvPath);
const executablePath =
process.platform === 'win32'
? resolveWindowsMpvPath(createWindowsMpvPathDeps(), configuredMpvPath)
: 'mpv';
if (!executablePath) {
const binary = process.platform === 'win32' ? 'mpv.exe' : 'mpv';
throw new Error(
`Could not find ${binary}. Check mpv.executablePath, SUBMINER_MPV_PATH, or PATH.`,
'Could not find mpv.exe. Check mpv.executablePath, SUBMINER_MPV_PATH, or PATH.',
);
}
return executablePath;