diff --git a/README.md b/README.md index 3437e354c..0979f9863 100644 --- a/README.md +++ b/README.md @@ -86,13 +86,17 @@ Browse sibling episode files and the active mpv queue in one overlay modal. Open Jellyfin Browse, launch, and cast media from your Jellyfin server with setup and discovery controls in the app tray + + Anime Browser + Search anime sources you supply as Aniyomi extension APKs and play an episode in mpv with the overlay attached (subminer anime); SubMiner ships no repositories and bundles no sources + Jimaku Search and download Japanese subtitles - Local Subtitle Generation - Generate Japanese subtitles from local audio in a standalone modal (Ctrl+Shift+G), the sidebar button, or launcher, with progress and optional managed model downloads. Requires whisper.cpp and FFmpeg. Optional Silero speech detection prioritizes dialogue in separately timed passages. Setup guide + Japanese Subtitle Generation + Generate Japanese subtitles from local files or finite anime streams in a standalone modal (Ctrl+Shift+G) or the sidebar button. The launcher supports local files. Transcription runs locally with whisper.cpp and FFmpeg, with progress and optional managed model downloads. Stream subtitles are saved in the application cache. Optional Silero speech detection prioritizes dialogue in separately timed passages. Setup guide TsukiHime @@ -182,6 +186,8 @@ See the [full requirements list](https://docs.subminer.moe/installation#_1-insta ```bash paru -S subminer-bin +# optional: the anime browser bridge, shared with Mangatan and updated by pacman +paru -S mangatan-extension-server ``` @@ -264,22 +270,40 @@ Full guides on configuration, Anki setup, Jellyfin, immersion tracking, and more SubMiner builds on the work of these open-source projects: -| Project | Role | -| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -| [ani-skip](https://github.com/synacktraa/ani-skip) | AniSkip API client for anime intro/outro skip timestamps | -| [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script) | Inspiration for the mining workflow | -| [asbplayer](https://github.com/killergerbah/asbplayer) | Inspiration for subtitle sidebar and logic for YouTube subtitle parsing | -| [Bee's Character Dictionary](https://github.com/bee-san/Japanese_Character_Name_Dictionary) | Character name recognition in subtitles | -| [Bun](https://github.com/oven-sh/bun) | Bundled runtime for the `subminer` command-line launcher | -| [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner) | Inspiration for Electron overlay with Yomitan integration | -| [jellyfin-mpv-shim](https://github.com/jellyfin/jellyfin-mpv-shim) | Jellyfin integration | -| [Jimaku.cc](https://jimaku.cc) | Japanese subtitle search and downloads | -| [Renji's Texthooker Page](https://github.com/Renji-XD/texthooker-ui) | Base for the WebSocket texthooker integration | -| [Yomitan](https://github.com/yomidevs/yomitan) | Dictionary engine powering all lookups and the morphological parser | -| [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab) | JLPT level tags for vocabulary | +| Project | Role | +| ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| [ani-skip](https://github.com/synacktraa/ani-skip) | AniSkip API client for anime intro/outro skip timestamps | +| [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script) | Inspiration for the mining workflow | +| [Aniyomi](https://github.com/aniyomiorg/aniyomi) | Anime extension API and data model the anime browser targets | +| [asbplayer](https://github.com/killergerbah/asbplayer) | Inspiration for subtitle sidebar and logic for YouTube subtitle parsing | +| [Bee's Character Dictionary](https://github.com/bee-san/Japanese_Character_Name_Dictionary) | Character name recognition in subtitles | +| [Bun](https://github.com/oven-sh/bun) | Bundled runtime for the `subminer` command-line launcher | +| [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner) | Inspiration for Electron overlay with Yomitan integration | +| [jellyfin-mpv-shim](https://github.com/jellyfin/jellyfin-mpv-shim) | Jellyfin integration | +| [Jimaku.cc](https://jimaku.cc) | Japanese subtitle search and downloads | +| [M-Extension-Server](https://github.com/1Selxo/M-Extension-Server) | Runs Aniyomi extension APKs off Android; the bridge the anime browser drives | +| [Mangatan](https://github.com/1Selxo/Mangatan) | Reference client for the bridge protocol the anime browser speaks | +| [Renji's Texthooker Page](https://github.com/Renji-XD/texthooker-ui) | Base for the WebSocket texthooker integration | +| [Yomitan](https://github.com/yomidevs/yomitan) | Dictionary engine powering all lookups and the morphological parser | +| [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab) | JLPT level tags for vocabulary | + +## Disclaimer + +SubMiner is a media player and language-learning tool. The project does not +host, supply, or link to anime, video streams, or subtitle libraries. The anime +browser runs Aniyomi extensions that you install yourself from repositories you +choose; SubMiner bundles none and recommends none. The SubMiner developers are +not affiliated with any content provider and have no control over what those +sources offer, whether they stay available, or the quality of what they serve. ## License -SubMiner is released under the [GNU General Public License v3.0](LICENSE). +[GNU General Public License v3.0](LICENSE) + +The anime browser drives [M-Extension-Server](https://github.com/1Selxo/M-Extension-Server), +downloaded from its upstream releases at runtime rather than +bundled or redistributed here; its bundles carry their own dependencies, including a JRE and +GPL-3.0 NewPipe Extractor. SubMiner includes none of them and talks to the bridge +over its own HTTP protocol. It ships no extensions or repositories by default. Release packages also bundle an unmodified copy of [Bun](https://github.com/oven-sh/bun), which is MIT licensed and statically links JavaScriptCore (LGPL 2.0) and TinyCC (LGPL 2.1). Its license texts and third-party notices ship inside the app under `resources/bun/licenses`, and each release publishes `bun-v1.3.5-source.tar.gz` with the corresponding source. See [Bundled Bun runtime](https://docs.subminer.moe/installation#bundled-bun-runtime). diff --git a/changes/anilist-season-resolution.md b/changes/anilist-season-resolution.md new file mode 100644 index 000000000..9d6727e61 --- /dev/null +++ b/changes/anilist-season-resolution.md @@ -0,0 +1,7 @@ +type: fixed +area: anilist + +- Resolving "season N" against AniList no longer lands one season short for franchises whose broadcast seasons are listed as several entries. AniList records the back half of a split cour ("… Season 2 Part 2", "… Cour 2", "第2クール") as its own sequel, and the resolver counted each as a season — so Mushoku Tensei season 3 resolved to the season 2 entry and Re:ZERO season 4 to season 3, which then drove the character dictionary and AniList progress updates to the wrong series. A sequel carrying a part or cour marker that matches the season it continues is now followed without advancing the season count, in both the relation walk and the air-order fallback. Titles are compared ignoring punctuation, and the marker is looked for in every title and synonym rather than just the display title. A wrong match cached before this fix is remembered in `character-dictionaries/anilist-resolution-cache.json` and needs removing (or overriding from the character dictionary picker) for the affected series. +- A special or OVA sitting in a franchise's sequel chain is walked through without counting as a season. Dr. STONE links STONE WARS to New World through a one-episode special, which made New World resolve as season 4 and every later season shift with it. +- An AniList rate limit or network failure while walking sequel relations is reported so the caller can retry, instead of falling through to the air-date fallback. That fallback is for a franchise with missing relation edges; running it after a failed lookup turned a transient 429 into a confidently wrong season. +- The shared title parser recognizes spelled-out episode labels (`Episode 4`, `第4話`) and a season named at the end of the title (`… Season 3`, `… 3rd Season`, `… S3`), which now fill the Season field instead of being searched for as part of the series name. The TsukiHime modal gained a Season field to match Jimaku, and seasons after the first are included in its search query so a later season's releases are actually found. diff --git a/changes/anime-browser-disclaimer-docs.md b/changes/anime-browser-disclaimer-docs.md new file mode 100644 index 000000000..8c849acdc --- /dev/null +++ b/changes/anime-browser-disclaimer-docs.md @@ -0,0 +1,4 @@ +type: docs +area: anime + +- Added a README disclaimer stating that SubMiner hosts no content, ships no anime sources or repositories, is not affiliated with any content provider, and that users are responsible for the content they access. The anime browser docs no longer describe SubMiner as a "host" of extensions. diff --git a/changes/anime-browser.md b/changes/anime-browser.md new file mode 100644 index 000000000..f0b41edf4 --- /dev/null +++ b/changes/anime-browser.md @@ -0,0 +1,36 @@ +type: added +area: anime + +- Added an anime browser window that searches Aniyomi extension sources, shows cover art and episode lists, and plays an episode in mpv so the overlay and mining tools attach as usual. +- Browser failures show a dismissible explanation with next steps and expandable technical details, including the bridge's error response instead of only its HTTP status. +- Added `subminer anime` and the `--anime` flag to open the browser, plus a "Browse Anime" tray entry. +- Anime extensions are read from `/anime-extensions`; drop Aniyomi `.apk` files there to add sources. +- Added a source settings tab so extensions that need configuration (server address, credentials, quality) can be set up from the browser; values persist per extension and source, and updated extension schemas replace stale saved field definitions without losing values. Older unscoped preferences are discarded once because their package ownership cannot be proven safely. +- Added an Extensions tab for adding repository URLs and installing, updating, or removing extensions in place; extensions that fail to load are listed with the reason. Repository requests time out instead of hanging, APK updates are staged before replacing the installed copy, and removing an extension refreshes the runtime even when saved credential cleanup fails while still reporting that failure. +- Browse, Extensions, and Source settings are tabs, so each one gets the full window instead of sharing it with the search results. +- Repository URLs only need to be an https URL to a `.json` index; the file name is not restricted to `index.min.json`. +- The source picker offers "All sources", which searches every installed source at once. Results stream in as each source answers, with per-source progress in the status bar. Results are tagged with their source, failures do not blank the grid, and **Load more** appends later pages without duplicating streamed entries. +- The Extensions tab opens with an Installed section listing every extension on disk with Remove, including ones added by hand or whose repository has since been removed. It compares APK and repository version codes, enables Update only for newer builds, marks current extensions as Up to date, and offers Update all when multiple updates are waiting. +- Added `anime.repos`, `anime.extensionsDir`, `anime.preferredQuality`, and `anime.defaultSource` config keys; **Set default** on an Installed row in the Extensions tab makes that source (or All sources) the one the browser opens on, and the current default carries a tag. SubMiner ships no extension repositories and performs no discovery. +- The settings app now groups Anime Browser config under an **Aniyomi** section in Integrations. A new `anime.autoOpenJimaku` option pauses newly loaded Anime Browser episodes, waits for mpv's video window to appear, closes the in-player browser, hides the standalone browser window so it is not pulled in front of mpv when the modals close, opens Jimaku with the episode details filled in, and resumes after a subtitle loads or the modal closes without overriding playback that was already paused. +- Anime playback targets Japanese audio: dub-labelled entries are skipped when the source offers an alternative, `alang` prefers Japanese, and the source's own audio and subtitle tracks are loaded into mpv (Japanese selected) instead of being discarded, so all of them can be switched from mpv's track menu. +- The primary subtitle slot stays reserved for Japanese: a source that only has, say, English subtitles gets them added with a normalized language tag (`English` → `en`) but not selected, so the regular `secondarySub` auto-load can route them to the secondary slot instead. +- HLS streams pass through a local strip proxy that removes fake image headers some hosts glue onto their video segments and gives disguised segment URLs (`.image`, `.jpg`, `.css`, and other rotating fake extensions) a media-safe local alias, so those streams play in mpv and support Anki audio and image extraction with current ffmpeg releases. Upstream responses use identity encoding so playlists remain readable for URL rewriting. +- The strip proxy retries a failed segment fetch once after a short pause and logs upstream error statuses; a host that errors on the very first fetches right after an episode resolves no longer kills the whole playback, disconnecting clients release their active upstream fetch instead of consuming the socket and bandwidth in the background, stalled response bodies retain the upstream inactivity timeout, and only origin-form requests can reach the configured local bridge. +- The strip proxy no longer forwards `Range` headers to the bridge: ffmpeg opens every HLS segment with `Range: bytes=0-`, the bridge answers some of those with 206, and a partial response bypassed the disguise strip, so whether an episode played depended on the bridge's cache state. +- A bridge that dies out from under the app (killed, crashed, or stopped mid-operation) no longer leaves the browser failing every request until an app restart: the exit is detected, surfaced in the status bar, and the bridge restarts on the next request. +- "Playing" is only reported once mpv actually configures a video output; slow streams remain loading while mpv opens them, and failed streams show mpv's error. +- The bridge bundle is downloaded from the newest upstream release that ships a bundle for the platform (skipping releases older than the oldest server SubMiner is known to work with), the same way Mangatan fetches it, so all four published bundles (macOS arm64 and x64, Linux x64, Windows x64) work without a maintainer hashing each one first. +- The Extensions tab's available list has a language chip row: pick one or more languages to narrow a repository index that otherwise lists every language it knows, or "All" to clear the filter. Rows name the language ("Japanese" instead of `ja`) and the Available heading counts what the filter leaves. +- A stream's subtitle tracks are downloaded to a temp directory and loaded into mpv as files rather than streamed from the source URL, so they can serve as the alass reference in Subsync the way Jellyfin subtitles do. The format is detected from the file's own content, a track that fails to download falls back to its URL so the episode still plays, and the directory is removed when the next episode starts or the app exits. +- An episode launched from the browser carries its series, season and episode number through the app instead of a single joined string: stats group by series (every stream previously landed in one entry named `m3u8`), rewatching reuses the existing entry, the Jimaku and TsukiHime modals prefill Title/Season/Episode from the source's own listing, AniList updates use those fields directly, and the mpv title reads `Series S03E04 - Episode Name`. +- Opening the browser shows a tray icon on every platform and — on macOS — puts the app in the Cmd+Tab switcher and Dock, so you can switch between it and mpv; the Dock icon is released again when the window closes during playback. Launching a video starts a regular SubMiner session, and in `subminer anime` standalone mode closing the window during playback no longer quits the app and kills the stream. +- The episode list has a filter box: type an episode number (`12`), a range (`12-18`), or part of an episode name to narrow a long list, with a `6 of 25` counter while it is applied. Escape inside the box clears the filter instead of leaving the detail page. +- Episodes already watched are dimmed and marked in the episode list, with a watched count in the header. The marks come from the stats history playback already writes (an episode is marked once a session passes the completion threshold), so they match the stats window and survive the stream URL changing between playbacks. They refresh when the browser window comes back to the front, and stay empty when immersion tracking is disabled. +- Right-clicking an episode opens a menu for marking it watched or unwatched by hand, plus "Mark this and N below watched/unwatched" for the episode and every episode listed under it. Sources list newest first, so a span covers the back catalogue, which is how a series watched elsewhere gets caught up. A filter never narrows what a span covers, and the status bar reports how many episodes were touched. +- Marking an episode that was never played creates its stats row, carrying the same series, season and episode fields playback would have recorded. Both stats library views join the lifetime tables, so a manual mark does not show up there as watch time nobody spent, and clearing a mark creates nothing. +- Episodes can be queued instead of replacing what is playing. Every episode row has **Play** and **Queue** buttons (clicking the row still plays now), the right-click menu offers the same two, and a queued episode shows its place in line ("next up", "#2 in queue") with a queue count and **Clear queue** in the episode header. The queue spans anime, resolves and appends each episode to mpv's real playlist as soon as it is queued while subtitle tracks cache in the background, so next/previous navigation works immediately and the next episode starts without a resolution pause when the current one ends. Queueing with nothing playing just plays. +- Added an in-player Anime Browser modal on `Ctrl+Alt+A`. The shortcut toggles it without losing its page or scroll position. It stays within the player bounds and shares the active episode, playback queue, source configuration, and watch history with the standalone browser, while each surface keeps independent search and navigation state. The modal validates its embedded page before changing overlay state, so a load setup failure leaves it closed rather than revealing a broken modal. The SubMiner logo identifies the Anime Browser in both the modal and standalone window. +- The bridge is reused from a package-manager install when one exists: on Arch the AUR `mangatan-extension-server` package (shared with Mangatan) is picked up from `/usr/share/mangatan/extension_server`, so nothing is downloaded and pacman keeps it current. `anime.bridgeDir` points SubMiner at a bundle anywhere else. `subminer-bin` lists the package as an optional dependency. +- Once the bridge is running, SubMiner checks GitHub for a newer release, including for AUR and custom installations. AUR update notices direct users to their AUR helper; custom installs get manual-update guidance. Only SubMiner-managed copies offer an **Update to vX** button. The new release is downloaded beside the running bridge, then the bridge restarts on it. The updater waits for a bridge still starting to stop and keeps the previous bundle until the replacement is active. The Extensions tab shows the bridge version, where it lives, and who updates it without describing an unchecked install as current. +- The detail page keeps its episode list when only the details call fails: the episodes render with a "Details unavailable" note and the error in the status bar, instead of an empty page. diff --git a/changes/anime-playback-loading-docs.md b/changes/anime-playback-loading-docs.md new file mode 100644 index 000000000..5503f009a --- /dev/null +++ b/changes/anime-playback-loading-docs.md @@ -0,0 +1,4 @@ +type: docs +area: anime + +- Document how the anime browser waits for slow streams to start without reporting a premature playback failure. diff --git a/changes/anime-stream-subtitle-generation-docs.md b/changes/anime-stream-subtitle-generation-docs.md new file mode 100644 index 000000000..3b678ecc4 --- /dev/null +++ b/changes/anime-stream-subtitle-generation-docs.md @@ -0,0 +1,5 @@ +type: docs +area: subtitles + +- Document anime stream generation, audio-only and lower-resolution extraction candidates, concurrent HLS downloads, temporary disk usage, audio cache reuse and limits, full-episode processing, supported audio tracks, and stream access limitations. +- Explain HLS source-check diagnostics and session-only audio cleanup on quit and after crashes. diff --git a/changes/anime-stream-subtitle-generation.md b/changes/anime-stream-subtitle-generation.md new file mode 100644 index 000000000..ef7122d0c --- /dev/null +++ b/changes/anime-stream-subtitle-generation.md @@ -0,0 +1,6 @@ +type: added +area: subtitles + +- Generate Japanese subtitles from finite HTTP/HTTPS anime episode streams in the existing generation modal. Reuse mpv's supported request headers and the anime proxy, transcribe downloaded audio locally, retain generated SRT files in the application cache, and load them only into the matching stream. Local-file generation and the launcher keep their existing behavior. +- Prepare plain HLS episodes with up to four concurrent segment downloads, preserving audio order and timing. Show download progress, cancel active requests together, and retain FFmpeg extraction for complex playlists. +- Reduce anime-stream extraction downloads by preferring separate Japanese audio or lower-resolution video when duration and sampled audio match playback. Sample HLS through short local segment windows, account for timestamp origins, retry empty original samples, and report why candidates are rejected. Selected external HTTP audio tracks are extracted directly with their playback delay preserved. Reuse verified completed audio across transcription retries and model changes within the current app session. Remove downloaded audio and working files on quit, recover abandoned sessions on startup, and preserve saved subtitles. diff --git a/changes/character-dictionary-without-subtitles-docs.md b/changes/character-dictionary-without-subtitles-docs.md new file mode 100644 index 000000000..08c903c8a --- /dev/null +++ b/changes/character-dictionary-without-subtitles-docs.md @@ -0,0 +1,4 @@ +type: docs +area: subtitles + +- Clarify that character dictionary sync works without a subtitle track and explain readiness and import timeouts. diff --git a/changes/character-dictionary-without-subtitles.md b/changes/character-dictionary-without-subtitles.md new file mode 100644 index 000000000..99bd0cc49 --- /dev/null +++ b/changes/character-dictionary-without-subtitles.md @@ -0,0 +1,4 @@ +type: fixed +area: subtitles + +- Let character dictionaries finish syncing when no Japanese subtitles are loaded. Wait for Yomitan extension readiness instead of the first processed subtitle, and report readiness failures instead of leaving the notification stuck on Checking. diff --git a/changes/fix-cleanup-and-tsukihime-callbacks.md b/changes/fix-cleanup-and-tsukihime-callbacks.md new file mode 100644 index 000000000..ba5c5af0b --- /dev/null +++ b/changes/fix-cleanup-and-tsukihime-callbacks.md @@ -0,0 +1,5 @@ +type: fixed +area: runtime + +- Shutdown finishes independent media and Discord cleanup and waits for sync shutdown even when Jellyfin cleanup fails, then reports the first cleanup error. +- Tsukihime ignores delayed media-info results and errors from a closed modal session. diff --git a/changes/linux-overlay-content-ready-fallback.md b/changes/linux-overlay-content-ready-fallback.md new file mode 100644 index 000000000..e63a2278e --- /dev/null +++ b/changes/linux-overlay-content-ready-fallback.md @@ -0,0 +1,4 @@ +type: fixed +area: overlay + +- Fixed the overlay never loading (stuck on the "Overlay loading" OSD spinner) when the Yomitan content-script reload raced overlay window creation at startup, most visible when playing from the anime browser on Linux/Wayland: a hidden window stops painting after that reload, so the ready-to-show signal that gates showing the overlay never fired. Content-ready now falls back to did-finish-load after a short grace period. diff --git a/changes/macos-window-activation.md b/changes/macos-window-activation.md new file mode 100644 index 000000000..9cadd27da --- /dev/null +++ b/changes/macos-window-activation.md @@ -0,0 +1,4 @@ +type: fixed +area: macos + +- `subminer anime` (and `--settings` / `--sync`) now bring their window to the front on macOS. `show()`/`focus()` only reorder windows inside the app that is already active, so the window opened behind the terminal that launched it; SubMiner now activates itself when opening one. The anime browser also restores its Dock icon before showing rather than after, because the overlay's fullscreen transform leaves the app as an accessory process that macOS refuses to bring forward at all. diff --git a/changes/subsync-file-url-tracks.md b/changes/subsync-file-url-tracks.md new file mode 100644 index 000000000..4a603aaf2 --- /dev/null +++ b/changes/subsync-file-url-tracks.md @@ -0,0 +1,4 @@ +type: fixed +area: subsync + +- Subsync no longer fails with `Protocol "file:" not supported` on a subtitle that was dropped onto mpv. mpv reports such a track as a percent-encoded `file://` URL, which subsync read as a stream and tried to fetch over HTTP; the URL is now decoded back to its path, so both the retimed target and an alass reference work. A `file://` video path is treated as local too, which restores the video reference and ffsubsync for a dropped file. diff --git a/changes/subsync-stream-tracks.md b/changes/subsync-stream-tracks.md new file mode 100644 index 000000000..407d486f5 --- /dev/null +++ b/changes/subsync-stream-tracks.md @@ -0,0 +1,7 @@ +type: fixed +area: subsync + +- Subsync now works when mpv loaded a subtitle track from a URL, which is how Aniyomi extension streams and Jellyfin add theirs. The track is downloaded to a temporary file first — reusing mpv's own request headers, so authenticated and referer-gated hosts stay reachable — instead of being rejected with "Subtitle file not found: https://…". This applies to both the sync target and the alass reference, so a Jimaku or TsukiHime download can be retimed against a stream's own subtitles. Internal tracks of a stream also pass mpv's headers through to `ffmpeg`. +- Alass now works on tracks that arrive as WebVTT, which is what Aniyomi extension streams serve. Alass picks its parser from the file extension and has no WebVTT support, so it treated a `.vtt` reference as a video file and failed with "no audio stream in file". Both the target and the reference are rewritten as SRT for alass, keeping the cue text as-is, and the originals are left untouched. +- Leaving `subsync.alass_path`, `ffsubsync_path`, or `ffmpeg_path` empty now actually auto-discovers the binary, as the config help has always claimed. Previously it fell back to a hard-coded `/usr/bin/`, which does not exist on macOS and broke subsync for every default-config install there. Discovery searches `PATH` plus the usual install prefixes (a GUI launch inherits a minimal `PATH`) and accepts `alass-cli` as well as `alass`. An explicitly configured path is still used verbatim and never silently substituted. +- Subsync failures are written to the application log. Previously the only trace was an OSD toast that vanished after a few seconds, leaving nothing to diagnose from. diff --git a/config.example.jsonc b/config.example.jsonc index 8e9a3cd60..6afde16c2 100644 --- a/config.example.jsonc +++ b/config.example.jsonc @@ -359,6 +359,12 @@ "__playlist-browser-open" ] // Command setting. }, + { + "key": "Ctrl+Alt+KeyA", // Key setting. + "command": [ + "__anime-browser-open" + ] // Command setting. + }, { "key": "Ctrl+Shift+KeyH", // Key setting. "command": [ @@ -636,6 +642,21 @@ } // Lapis kiku setting. }, // Automatic Anki updates and media generation options. + // ========================================== + // Anime Browser + // Anime browser sources. SubMiner ships no extension repositories and bundles no sources; + // add a repository index URL here (or drop .apk files in the extensions directory) to have any. + // Hot-reload: autoOpenJimaku applies to the next episode; other anime changes apply the next time the anime browser opens. + // ========================================== + "anime": { + "autoOpenJimaku": false, // Pause Anime Browser playback and open Jimaku when an episode loads. Playback resumes after a subtitle loads or the modal closes only when auto-open initiated the pause. Values: true | false + "extensionsDir": "", // Directory holding Aniyomi extension .apk files. Empty uses /anime-extensions. + "repos": [], // Extension repository index URLs (any https .json index, e.g. https://.../index.min.json). Empty by default; SubMiner ships no repositories. + "preferredQuality": "", // Preferred stream quality label, matched as a substring (for example: 1080). Empty uses the source order. + "defaultSource": "", // Source the Anime Browser selects when it opens: a source id (:) or "all" for every installed source. Empty selects the first installed source. "Set default" in the Extensions tab writes this value. + "bridgeDir": "" // Directory holding an M-Extension-Server bundle (java runtime plus server jar) to run instead of the copy SubMiner downloads. Empty checks the package-manager install (Arch: mangatan-extension-server), then /anime-bridge. + }, // Anime browser sources. SubMiner ships no extension repositories and bundles no sources; + // ========================================== // Jimaku // Jimaku API configuration and defaults. diff --git a/docs-site/.vitepress/config.ts b/docs-site/.vitepress/config.ts index acace595e..8a4dcad1a 100644 --- a/docs-site/.vitepress/config.ts +++ b/docs-site/.vitepress/config.ts @@ -368,6 +368,7 @@ const sidebar: DefaultTheme.SidebarItem[] = [ { text: 'Anki', link: '/anki-integration' }, { text: 'Jellyfin', link: '/jellyfin-integration' }, { text: 'YouTube', link: '/youtube-integration' }, + { text: 'Anime Browser', link: '/anime-browser' }, { text: 'Jimaku', link: '/jimaku-integration' }, { text: 'Subtitle Generation', link: '/subtitle-generation' }, { text: 'TsukiHime', link: '/tsukihime-integration' }, diff --git a/docs-site/anime-browser.md b/docs-site/anime-browser.md new file mode 100644 index 000000000..c9af5ff9b --- /dev/null +++ b/docs-site/anime-browser.md @@ -0,0 +1,419 @@ +# Anime Browser + +Search anime sources, pick an episode, and play it in mpv with SubMiner's overlay +and mining tools attached — the same way a local file or a Jellyfin stream works. + +Open it with `subminer anime`, with `SubMiner.AppImage --anime`, or from +**Browse Anime** in the tray menu. The window stays open while you watch, so you +can queue the next episode without reopening it. + +During playback, `Ctrl+Alt+A` toggles the same browser as a modal inside the mpv +player bounds. It uses a dedicated modal surface, so it stays above fullscreen +playback and closes like the other in-player tools. Toggling it off keeps its +current page and scroll position ready for the next toggle. The standalone +window and the modal keep their own search, selected source, tab, and scroll +state, so using one does not replace or cancel what you were doing in the other. + +Both surfaces use the same playback queue, source configuration, and stats +history. Queue changes and the currently playing episode appear in both +immediately, and watched marks come from the same history that playback and the +stats window update. Closing and reopening the modal therefore picks up progress +made from either browser surface. + +While the window is open, SubMiner shows a tray icon and — on macOS — appears +in the Cmd+Tab switcher and the Dock (macOS ties the two together), so you can +flip between the browser and mpv. SubMiner normally hides itself from the Dock +because the subtitle overlay needs that to float above fullscreen video; it +hides again when the window closes during playback. + +Launching an episode starts a full SubMiner playback session, the same as +playing a local file: the overlay and mining tools attach, and the tray icon +stays available. In standalone `subminer anime` mode, closing the window while +a video is playing leaves playback running — reopen the browser from the tray +(**Browse Anime**). The app only exits with the window when nothing is playing. + +## How it works + +SubMiner does not implement any anime source itself. It runs **Aniyomi extension +APKs** through a bundled JVM sidecar ([M-Extension-Server][mes]), asks the +selected extension to resolve an episode, and hands the resulting URL to mpv. + +``` +extension APK → bridge (JVM) → { url, headers } → mpv → SubMiner overlay +``` + +Because the extension resolves the stream, whichever sources you install decide +what is available. SubMiner runs the installed extensions locally and nothing +more. + +## Installing extensions + +**SubMiner ships no extension repositories and bundles no sources.** There is no +default repository, no suggested list, and no discovery. Until you add one, the +browser has nothing to search — that is deliberate. Every repository and +extension comes from you, and SubMiner is not affiliated with any of the sites +they connect to. + +There are two ways to add extensions. + +### From a repository + +The window has three tabs — **Browse**, **Extensions**, and **Source settings** — +and each one fills the window, so a long extension list is not squeezed in above +the search results. + +Open the **Extensions** tab, paste a repository index URL, and choose +**Add repository**. The URL must be `https` and point at a `.json` index file — +`index.min.json` is the common Aniyomi name, but repositories are free to publish +under another one (for example `video.min.json`). Anything else is rejected +immediately rather than failing later. Everything before the file name is treated +as the repository root, so `.apk` and icon URLs are resolved relative to it. + +Extensions your repositories offer but you do not have appear under +**Available**, each with **Install**. Repositories are stored in config under +`anime.repos`, so you can also manage them there and keep them in a dotfile. + +Every row carries the extension's icon, as the repository publishes it, so a +site is recognisable before you read the name. A repository row shows its host's +favicon instead. Icons are the only part of a row that is fetched from the +network, and a row whose icon is missing falls back to the first letter of its +name rather than an empty box. + +A repository index lists every language it knows about, which is far more than +any one person reads, so the **Available** list has a language chip row above +it. Pick one or more languages to narrow it, or **All** to clear the filter; +picking a language replaces **All** rather than sitting beside it. Rows name the +language in full ("Japanese" rather than `ja`), extensions whose sources span +languages are grouped under **Multi-language**, and the Available heading counts +how many of the offered extensions the filter leaves. + +### Managing what is installed + +The Extensions tab opens with an **Installed** section listing everything in the +extensions directory, with the sources each one provides and a **Remove** +button. It is built from the directory rather than from a repository, so an +extension you dropped in by hand — or one whose repository you have since +removed — is still listed and still removable. An installed extension borrows +its icon from the catalogue, so one no repository carries shows its monogram. + +SubMiner compares the APK's Android version code with the newest build in the +configured repositories. **Update** is clickable only when the repository has a +newer build. A current extension says **Up to date**. If an unusual APK has no +readable version code, its disabled button says **Version unknown**. **Update +all** installs every newer build in one pass and disables itself when there is +nothing to update. + +### From a file + +Drop Aniyomi `.apk` files into the extensions directory, shown at the top of the +Extensions tab. It defaults to `/anime-extensions` — on macOS, +`~/Library/Application Support/SubMiner/anime-extensions` — and can be moved with +`anime.extensionsDir`. + +A single APK may provide several sources; each appears separately in the +**Source** picker. Extensions that fail to load are listed in the Installed +section with the reason, so a broken APK is visible rather than silently +missing. + +An extension that fails to load is skipped rather than blocking the others, so +one bad APK will not hide the rest. + +### Default source + +The browser opens on the first installed source. To open on a different one, +press **Set default** on its row in the Installed list; the row then carries a +**default** tag, and only one source can carry it. The choice is written to +`anime.defaultSource` and applies the next time a browser window or the +in-player modal opens. With more than one source installed, an **All sources** +row at the top of the list can be the default too. A default that is no longer +installed falls back to the first source. + +## Searching every source at once + +With more than one source installed, the **Source** picker gains an +**All sources** entry. Searching with it selected runs the query against every +installed source at once, and each source's results appear the moment that +source answers — a fast source is on screen while a slow one is still +resolving. The status bar counts sources as they finish +(`Searching… 3/5 sources · 42 results`). + +Each cover is labelled with the source it came from, and opening one always +queries that source, whatever the picker says afterwards. + +A source that errors is named in the status bar and the rest still show their +results; one extension that needs a login cannot blank the grid. If every +source fails, the first error is shown in full. + +Typing a new search while one is still running simply starts over: results +from the superseded search are discarded, even if its sources answer late. + +When a source reports another page, **Load more** appears below the covers. +It appends the next page without duplicating entries that already arrived in +the live result stream. A failed next-page request remains available to retry. + +Source settings belong to a single extension, so the **Source settings** tab +asks you to pick one while **All sources** is selected. + +## Finding an episode, and what you have watched + +An episode list can run to hundreds of entries, so the episode header carries a +filter box: + +- A number, `12`, keeps that episode. Sources that report no numbers at all + are still searched by name, so `12` also matches `Episode 12` in a title. +- A range, `12-18`, keeps the episodes between the two, in either order + (`18-12` reads the same). Episodes the source gave no number are left out of + a range. +- Anything else is a case-insensitive substring of the episode name, so `beach` + finds `OVA: Beach Special`. + +The counter next to **Episodes** reads `6 of 25` while a filter is applied. +Pressing Escape in the filter box clears it; pressing it anywhere else goes back +to the results grid. + +Episodes you have already watched are dimmed and marked `✓ watched`, with a +count in the header. This is not a separate list the browser keeps: it reads the +same stats history the rest of SubMiner writes to, where an episode is marked +watched once a session runs past the completion threshold. Streams are recorded +under a stable per-episode identity, so the mark survives the stream URL +changing between playbacks, and it is the same mark the stats window and +`--mark-watched` use. + +Because playback marks an episode partway through the session, the marks +refresh when the browser window comes back to the front: finish an episode in +mpv, switch back, and it is marked. With +[immersion tracking](configuration.md) disabled there is no history to read, so +no episode is marked. + +### Marking by hand, and catching up + +Right-click an episode for: + +- **Mark watched** / **Mark unwatched**: the single episode, whichever way it + is not already. +- **Mark this and N below watched** / **... unwatched**: that episode and every + episode listed below it. Sources list newest first, so "below" is the back + catalogue: right-click the last episode you saw and mark everything down to + the start, which is how you catch up a series you watched somewhere else. + +A filter narrows what you are looking at, not what you mark: a span always +covers the full episode list, and the status bar says how many episodes it +touched. The oldest episode has nothing below it, so it only offers the single +entry. Escape closes the menu. + +Marking an episode you have never played creates its stats row so the mark has +somewhere to live. That row carries the same series, season and episode fields +playback would have recorded, and both stats library views join the lifetime +tables, so a manually marked episode does not appear there as watch time you +never spent. Clearing a mark never creates anything. + +Marks are written to the stats history, so with immersion tracking disabled +there is nowhere to write them and the status bar says so. + +## Settings + +| Key | Purpose | +| ------------------------ | ------------------------------------------------------------------------ | +| `anime.autoOpenJimaku` | Pause new episodes and open Jimaku for Japanese subtitles. | +| `anime.repos` | Repository index URLs. Empty by default. | +| `anime.extensionsDir` | Where APKs are read from. Empty uses `/anime-extensions`. | +| `anime.preferredQuality` | Preferred stream label, matched as a substring (for example `1080`). | +| `anime.defaultSource` | Source the browser opens on: a source id or `all`. Empty uses the first. | +| `anime.bridgeDir` | A bridge bundle to run instead of the downloaded one. Empty by default. | + +Enable `anime.autoOpenJimaku` to hand each newly loaded Anime Browser episode +to Jimaku. SubMiner pauses playback on its first frame, waits for mpv's video +window to appear, closes the in-player browser if it is open, hides the +standalone browser window, and opens Jimaku with the source title, season, and +episode already filled in. Playback resumes after the selected subtitle loads. +Closing Jimaku also releases the automatic pause, while playback that was +already paused stays paused. A stream that never shows a window resumes without +opening Jimaku. Reopen the browser window with `subminer anime`, the tray +entry, or the shortcut to keep browsing. + +## Source settings + +Most extensions need configuration before they return anything — a server +address and credentials, a preferred quality, a language filter. Open the +**Source settings** tab to edit them. Changes save as you make them and +persist across restarts in `/anime-source-preferences.json`. + +Each save is handed back to the extension, so it can react: the Jellyfin source +logs in when the address and password land, then fills in its media-library +picker. Password-like fields are masked. Because that file can hold +credentials, it is written with owner-only permissions. Values are scoped to +the exact extension package and source, so two extensions that reuse the same +internal source ID cannot read each other's settings. Preferences saved by an +older build without package ownership are discarded; re-enter those source +settings once after upgrading. + +## The bridge + +SubMiner looks for the server in three places, in order, and runs the first +one it finds: + +1. `anime.bridgeDir`, if set. It must hold a Java runtime and the + `MExtensionServer-*.jar`, laid out as upstream ships them. A directory that + holds neither is an error rather than a silent fallback. +2. A package-manager install. On Arch that is the AUR + [`mangatan-extension-server`](https://aur.archlinux.org/packages/mangatan-extension-server) + package, which Mangatan also uses, at `/usr/share/mangatan/extension_server`. + Installing it means no download, and pacman keeps the bridge current. The + `subminer-bin` package lists it as an optional dependency. +3. SubMiner's own copy in `/anime-bridge`. The first launch downloads + the newest upstream release that ships a bundle for your platform (~130 MB, + containing the server and a matching Java runtime, so no system JDK is + required), the same way Mangatan does. Releases older than the oldest server + SubMiner is known to work with are skipped. Progress appears in the banner + at the top of the window. + +The **Bridge** note at the bottom of the Extensions tab says which one is in +use, its version, and who updates it. + +### Updating the bridge + +Only the copy SubMiner downloaded is ever updated by SubMiner. A package-manager +install or an `anime.bridgeDir` belongs to whoever put it there. + +Once the bridge is running, SubMiner asks GitHub for the newest release and +compares it with the installed version, including package-manager installs and +custom bridge directories. When upstream has published a newer release, the +banner reads "Extension bridge v… is installed; v… is available". + +For an AUR bridge, the banner directs you to update `mangatan-extension-server` +through your AUR helper, for example `paru -S mangatan-extension-server`, then +restart SubMiner. Custom installations get a reminder to use their package +manager or original installation method. These notices have no in-app update +button. The check uses upstream releases, so AUR packaging may lag behind the +notice. External installations with an unreadable version do not show a notice. + +For SubMiner's own copy, the banner includes an **Update to v…** button. +Clicking it downloads the new release beside the +running bridge, so a failed download changes nothing, then stops the bridge, +swaps the directories, and starts it again. The restart takes a few seconds +and kills the stream of an episode that is playing, the same as when the +bridge exits for any other reason; the queue and browser state survive. The +check is one unauthenticated GitHub API call per bridge start; if it fails +(offline, rate limited) it is logged and no update is offered until the next +start. + +Upstream publishes no checksums, so the download is trusted the way Mangatan +and the AUR package trust it: TLS to GitHub and the maintainer's account. That +is the same trust running the server implies in the first place. + +The bridge stays running while the window is open. Resolved video URLs point at +its own loopback proxy so the extension's cookies and headers apply, which means +those URLs stop working once it exits — the window keeps it alive for the whole +session. + +If the bridge dies anyway (killed by hand, crashed, or stopped mid-operation), +the exit is detected and named in the status bar, and the next request starts a +new one. Playback already in flight still ends when its stream URL dies, but the +browser recovers without an app restart. + +Two known limits: + +- There is no Android WebView, so extensions that need one (typically for + Cloudflare challenges) will fail with an error from the source. +- Bundles are published for macOS (arm64, x64), Linux (x64), and Windows (x64). + Other platforms are unsupported outright. + +## Playback + +Selecting an episode resolves the best available stream, applies the source's +required headers as mpv `http-header-fields`, and loads it. The headers are +readable back off mpv, so Anki card audio and screenshots fetch correctly too. + +HLS streams are routed through a small local proxy before mpv sees them. Some +hosts disguise their video segments by prepending a fake image header (a real +1x1 PNG) so scrapers back off; Aniyomi's own player strips this, but ffmpeg +probes the segment as a picture and playback dies with "no audio or video data +played". The proxy scans each segment for the first genuine MPEG-TS packet run +and drops whatever junk sits in front of it. Segments that are not TS (fMP4, +subtitles, encryption keys) pass through untouched, and direct-file streams +skip the proxy entirely. Segment URLs disguised behind fake extensions +(`.image`, `.jpg`, `.css`, and friends) are exposed locally with a `.ts` +suffix so current ffmpeg releases accept them when Anki extracts audio, +screenshots, or animated images from the playing stream. + +"Playing" in the status bar means playing: after handing mpv the stream, +SubMiner waits until mpv actually configures a video output before reporting +success. If mpv gives up instead — a dead host, an undecodable stream — the +browser shows mpv's error rather than pretending playback started (a failed +load leaves no mpv window, because the player idles windowless). +Slow streams stay in the loading state while mpv is still opening them, even +after the initial playback confirmation deadline. Loading ends when video +appears, mpv reports an error or returns to idle, or another episode replaces +the request. + +Failed requests appear in a dismissible panel at the bottom of the browser. +It explains the failed action and suggests a next step. Expand **Technical +details** to read the bridge or mpv error when reporting a problem. Missing +bridge APIs and incomplete extension data have distinct explanations; an +update may be needed, but a compatible release may not yet be available. + +Choosing **Queue** resolves the episode and appends the playable stream to mpv's +own playlist immediately, while its subtitle tracks cache in the background. +That makes mpv's next command available at once and lets the next episode begin +automatically when the current one ends, without waiting for another source +request. Resolved stream URLs can be short-lived, so a very long queue can still +outlive what its source issued; dequeue and queue that episode again to refresh +it. + +### Japanese audio, and switching tracks + +Sources often return a dub and the original audio as two separate entries — or +as two audio tracks of one stream — and the dub is frequently listed first. +SubMiner always aims at the Japanese audio: + +- Entries labelled as a dub are skipped as long as another entry exists. This + outranks `anime.preferredQuality`: a 1080p dub is the wrong file, not a better + one. If every entry is a dub, it still plays. +- mpv's `alang` is set to `ja,jpn,jp,japanese` before the file loads, so a + stream carrying several audio tracks starts on the Japanese one. With no + Japanese track, mpv falls back to the first one as usual. +- Any audio or subtitle tracks the extension supplies separately are added to + mpv with `audio-add` / `sub-add`, tagged with their language, and the + Japanese one is selected. + +The primary subtitle slot is reserved for Japanese — it is what the overlay +mines. A source that only carries, say, English subtitles does not get them +promoted to primary; instead the track is added with a normalized language tag +(`English` → `en`), and the regular [dual-subtitle settings](configuration.md) +apply: with `secondarySub.autoLoadSecondarySub` enabled and the language listed +in `secondarySub.secondarySubLanguages`, it is picked up as the secondary +subtitle, exactly as it would be for a local file. + +Every track is added, including the ones that are not selected, so all of them +appear in mpv's track menu and can be switched by hand while watching +(`#` cycles audio, `j` cycles subtitles by default). + +The extension hands over subtitle tracks as URLs, but SubMiner downloads each +one to a temporary directory and gives mpv the local file. mpv is happy either +way; [Subsync](/troubleshooting#subtitle-sync-subsync) is not, because alass +needs a file on disk to use as the timing reference. A track that fails to +download falls back to its URL so the episode still plays, the format is +detected from the file's own content rather than its URL, and the directory is +removed when the next episode starts or the app exits. + +### Generating Japanese subtitles + +While an episode is playing, press **Ctrl+Shift+G** to [generate Japanese subtitles](/subtitle-generation#generating-for-anime-streams) from its audio. SubMiner fetches the complete episode audio, transcribes it locally with whisper.cpp, and loads the resulting SRT if the same stream is still playing. Generated subtitles stay in the application cache. This requires a finite HTTP/HTTPS stream with an internal audio track; live streams and separately attached audio tracks are not supported. + +### Series, season, and episode + +The episode's identity travels with it instead of being guessed back out of the +stream URL, which carries nothing but a proxy path and a file extension. The +title and episode label from the source's own listing are split into series, +season, and episode number once, at launch, and everything downstream reads +those fields: + +- mpv's title reads `Series S03E04 - Episode Name`. +- Stats group by series, and rewatching an episode reuses its entry instead of + creating a new one. +- The [Jimaku](/jimaku-integration) and [TsukiHime](/tsukihime-integration) + modals open with Title, Season, and Episode already filled in, so a subtitle + search is one keypress rather than a retype. +- [AniList](/anilist-integration) progress updates use those fields directly. + +[mes]: https://github.com/1Selxo/M-Extension-Server diff --git a/docs-site/architecture.md b/docs-site/architecture.md index 0aa87cc33..3567763d0 100644 --- a/docs-site/architecture.md +++ b/docs-site/architecture.md @@ -73,8 +73,8 @@ src/ shared/ipc/ # Cross-process IPC channel constants + payload validators renderer/ # Overlay renderer (modularized UI/runtime) handlers/ # Keyboard/mouse/gamepad interaction modules - modals/ # Modal flows (Jimaku, Kiku, subsync, runtime options, session help, - # changelog, character dictionary, playlist browser, subtitle + modals/ # Modal flows (Anime Browser, Jimaku, Kiku, subsync, runtime options, + # session help, changelog, character dictionary, playlist browser, subtitle # sidebar, YouTube track picker, controller config/debug/select) positioning/ # Subtitle position controller (drag-to-reposition) settings/ # Settings window UI (model, controls, markup) diff --git a/docs-site/character-dictionary.md b/docs-site/character-dictionary.md index 55b01bf02..415552a86 100644 --- a/docs-site/character-dictionary.md +++ b/docs-site/character-dictionary.md @@ -35,6 +35,8 @@ Character dictionary sync is disabled by default. To turn it on: ::: tip The first sync for a media title takes a few seconds while character data and portraits are fetched from AniList. Subsequent launches reuse the cached media match and snapshot without a fresh AniList lookup. + +Dictionary sync does not require a loaded Japanese subtitle track. Once Yomitan is ready, SubMiner can reuse an installed character dictionary even before the first subtitle is processed. If Yomitan readiness times out, the status reports a failure instead of staying on **Checking**. ::: ::: info @@ -168,7 +170,7 @@ These phases are emitted through the configured notification surface. Some phase 2. **generating** - No cache hit: fetch characters from AniList GraphQL, download portraits (250ms throttle between image requests), save snapshot JSON. 3. MRU update (no notification) - add the media ID to the most-recently-used list and evict old entries beyond `maxLoaded`. 4. **building** - Merge active snapshots into a single Yomitan ZIP. A SHA-1 revision hash is computed from the media set - if it matches the previously imported revision, the import is skipped. -5. **importing** - Push the ZIP into Yomitan. Waits for Yomitan mutation readiness (7-second timeout per operation). +5. **importing** - Push the ZIP into Yomitan when the installed revision differs. Dictionary checks and settings updates wait for the extension to be ready, independently of subtitle processing. Readiness and quick operations have bounded timeouts; imports have a separate budget that grows with the ZIP size. 6. **ready** - Dictionary is live. Character names will match on the next subtitle line. **State tracking** is persisted in `character-dictionaries/auto-sync-state.json`. AniList media matches are cached separately in `character-dictionaries/anilist-resolution-cache.json` so snapshot hits do not need another AniList search. diff --git a/docs-site/configuration.md b/docs-site/configuration.md index 89d2ab40f..52ce77f9c 100644 --- a/docs-site/configuration.md +++ b/docs-site/configuration.md @@ -154,6 +154,7 @@ The configuration file includes several main sections: **External Integrations** +- [**Anime Browser**](#anime-browser) - Extension repositories and stream preferences for the anime browser - [**Jimaku**](#jimaku) - Jimaku API configuration and defaults - [**TsukiHime**](#tsukihime) - Multi-language subtitle search and download - [**TMDB**](#tmdb) - Posters and synopses for live-action dramas and movies in the stats Library @@ -591,6 +592,7 @@ See `config.example.jsonc` for detailed configuration options and more examples. | `KeyJ` | `["cycle", "sid"]` | Cycle primary subtitle track | | `Shift+KeyJ` | `["cycle", "secondary-sid"]` | Cycle secondary subtitle track | | `Ctrl+Alt+KeyP` | `["__playlist-browser-open"]` | Open playlist browser | +| `Ctrl+Alt+KeyA` | `["__anime-browser-open"]` | Toggle Anime Browser in the player | | `Ctrl+Alt+KeyC` | `["__youtube-picker-open"]` | Open the manual YouTube subtitle picker | | `ArrowRight` | `["seek", 5]` | Seek forward 5 seconds | | `ArrowLeft` | `["seek", -5]` | Seek backward 5 seconds | @@ -632,7 +634,7 @@ See `config.example.jsonc` for detailed configuration options and more examples. { "key": "Space", "command": null } ``` -**Special commands:** Commands prefixed with `__` are handled internally by the overlay rather than sent to mpv. `__playlist-browser-open` opens the split-pane playlist browser for the current file's parent directory and the live mpv queue. `__replay-subtitle` replays the current subtitle and pauses at its end. `__play-next-subtitle` seeks to the next subtitle, plays it, and pauses at its end. `__runtime-options-open` opens the runtime options palette. `__runtime-option-cycle:[:next|prev]` cycles a runtime option value. +**Special commands:** Commands prefixed with `__` are handled internally by the overlay rather than sent to mpv. `__playlist-browser-open` opens the split-pane playlist browser for the current file's parent directory and the live mpv queue. `__anime-browser-open` opens the Anime Browser inside the player bounds. `__replay-subtitle` replays the current subtitle and pauses at its end. `__play-next-subtitle` seeks to the next subtitle, plays it, and pauses at its end. `__runtime-options-open` opens the runtime options palette. `__runtime-option-cycle:[:next|prev]` cycles a runtime option value. **Supported commands:** Any valid mpv JSON IPC command array (`["cycle", "pause"]`, `["seek", 5]`, `["script-binding", "..."]`, etc.) @@ -1119,6 +1121,35 @@ When the manual merge popup opens, SubMiner pauses playback and closes any open ## External integrations +### Anime Browser + +Sources for the [anime browser](/anime-browser). SubMiner ships no extension repositories and bundles no sources, so these are empty until you add one: + +```json +{ + "anime": { + "autoOpenJimaku": false, + "extensionsDir": "", + "repos": [], + "preferredQuality": "", + "defaultSource": "" + } +} +``` + +| Option | Type | Default | Description | +| ------------------------ | ---------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `anime.autoOpenJimaku` | `boolean` | `false` | Pause Anime Browser playback and open Jimaku when an episode loads. Playback resumes after a subtitle loads or the modal closes. Playback that was already paused stays paused. | +| `anime.extensionsDir` | `string` | `""` | Directory holding Aniyomi extension `.apk` files. Empty uses `/anime-extensions`. | +| `anime.repos` | `string[]` | `[]` | Extension repository index URLs. Any `https` URL ending in `.json` works; `index.min.json` is only the common name. | +| `anime.preferredQuality` | `string` | `""` | Preferred stream quality label, matched as a substring (for example `1080`). Empty keeps the source's own order. A Japanese-audio entry always outranks a higher-quality dub. | +| `anime.defaultSource` | `string` | `""` | Source the Anime Browser selects when it opens: a source id (`:`) or `all` for every installed source. Empty selects the first installed source. **Set default** in the Extensions tab writes this value. | +| `anime.bridgeDir` | `string` | `""` | Directory holding an M-Extension-Server bundle (Java runtime plus server jar) to run instead of the downloaded copy. Empty checks the package-manager install first, then `/anime-bridge`. See [the bridge](anime-browser.md#the-bridge). | + +Repositories added from the browser's Extensions tab are written back to `anime.repos`, so the list can also be kept in a dotfile. Changes apply the next time the anime browser opens. + +Per-source settings (server addresses, credentials, per-extension quality or language options) are not part of `config.jsonc`. They belong to the extension, are edited in the browser's **Source settings** tab, and persist in `/anime-source-preferences.json` with owner-only permissions. + ### Jimaku Configure Jimaku API access and defaults: @@ -1213,11 +1244,17 @@ Sync a subtitle track from the overlay picker using `alass` or `ffsubsync`. The | Option | Values | Description | | ---------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------- | -| `alass_path` | string path | Path to `alass` executable. Empty falls back to `/usr/bin/alass`. `alass` must be installed separately. | -| `ffsubsync_path` | string path | Path to `ffsubsync` executable. Empty falls back to `/usr/bin/ffsubsync`. `ffsubsync` must be installed separately. | -| `ffmpeg_path` | string path | Path to `ffmpeg` (used for internal subtitle extraction). Empty or `null` falls back to `/usr/bin/ffmpeg`. | +| `alass_path` | string path | Path to `alass` executable. Empty auto-discovers `alass` or `alass-cli`. `alass` must be installed separately. | +| `ffsubsync_path` | string path | Path to `ffsubsync` executable. Empty auto-discovers `ffsubsync`. `ffsubsync` must be installed separately. | +| `ffmpeg_path` | string path | Path to `ffmpeg` (used for internal subtitle extraction). Empty or `null` auto-discovers `ffmpeg`. | | `replace` | `true`, `false` | When `true` (default), overwrite the active subtitle file on successful sync. When `false`, write `_retimed.`. | +Auto-discovery searches `PATH`, then the usual install prefixes (`/opt/homebrew/bin`, `/usr/local/bin`, `/opt/local/bin`, `/usr/bin`, `/bin`) — a GUI launch inherits a minimal `PATH` that often omits the first two. Set the option explicitly if your binary lives elsewhere. + +Subtitle tracks that mpv loaded from a URL (Aniyomi extension streams, Jellyfin) are downloaded to a temporary file first, reusing mpv's own request headers, so they can be used as either the sync target or the alass reference. + +Stats dashboard sentence mining also uses `alass_path` when available to align a local English sidecar against the local Japanese sidecar before filling the card translation field. This stats-only retime writes a temporary cached copy and never edits the original subtitle files. + Default trigger is `Ctrl+Alt+S` via `shortcuts.triggerSubsync`. Customize it there, or set it to `null` to disable. diff --git a/docs-site/installation.md b/docs-site/installation.md index 261e4fc49..07f490d95 100644 --- a/docs-site/installation.md +++ b/docs-site/installation.md @@ -221,6 +221,10 @@ Install [`subminer-bin`](https://aur.archlinux.org/packages/subminer-bin) from t paru -S subminer-bin ``` +For the [anime browser](anime-browser.md#the-bridge), optionally add +`mangatan-extension-server`. SubMiner picks the package up instead of +downloading its own copy of the bridge, and pacman keeps it updated. + Or manually: ```bash diff --git a/docs-site/jimaku-integration.md b/docs-site/jimaku-integration.md index 424df3007..21f3ee364 100644 --- a/docs-site/jimaku-integration.md +++ b/docs-site/jimaku-integration.md @@ -81,6 +81,8 @@ SubMiner extracts media info from the current video path to pre-fill the search - **Season + episode patterns:** `S01E03`, `1x03` - **Episode-only patterns:** `E03`, `EP03`, or dash-separated numbers like `Title - 03 -` +- **Spelled-out episode labels:** `Episode 4`, `第4話` +- **Season named at the end of the title:** `… Season 3`, `… 3rd Season`, `… S3`, `… 第3期` - the season goes in the Season field instead of being searched for as part of the series name - **Season folders:** a parent directory named `Season 2` or `S2` fills in the season when the filename lacks one - **Bracket tags:** `[SubGroup]`, `[1080p]`, `[HEVC]` - stripped before title extraction - **Year tags:** `(2024)` - stripped @@ -89,6 +91,8 @@ SubMiner extracts media info from the current video path to pre-fill the search If the parser produces a high-confidence result (title + episode both detected), the search runs automatically when the modal opens. Otherwise, you can adjust the fields manually before searching. +Episodes launched from the [anime browser](/anime-browser) skip parsing entirely: Title, Season, and Episode come from the source's own listing, which is why the search runs immediately even though the stream URL says nothing about the episode. + ## Troubleshooting **"Jimaku API key not set"** diff --git a/docs-site/public/config.example.jsonc b/docs-site/public/config.example.jsonc index 8e9a3cd60..6afde16c2 100644 --- a/docs-site/public/config.example.jsonc +++ b/docs-site/public/config.example.jsonc @@ -359,6 +359,12 @@ "__playlist-browser-open" ] // Command setting. }, + { + "key": "Ctrl+Alt+KeyA", // Key setting. + "command": [ + "__anime-browser-open" + ] // Command setting. + }, { "key": "Ctrl+Shift+KeyH", // Key setting. "command": [ @@ -636,6 +642,21 @@ } // Lapis kiku setting. }, // Automatic Anki updates and media generation options. + // ========================================== + // Anime Browser + // Anime browser sources. SubMiner ships no extension repositories and bundles no sources; + // add a repository index URL here (or drop .apk files in the extensions directory) to have any. + // Hot-reload: autoOpenJimaku applies to the next episode; other anime changes apply the next time the anime browser opens. + // ========================================== + "anime": { + "autoOpenJimaku": false, // Pause Anime Browser playback and open Jimaku when an episode loads. Playback resumes after a subtitle loads or the modal closes only when auto-open initiated the pause. Values: true | false + "extensionsDir": "", // Directory holding Aniyomi extension .apk files. Empty uses /anime-extensions. + "repos": [], // Extension repository index URLs (any https .json index, e.g. https://.../index.min.json). Empty by default; SubMiner ships no repositories. + "preferredQuality": "", // Preferred stream quality label, matched as a substring (for example: 1080). Empty uses the source order. + "defaultSource": "", // Source the Anime Browser selects when it opens: a source id (:) or "all" for every installed source. Empty selects the first installed source. "Set default" in the Extensions tab writes this value. + "bridgeDir": "" // Directory holding an M-Extension-Server bundle (java runtime plus server jar) to run instead of the copy SubMiner downloads. Empty checks the package-manager install (Arch: mangatan-extension-server), then /anime-bridge. + }, // Anime browser sources. SubMiner ships no extension repositories and bundles no sources; + // ========================================== // Jimaku // Jimaku API configuration and defaults. diff --git a/docs-site/shortcuts.md b/docs-site/shortcuts.md index 4dd5b6b0b..4f873ab91 100644 --- a/docs-site/shortcuts.md +++ b/docs-site/shortcuts.md @@ -51,6 +51,7 @@ These control playback and subtitle display. They require overlay window focus. | `J` | Cycle primary subtitle track | | `Shift+J` | Cycle secondary subtitle track | | `Ctrl+Alt+P` | Open playlist browser for current directory + queue | +| `Ctrl+Alt+A` | Toggle Anime Browser inside the player | | `ArrowRight` | Seek forward 5 seconds | | `ArrowLeft` | Seek backward 5 seconds | | `ArrowUp` | Seek forward 60 seconds | @@ -69,7 +70,7 @@ These control playback and subtitle display. They require overlay window focus. | `Right-click` | Toggle pause (outside subtitle area) | | `Right-click + drag` | Reposition subtitles (on subtitle area) | -The mpv-command rows above (`Space`, `F`, `J`, `Shift+J`, the seek/sub-seek/sub-step/sub-delay keys, replay/play-next, and quit) are merged from the `keybindings` config array and can be remapped or disabled there. `V` and the mouse actions are built-in overlay behaviors and are not part of the `keybindings` array. The playlist browser opens a split overlay modal with sibling video files on the left and the live mpv playlist on the right. +The mpv-command rows above (`Space`, `F`, `J`, `Shift+J`, the seek/sub-seek/sub-step/sub-delay keys, replay/play-next, and quit) are merged from the `keybindings` config array and can be remapped or disabled there. `V` and the mouse actions are built-in overlay behaviors and are not part of the `keybindings` array. The playlist browser opens a split overlay modal with sibling video files on the left and the live mpv playlist on the right. The Anime Browser shortcut toggles a dedicated overlay modal bounded to the player; the standalone Anime Browser window remains independent, while active playback is shared. On macOS managed playback, SubMiner disables mpv's menu-bar shortcuts so configured SubMiner shortcuts like `Cmd+Shift+O` reach the mpv plugin instead of opening native mpv menu actions. diff --git a/docs-site/subtitle-generation.md b/docs-site/subtitle-generation.md index 7f71a66bf..5108d868b 100644 --- a/docs-site/subtitle-generation.md +++ b/docs-site/subtitle-generation.md @@ -1,6 +1,6 @@ # Japanese subtitle generation -Generate Japanese SRT subtitles from a local video's audio using [whisper.cpp](https://github.com/ggml-org/whisper.cpp). The launcher and overlay use the same local generation service. Audio stays on your computer. Model downloads require an internet connection; generation with an installed model does not. +Generate Japanese SRT subtitles from a local video's audio or an anime stream using [whisper.cpp](https://github.com/ggml-org/whisper.cpp). The launcher and overlay use the same local generation service. Transcription runs on your computer without uploading audio. Model downloads and stream audio extraction require an internet connection; local-file generation with an installed model does not. ## Setup @@ -51,7 +51,7 @@ A configured external Model Path takes precedence and hides the managed model pi ## From the overlay -1. Open a local video in mpv and select its Japanese audio track. +1. Open a local video or an anime episode stream in mpv and select its Japanese audio track. 2. Press **Ctrl+Shift+G** to open the standalone generation modal. When the subtitle sidebar has no subtitle lines loaded, it also offers a **Generate Japanese subtitles** button. Neither an open sidebar nor an existing subtitle track is required for the shortcut. 3. Choose a model and download it if prompted, or configure your existing model path in Settings and click **Check again**. 4. Optionally check **Focus on spoken dialogue** and click **Download speech detection model** if prompted. @@ -63,6 +63,24 @@ The modal adapts to the player window, using a wider layout when space allows an SubMiner saves `