diff --git a/bun.lock b/bun.lock index ffc6190f..aac8e779 100644 --- a/bun.lock +++ b/bun.lock @@ -27,7 +27,7 @@ "eslint": "^10.8.0", "prettier": "^3.8.1", "typescript": "^5.9.3", - "undici": "7.29.0", + "undici": "7.29.1", }, }, }, @@ -39,7 +39,7 @@ "app-builder-lib": "26.16.1", "brace-expansion": "5.0.9", "electron-builder-squirrel-windows": "26.16.1", - "fast-uri": "3.1.6", + "fast-uri": "3.1.7", "form-data": "4.0.6", "ip-address": "10.2.0", "js-yaml": "4.3.2", @@ -48,7 +48,7 @@ "picomatch": "4.0.4", "tar": "7.5.21", "tmp": "0.2.7", - "undici": "7.29.0", + "undici": "7.29.1", }, "packages": { "@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], @@ -407,7 +407,7 @@ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - "fast-uri": ["fast-uri@3.1.6", "", {}, "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q=="], + "fast-uri": ["fast-uri@3.1.7", "", {}, "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -717,7 +717,7 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "undici": ["undici@7.29.0", "", {}, "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="], + "undici": ["undici@7.29.1", "", {}, "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], diff --git a/changes/closed-pipe-crash.md b/changes/closed-pipe-crash.md new file mode 100644 index 00000000..06bad486 --- /dev/null +++ b/changes/closed-pipe-crash.md @@ -0,0 +1,4 @@ +type: fixed +area: app + +- SubMiner no longer crashes with "write EPIPE" when the terminal command that started it is stopped with Ctrl+C. diff --git a/changes/hyprland-fullscreen-refresh.md b/changes/hyprland-fullscreen-refresh.md new file mode 100644 index 00000000..d6d7e97c --- /dev/null +++ b/changes/hyprland-fullscreen-refresh.md @@ -0,0 +1,4 @@ +type: fixed +area: overlay + +- Keep the subtitle overlay mapped during fullscreen transitions on Hyprland, avoiding focus changes from repeated hide/show calls that can cancel mpv fullscreen entry. Preserve the hide/show refresh on Sway. diff --git a/changes/patched-electron-undici.md b/changes/patched-electron-undici.md new file mode 100644 index 00000000..65cca7fd --- /dev/null +++ b/changes/patched-electron-undici.md @@ -0,0 +1,4 @@ +type: fixed +area: dependencies + +- Updated Electron to 42.10.0 and undici to 7.29.1 to resolve the security advisories blocking CI. diff --git a/changes/timing-review-thin-handles.md b/changes/timing-review-thin-handles.md new file mode 100644 index 00000000..c5b08e0e --- /dev/null +++ b/changes/timing-review-thin-handles.md @@ -0,0 +1,5 @@ +type: fixed +area: overlay + +- Media timing review trim handles are now thin lines centered on the clip edge, so it's clear where the clip starts and ends and less of the waveform is covered. +- Original subtitle boundaries use orange bars without text tags, leaving the waveform unobstructed. diff --git a/changes/youtube-browser-window.md b/changes/youtube-browser-window.md new file mode 100644 index 00000000..3b33ac24 --- /dev/null +++ b/changes/youtube-browser-window.md @@ -0,0 +1,7 @@ +type: added +area: youtube + +- Added a YouTube browser window (`subminer youtube`/`subminer yt`, or tray **Browse YouTube**) that keeps your YouTube login across restarts. +- Clicking a video plays it in mpv instead of the page; middle-click, Shift/Ctrl-click, or the right-click menu queues it in mpv's playlist. +- Queued YouTube videos get the same Japanese subtitle setup when mpv reaches them. +- Closing the YouTube window quits a SubMiner started by `subminer youtube` (after mpv closes, if a video is still playing). diff --git a/changes/youtube-translated-caption-429.md b/changes/youtube-translated-caption-429.md new file mode 100644 index 00000000..aa6acfd1 --- /dev/null +++ b/changes/youtube-translated-caption-429.md @@ -0,0 +1,4 @@ +type: fixed +area: youtube + +- YouTube Japanese subtitles no longer fail with "HTTP 429" on videos where YouTube also lists a machine-translated Japanese track; SubMiner now picks the real track and falls back to yt-dlp if a direct subtitle download is refused. diff --git a/changes/youtube-whisper-subtitles.md b/changes/youtube-whisper-subtitles.md new file mode 100644 index 00000000..ae3cc246 --- /dev/null +++ b/changes/youtube-whisper-subtitles.md @@ -0,0 +1,7 @@ +type: added +area: youtube + +- Added `youtube.subtitleSource`: set it to `whisper` to transcribe YouTube videos with Whisper instead of downloading YouTube's captions. The default stays `youtube`. +- Whisper mode keeps the video paused while it transcribes, with progress in the subtitle generation modal; close the modal to keep watching, or cancel to continue without subtitles. +- The subtitle generation modal (`Ctrl+Shift+G`) now also works on YouTube videos, whatever `youtube.subtitleSource` is set to. +- Whisper mode downloads a small audio-only stream and deletes it once generation ends, or when you switch videos, close mpv, or quit. diff --git a/config.example.jsonc b/config.example.jsonc index 22640179..fe7f4f96 100644 --- a/config.example.jsonc +++ b/config.example.jsonc @@ -682,13 +682,14 @@ // ========================================== // YouTube Playback Settings // Defaults for managed subtitle language preferences and YouTube subtitle loading. - // Hot-reload: primarySubLanguages applies to the next YouTube subtitle load. + // Hot-reload: primarySubLanguages and subtitleSource apply to the next YouTube subtitle load. // ========================================== "youtube": { "primarySubLanguages": [ "ja", "jpn" ], // Comma-separated primary subtitle language priority for managed subtitle auto-selection. + "subtitleSource": "youtube", // Where primary YouTube subtitles come from. Whisper transcribes the audio locally using the subtitleGeneration settings. Values: youtube | whisper "mediaCache": { "mode": "direct", // How YouTube card audio/images are extracted. Values: direct | background "maxHeight": 720 // Maximum video height downloaded for the YouTube background media cache. Set to 0 for unlimited. diff --git a/docs-site/anki-integration.md b/docs-site/anki-integration.md index 785e9aae..ddc24b23 100644 --- a/docs-site/anki-integration.md +++ b/docs-site/anki-integration.md @@ -122,7 +122,9 @@ Media settings apply to the next card without a restart. With `media.reviewTiming` on, SubMiner pauses before making media for word, sentence, and audio cards and opens a review dialog. You can also toggle it for the current session with **Review Media Timing** in the runtime options palette (`Ctrl/Cmd+Shift+O`). Clipboard updates and stats-dashboard mining skip the review. -The dialog shows the clip over a speech waveform. When the waveform loads, an untouched clip end moves back to just after the last speech in the line. The Line end rail still marks the subtitle's own end. +The dialog shows the clip over a speech waveform. When the waveform loads, an untouched clip end moves back to just after the last speech in the line. The orange bars still mark the subtitle's original start and end. + +During preview, the yellow cursor follows the audio player's position through the selected range and stays at the end when playback finishes. Buffering pauses the cursor too. If the player stops reporting progress for 15 seconds, the preview stops so you can retry. | Action | How | | ------------------------ | --------------------------------------------------------------------- | diff --git a/docs-site/configuration.md b/docs-site/configuration.md index b3de3142..1ec87475 100644 --- a/docs-site/configuration.md +++ b/docs-site/configuration.md @@ -54,7 +54,7 @@ These apply live: - `subtitleStyle`, `subtitleSidebar`, `subtitleSelection`, `keybindings`, `shortcuts` - `logging.level`, `logging.rotation`, `logging.files` -- `secondarySub.defaultMode`, `youtube.primarySubLanguages` +- `secondarySub.defaultMode`, `youtube.primarySubLanguages`, `youtube.subtitleSource` - `mpv.aniskipEnabled`, `mpv.aniskipButtonKey`, `stats.toggleKey`, `stats.markWatchedKey` - `ankiConnect.deck`, `ankiConnect.fields.*`, `ankiConnect.behavior.autoUpdateNewCards` - `ankiConnect.media.normalizeAudio`, `media.mirrorMpvVolume`, `media.reviewTiming` @@ -594,11 +594,12 @@ Settings for mpv instances that SubMiner starts, and for the bundled mpv plugin. ### YouTube playback settings -Language and card-media settings for YouTube playback. YouTube always loads a Japanese primary and English secondary track, preferring manual uploads over auto captions. See [YouTube integration](/youtube-integration). +Subtitle, language, and card-media settings for YouTube playback. With YouTube captions, SubMiner loads a Japanese primary and English secondary track, preferring manual uploads over auto captions. See [YouTube integration](/youtube-integration). | Key | Default | What it does | | ------------------------------ | --------------- | -------------------------------------------------------------------------------------------- | | `youtube.primarySubLanguages` | `["ja", "jpn"]` | Languages that count as a valid primary track, also used for local playback | +| `youtube.subtitleSource` | `"youtube"` | `youtube` downloads YouTube's captions. `whisper` transcribes the audio with Whisper | | `youtube.mediaCache.mode` | `"direct"` | `direct` cuts card media from the stream. `background` downloads the video with yt-dlp first | | `youtube.mediaCache.maxHeight` | `720` | Maximum download height in `background` mode. `0` is unlimited | diff --git a/docs-site/launcher-script.md b/docs-site/launcher-script.md index bd459095..f0741a5e 100644 --- a/docs-site/launcher-script.md +++ b/docs-site/launcher-script.md @@ -48,6 +48,7 @@ App flags such as `--setup` and `--dev` are not launcher flags. Pass them throug | `subminer doctor` | Check the app, mpv, ffmpeg, yt-dlp, pickers, config, and mpv socket | | `subminer doctor --refresh-known-words` | Refresh the known-word cache from Anki | | `subminer settings` | Open the settings window | +| `subminer youtube` / `yt` | Open the [YouTube browser](/youtube-integration#browse-youtube-in-subminer). Videos you pick play in mpv | | `subminer generate-subs [video]` | Generate [Japanese subtitles](/subtitle-generation) with whisper.cpp | | `subminer jellyfin` / `jf` | [Jellyfin](/jellyfin-integration) actions: `setup`, `login`, `logout`, `play`, `discovery` | | `subminer dictionary ` / `dict` | Build a [character dictionary](/character-dictionary) for a file or directory | diff --git a/docs-site/mpv-plugin.md b/docs-site/mpv-plugin.md index 0f05818e..40732c9e 100644 --- a/docs-site/mpv-plugin.md +++ b/docs-site/mpv-plugin.md @@ -124,7 +124,7 @@ script-message subminer-start backend=hyprland socket=/custom/path texthooker=no `log-level` sets SubMiner's log verbosity. Do not use `--debug` for this; it turns on the app's dev mode. -The plugin also handles messages the SubMiner app sends it (`subminer-autoplay-ready`, `subminer-visible-overlay-shown`, `subminer-visible-overlay-hidden`, `subminer-managed-subtitles-loading`, `subminer-overlay-loading-ready`, `subminer-reload-session-bindings`). You do not need to send these yourself. The AniSkip messages are listed on the [AniSkip page](/aniskip-integration#triggering-from-mpv). +The plugin also handles messages the SubMiner app sends it (`subminer-autoplay-ready`, `subminer-autoplay-hold`, `subminer-visible-overlay-shown`, `subminer-visible-overlay-hidden`, `subminer-managed-subtitles-loading`, `subminer-overlay-loading-ready`, `subminer-reload-session-bindings`). You do not need to send these yourself. The AniSkip messages are listed on the [AniSkip page](/aniskip-integration#triggering-from-mpv). ## Auto-start behavior diff --git a/docs-site/public/config.example.jsonc b/docs-site/public/config.example.jsonc index 22640179..fe7f4f96 100644 --- a/docs-site/public/config.example.jsonc +++ b/docs-site/public/config.example.jsonc @@ -682,13 +682,14 @@ // ========================================== // YouTube Playback Settings // Defaults for managed subtitle language preferences and YouTube subtitle loading. - // Hot-reload: primarySubLanguages applies to the next YouTube subtitle load. + // Hot-reload: primarySubLanguages and subtitleSource apply to the next YouTube subtitle load. // ========================================== "youtube": { "primarySubLanguages": [ "ja", "jpn" ], // Comma-separated primary subtitle language priority for managed subtitle auto-selection. + "subtitleSource": "youtube", // Where primary YouTube subtitles come from. Whisper transcribes the audio locally using the subtitleGeneration settings. Values: youtube | whisper "mediaCache": { "mode": "direct", // How YouTube card audio/images are extracted. Values: direct | background "maxHeight": 720 // Maximum video height downloaded for the YouTube background media cache. Set to 0 for unlimited. diff --git a/docs-site/subtitle-generation.md b/docs-site/subtitle-generation.md index 0c2da357..3447d13f 100644 --- a/docs-site/subtitle-generation.md +++ b/docs-site/subtitle-generation.md @@ -10,11 +10,13 @@ When a video has no Japanese subtitles, SubMiner can transcribe its audio into a Downloaded models go to `models/whisper/` next to your SubMiner config file. A configured `modelPath` always wins over the modal's choice. +YouTube videos can use the same setup automatically in place of YouTube's captions. See [Generate subtitles with Whisper](/youtube-integration#generate-subtitles-with-whisper). + The modal's **Local tools** section lists anything missing. After you install a tool or change a path, click **Check again**. ## Generating from the overlay -1. Open a local video in mpv and select its Japanese audio track. +1. Open a local video in mpv and select its Japanese audio track, or play a YouTube video. 2. Press `Ctrl+Shift+G`. If the subtitle sidebar is empty, its **Generate Japanese subtitles** button opens the same modal. 3. Pick a model and download it if needed. 4. Optionally check **Focus on spoken dialogue** (see below). @@ -24,6 +26,8 @@ The modal shows progress. **Cancel** stops the job. Closing the modal lets the j SubMiner saves `