mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-22 12:11:27 -07:00
Compare commits
1 Commits
main
...
youtube-su
| Author | SHA1 | Date | |
|---|---|---|---|
|
3ab6003b27
|
21
CHANGELOG.md
21
CHANGELOG.md
@@ -2,20 +2,19 @@
|
||||
|
||||
## v0.8.0 (2026-03-22)
|
||||
|
||||
### Added
|
||||
- Overlay: Added the subtitle sidebar feature with a new `subtitleSidebar` configuration surface.
|
||||
- Overlay: Added a sidebar modal with cue list rendering, click-to-seek, active-cue highlighting, and embedded layout support.
|
||||
- IPC: Added sidebar snapshot plumbing between renderer and main process for overlay/sidebar synchronization.
|
||||
|
||||
### Changed
|
||||
- Config: Added hot-reloadable sidebar options for enablement, layout, visibility, typography, opacity, sizing, and interaction behavior (`autoOpen`, `pauseOnHover`, `autoScroll`, toggle key).
|
||||
- Docs: Added full `subtitleSidebar` documentation coverage, including sample config, option table, and toggle shortcut notes.
|
||||
- Runtime: Improved subtitle prefetch and rendering flow so sidebar and overlay subtitle states are kept in sync across media transitions.
|
||||
- Docs: Refreshed the vendored Texthooker docs/index.html bundle to match the latest local build artifacts.
|
||||
|
||||
### Fixed
|
||||
- Overlay: Kept sidebar cue tracking stable across playback transitions and timing edge cases.
|
||||
- Overlay: Improved sidebar resume/start behavior to jump directly to the first resolved active cue.
|
||||
- Overlay: Stopped stale subtitle refreshes from regressing active-cue and text state.
|
||||
- Anki: Known-word cache refreshes now reconcile Anki changes incrementally instead of wiping and rebuilding on startup, mined cards can append their word into the cache immediately through a new default-enabled config flag, and explicit refreshes now run through `subminer doctor --refresh-known-words`.
|
||||
- Subtitle: Restored known-word coloring and JLPT underlines for subtitle tokens like `大体` when the subtitle token is kanji but the known-word cache only matches the kana reading.
|
||||
- Stats: Episode progress in the anime page now uses the last ended playback position instead of cumulative active watch time, avoiding distorted percentages after rewatches or repeated sessions.
|
||||
- Stats: Anime episode progress now keeps the latest known playback position through active-session checkpoints and stale-session recovery, so recently watched episodes no longer lose their progress percentage.
|
||||
- Stats: Anime episode progress now falls back to the latest retained subtitle/event timing when a session is missing a persisted playback-position checkpoint, so older watch sessions no longer get stuck at `0%` progress.
|
||||
- Overlay: Kept subtitle sidebar cue tracking stable across transitions by avoiding cue-line regression on subtitle timing edge cases and stale text updates.
|
||||
- Overlay: Improved sidebar config by documenting and exposing layout mode and typography options (`layout`, `fontFamily`, `fontSize`) in the generated documentation flow.
|
||||
- Overlay: Added `subtitleSidebar.autoOpen` (default `false`) to open the subtitle sidebar once during overlay startup when the sidebar feature is enabled.
|
||||
- Overlay: Made subtitle sidebar resume/start positioning jump directly to the first resolved active cue instead of smooth-scrolling through the full list, while keeping smooth auto-follow for later cue changes.
|
||||
|
||||
## v0.7.0 (2026-03-19)
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
type: docs
|
||||
area: docs
|
||||
|
||||
- Added a new WebSocket / Texthooker API and integration guide covering websocket payloads, custom client patterns, mpv plugin automation, and webhook-style relay examples.
|
||||
- Linked the new integration guide from configuration and mining workflow docs for easier discovery.
|
||||
@@ -95,7 +95,6 @@ export default {
|
||||
{ text: 'Building & Testing', link: '/development' },
|
||||
{ text: 'Architecture', link: '/architecture' },
|
||||
{ text: 'IPC + Runtime Contracts', link: '/ipc-contracts' },
|
||||
{ text: 'WebSocket + Texthooker API', link: '/websocket-texthooker-api' },
|
||||
{ text: 'Changelog', link: '/changelog' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## v0.8.0 (2026-03-22)
|
||||
- Added a configurable subtitle sidebar feature (`subtitleSidebar`) with overlay/embedded rendering, click-to-seek cue list, and hot-reloadable visibility and behavior controls.
|
||||
- Added release docs updates for sidebar configuration, including options, sample config, and toggle shortcut behavior.
|
||||
- Synced sidebar and overlay subtitle states during playback transitions via IPC-backed snapshot plumbing.
|
||||
- Fixed sidebar cue tracking to remain stable across timing edge cases and stale subtitle refreshes.
|
||||
- Improved sidebar resume/start behavior by jumping directly to the first resolved active cue.
|
||||
- Refreshed the vendored Texthooker docs/index.html bundle to match the latest local build artifacts.
|
||||
- Added incremental known-word cache refresh behavior so mined cards can append cache entries immediately and `subminer doctor --refresh-known-words` is now the explicit full refresh path.
|
||||
- Fixed known-word/JLPT subtitle styling so tokens like `大体` keep expected coloring even when only the kana reading is in cache.
|
||||
- Fixed anime progress to use last ended playback position and keep latest known checkpoint across sessions, preventing stale or zero percent regressions.
|
||||
- Kept subtitle sidebar cue tracking stable across transitions and improved sidebar configuration documentation for `layout`, `fontFamily`, and `fontSize`.
|
||||
- Added `subtitleSidebar.autoOpen` to open the subtitle sidebar at startup when enabled.
|
||||
- Improved sidebar resume/start behavior to jump directly to the active cue on resume while preserving auto-follow smooth motion.
|
||||
|
||||
## v0.7.0 (2026-03-19)
|
||||
- Added a full local immersion dashboard release line with Overview, Library, Trends, Vocabulary, and Sessions drill-down views backed by SQLite tracking data.
|
||||
|
||||
@@ -195,8 +195,6 @@ Defaults warm everything (`true` for all toggles, `lowPowerMode: false`). Settin
|
||||
|
||||
The overlay includes a built-in WebSocket server that broadcasts subtitle text to connected clients (such as texthooker-ui) for external processing.
|
||||
|
||||
For endpoint details, payload examples, and client patterns, see [WebSocket / Texthooker API & Integration](/websocket-texthooker-api).
|
||||
|
||||
By default, the server uses "auto" mode: it starts automatically unless [mpv_websocket](https://github.com/kuroahna/mpv_websocket) is detected at `~/.config/mpv/mpv_websocket`. If you have mpv_websocket installed, the built-in server is skipped to avoid conflicts.
|
||||
|
||||
See `config.example.jsonc` for detailed configuration options.
|
||||
|
||||
@@ -176,8 +176,6 @@ SubMiner runs a local HTTP server at `http://127.0.0.1:5174` (configurable port)
|
||||
|
||||
The texthooker page displays the current subtitle and updates as new lines arrive. This is useful if you prefer to do lookups in a browser rather than through the overlay's built-in Yomitan.
|
||||
|
||||
If you want to build your own browser client, websocket consumer, or automation relay, see [WebSocket / Texthooker API & Integration](/websocket-texthooker-api).
|
||||
|
||||
## Subtitle Sync (Subsync)
|
||||
|
||||
If your subtitle file is out of sync with the audio, SubMiner can resynchronize it using [alass](https://github.com/kaegi/alass) or [ffsubsync](https://github.com/smacke/ffsubsync).
|
||||
|
||||
@@ -1,357 +0,0 @@
|
||||
# WebSocket / Texthooker API & Integration
|
||||
|
||||
SubMiner exposes a small set of local integration surfaces for browser tools, automation helpers, and mpv-driven workflows:
|
||||
|
||||
- **Subtitle WebSocket** at `ws://127.0.0.1:6677` by default for plain subtitle pushes.
|
||||
- **Annotation WebSocket** at `ws://127.0.0.1:6678` by default for token-aware clients.
|
||||
- **Texthooker HTTP UI** at `http://127.0.0.1:5174` by default for browser-based subtitle consumption.
|
||||
- **mpv plugin script messages** for in-player automation and extension.
|
||||
|
||||
This page documents those integration points and shows how to build custom consumers around them.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Surface | Default | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `websocket` | `ws://127.0.0.1:6677` | Basic subtitle broadcast stream |
|
||||
| `annotationWebsocket` | `ws://127.0.0.1:6678` | Structured stream with token metadata |
|
||||
| `texthooker` | `http://127.0.0.1:5174` | Local texthooker UI with injected websocket config |
|
||||
| mpv plugin | `script-message subminer-*` | Start/stop/toggle/status automation inside mpv |
|
||||
|
||||
## Enable and Configure the Services
|
||||
|
||||
SubMiner's integration ports are configured in `config.jsonc`.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"websocket": {
|
||||
"enabled": "auto",
|
||||
"port": 6677
|
||||
},
|
||||
"annotationWebsocket": {
|
||||
"enabled": true,
|
||||
"port": 6678
|
||||
},
|
||||
"texthooker": {
|
||||
"launchAtStartup": true,
|
||||
"openBrowser": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### How startup behaves
|
||||
|
||||
- `websocket.enabled: "auto"` starts the basic subtitle websocket unless SubMiner detects the external `mpv_websocket` plugin.
|
||||
- `annotationWebsocket` is independent from `websocket` and stays enabled unless you explicitly disable it.
|
||||
- `texthooker.launchAtStartup` starts the local HTTP UI automatically.
|
||||
- `texthooker.openBrowser` controls whether SubMiner opens the texthooker page in your browser when it starts.
|
||||
|
||||
If you use the [mpv plugin](/mpv-plugin), it can also start a texthooker-only helper process and override the texthooker port in `subminer.conf`.
|
||||
|
||||
## Developer API Documentation
|
||||
|
||||
### 1. Subtitle WebSocket
|
||||
|
||||
Use the basic subtitle websocket when you only need the current subtitle line and a ready-to-render HTML sentence string.
|
||||
|
||||
- **Default URL:** `ws://127.0.0.1:6677`
|
||||
- **Transport:** local WebSocket server bound to `127.0.0.1`
|
||||
- **Direction:** server push only
|
||||
- **Client auth:** none
|
||||
- **Reconnects:** client-managed
|
||||
|
||||
When a client connects, SubMiner immediately sends the latest subtitle payload if one is available. After that, it pushes a new message each time the current subtitle changes.
|
||||
|
||||
#### Message shape
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"text": "無事",
|
||||
"sentence": "<span class=\"word word-known word-jlpt-n2\" data-reading=\"ぶじ\" data-headword=\"無事\" data-frequency-rank=\"745\" data-jlpt-level=\"N2\">無事</span>",
|
||||
"tokens": [
|
||||
{
|
||||
"surface": "無事",
|
||||
"reading": "ぶじ",
|
||||
"headword": "無事",
|
||||
"startPos": 0,
|
||||
"endPos": 2,
|
||||
"partOfSpeech": "other",
|
||||
"isMerged": false,
|
||||
"isKnown": true,
|
||||
"isNPlusOneTarget": false,
|
||||
"isNameMatch": false,
|
||||
"jlptLevel": "N2",
|
||||
"frequencyRank": 745,
|
||||
"className": "word word-known word-jlpt-n2",
|
||||
"frequencyRankLabel": "745",
|
||||
"jlptLevelLabel": "N2"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Field reference
|
||||
|
||||
| Field | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `version` | number | Current websocket payload version. Today this is `1`. |
|
||||
| `text` | string | Raw subtitle text. |
|
||||
| `sentence` | string | HTML string with `<span>` wrappers and `data-*` attributes for client rendering. |
|
||||
| `tokens` | array | Token metadata; empty when the subtitle is not tokenized yet. |
|
||||
|
||||
Each token may include:
|
||||
|
||||
| Token field | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `surface` | string | Display text for the token |
|
||||
| `reading` | string | Kana reading when available |
|
||||
| `headword` | string | Dictionary headword when available |
|
||||
| `startPos` / `endPos` | number | Character offsets in the subtitle text |
|
||||
| `partOfSpeech` | string | SubMiner token POS label |
|
||||
| `isMerged` | boolean | Whether this token represents merged content |
|
||||
| `isKnown` | boolean | Marked known by SubMiner's known-word logic |
|
||||
| `isNPlusOneTarget` | boolean | True when the token is the sentence's N+1 target |
|
||||
| `isNameMatch` | boolean | True for prioritized character-name matches |
|
||||
| `frequencyRank` | number | Frequency rank when available |
|
||||
| `jlptLevel` | string | JLPT level when available |
|
||||
| `className` | string | CSS-ready class list derived from token state |
|
||||
| `frequencyRankLabel` | string or `null` | Preformatted rank label for UIs |
|
||||
| `jlptLevelLabel` | string or `null` | Preformatted JLPT label for UIs |
|
||||
|
||||
### 2. Annotation WebSocket
|
||||
|
||||
Use the annotation websocket for custom clients that want the same structured token payload the bundled texthooker UI consumes.
|
||||
|
||||
- **Default URL:** `ws://127.0.0.1:6678`
|
||||
- **Payload shape:** same JSON contract as the basic subtitle websocket
|
||||
- **Primary difference:** this stream is intended to stay on even when the basic websocket auto-disables because `mpv_websocket` is installed
|
||||
|
||||
In practice, if you are building a new client, prefer `annotationWebsocket` unless you specifically need compatibility with an existing `websocket` consumer.
|
||||
|
||||
### 3. HTML markup conventions
|
||||
|
||||
The `sentence` field is pre-rendered HTML generated by SubMiner. Depending on token state, it can include classes such as:
|
||||
|
||||
- `word`
|
||||
- `word-known`
|
||||
- `word-n-plus-one`
|
||||
- `word-name-match`
|
||||
- `word-jlpt-n1` through `word-jlpt-n5`
|
||||
- `word-frequency-single`
|
||||
- `word-frequency-band-1` through `word-frequency-band-5`
|
||||
|
||||
SubMiner also adds tooltip-friendly data attributes when available:
|
||||
|
||||
- `data-reading`
|
||||
- `data-headword`
|
||||
- `data-frequency-rank`
|
||||
- `data-jlpt-level`
|
||||
|
||||
If you need a fully custom UI, ignore `sentence` and render from `tokens` instead.
|
||||
|
||||
## Texthooker Integration Guide
|
||||
|
||||
### When to use the bundled texthooker page
|
||||
|
||||
Use texthooker when you want a browser tab that:
|
||||
|
||||
- updates live from current subtitles
|
||||
- works well with browser-based Yomitan setups
|
||||
- inherits SubMiner's coloring preferences and websocket URL automatically
|
||||
|
||||
Start it with either:
|
||||
|
||||
```bash
|
||||
subminer texthooker
|
||||
```
|
||||
|
||||
or by leaving `texthooker.launchAtStartup` enabled.
|
||||
|
||||
### What SubMiner injects into the page
|
||||
|
||||
When SubMiner serves the local texthooker UI, it injects bootstrap values into `window.localStorage`, including:
|
||||
|
||||
- `bannou-texthooker-websocketUrl`
|
||||
- coloring toggles for known/N+1/name/frequency/JLPT styling
|
||||
- CSS custom properties for SubMiner's token colors
|
||||
|
||||
That means the bundled page already knows which websocket to connect to and which color palette to use.
|
||||
|
||||
### Build a custom websocket client
|
||||
|
||||
Here is a minimal browser client for the annotation stream:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<meta charset="utf-8" />
|
||||
<title>SubMiner client</title>
|
||||
<div id="subtitle">Waiting for subtitles...</div>
|
||||
<script>
|
||||
const subtitle = document.getElementById('subtitle');
|
||||
const ws = new WebSocket('ws://127.0.0.1:6678');
|
||||
|
||||
ws.addEventListener('message', (event) => {
|
||||
const payload = JSON.parse(event.data);
|
||||
subtitle.innerHTML = payload.sentence || payload.text;
|
||||
});
|
||||
|
||||
ws.addEventListener('close', () => {
|
||||
subtitle.textContent = 'Connection closed; reload or reconnect.';
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Build a custom Node client
|
||||
|
||||
```js
|
||||
import WebSocket from 'ws';
|
||||
|
||||
const ws = new WebSocket('ws://127.0.0.1:6678');
|
||||
|
||||
ws.on('message', (raw) => {
|
||||
const payload = JSON.parse(String(raw));
|
||||
console.log({
|
||||
text: payload.text,
|
||||
tokens: payload.tokens.length,
|
||||
firstToken: payload.tokens[0]?.surface ?? null,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Integration tips
|
||||
|
||||
- Bind only to `127.0.0.1`; these services are local-only by design.
|
||||
- Handle empty `tokens` arrays gracefully because subtitle text can arrive before tokenization completes.
|
||||
- Reconnect on disconnect; SubMiner does not manage client reconnects for you.
|
||||
- Prefer `payload.text` for logging/automation and `payload.sentence` or `payload.tokens` for UI rendering.
|
||||
|
||||
## Plugin Development
|
||||
|
||||
SubMiner does **not** currently expose a general-purpose third-party plugin SDK inside the app itself. Today, the supported extension surfaces are:
|
||||
|
||||
1. the local websocket streams
|
||||
2. the local texthooker UI
|
||||
3. the mpv Lua plugin's script-message API
|
||||
4. the launcher CLI
|
||||
|
||||
### mpv script messages
|
||||
|
||||
The mpv plugin accepts these script messages:
|
||||
|
||||
```text
|
||||
script-message subminer-start
|
||||
script-message subminer-stop
|
||||
script-message subminer-toggle
|
||||
script-message subminer-menu
|
||||
script-message subminer-options
|
||||
script-message subminer-restart
|
||||
script-message subminer-status
|
||||
script-message subminer-autoplay-ready
|
||||
script-message subminer-aniskip-refresh
|
||||
script-message subminer-skip-intro
|
||||
```
|
||||
|
||||
The start command also accepts inline overrides:
|
||||
|
||||
```text
|
||||
script-message subminer-start backend=hyprland socket=/custom/path texthooker=no log-level=debug
|
||||
```
|
||||
|
||||
### Practical extension patterns
|
||||
|
||||
#### Add another mpv script that coordinates with SubMiner
|
||||
|
||||
Examples:
|
||||
|
||||
- send `subminer-start` after your own media-selection script chooses a file
|
||||
- send `subminer-status` before running follow-up automation
|
||||
- send `subminer-aniskip-refresh` after you update title/episode metadata
|
||||
|
||||
#### Build a launcher wrapper
|
||||
|
||||
Examples:
|
||||
|
||||
- open a media picker, then call `subminer /path/to/file.mkv`
|
||||
- launch browser-only subtitle tooling with `subminer texthooker`
|
||||
- disable the helper UI for a session with `subminer --no-texthooker video.mkv`
|
||||
|
||||
#### Build an overlay-adjacent client
|
||||
|
||||
Examples:
|
||||
|
||||
- browser widget showing current subtitle + token breakdown
|
||||
- local vocabulary capture helper that writes interesting lines to a file
|
||||
- bridge service that forwards websocket events into your own workflow engine
|
||||
|
||||
## Webhook Examples
|
||||
|
||||
SubMiner does **not** currently send outbound webhooks by itself. The supported pattern is to consume the websocket locally and relay events into another system.
|
||||
|
||||
That still makes webhook-style automation straightforward.
|
||||
|
||||
### Example: forward subtitle lines to a local webhook receiver
|
||||
|
||||
```js
|
||||
import WebSocket from 'ws';
|
||||
|
||||
const ws = new WebSocket('ws://127.0.0.1:6678');
|
||||
|
||||
ws.on('message', async (raw) => {
|
||||
const payload = JSON.parse(String(raw));
|
||||
|
||||
await fetch('http://127.0.0.1:5678/subminer/subtitle', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
text: payload.text,
|
||||
tokens: payload.tokens,
|
||||
receivedAt: new Date().toISOString(),
|
||||
}),
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Automation ideas
|
||||
|
||||
- **n8n / Make / Zapier relay:** send each subtitle line into an automation workflow for logging, translation, or summarization.
|
||||
- **Discord / Slack notifier:** post only lines that contain unknown words or N+1 targets.
|
||||
- **Obsidian / Markdown capture:** append subtitle lines plus token metadata to a daily immersion note.
|
||||
- **Local LLM pipeline:** trigger a glossary, translation, or sentence-mining workflow whenever a new line arrives.
|
||||
|
||||
### Filtering example: only forward N+1 lines
|
||||
|
||||
```js
|
||||
import WebSocket from 'ws';
|
||||
|
||||
const ws = new WebSocket('ws://127.0.0.1:6678');
|
||||
|
||||
ws.on('message', async (raw) => {
|
||||
const payload = JSON.parse(String(raw));
|
||||
const hasNPlusOne = payload.tokens.some((token) => token.isNPlusOneTarget);
|
||||
|
||||
if (!hasNPlusOne) return;
|
||||
|
||||
await fetch('http://127.0.0.1:5678/subminer/n-plus-one', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ text: payload.text, tokens: payload.tokens }),
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Recommended Integration Combinations
|
||||
|
||||
- **Browser Yomitan client:** `texthooker` + `annotationWebsocket`
|
||||
- **Custom dashboard:** `annotationWebsocket` only
|
||||
- **Lightweight subtitle mirror:** `websocket` only
|
||||
- **mpv-side automation:** mpv plugin script messages + optional websocket relay
|
||||
- **Webhook-style workflows:** `annotationWebsocket` + your own local relay service
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [Configuration](/configuration#websocket-server)
|
||||
- [Mining Workflow — Texthooker](/mining-workflow#texthooker)
|
||||
- [MPV Plugin](/mpv-plugin)
|
||||
- [Launcher Script](/launcher-script)
|
||||
- [Anki Integration](/anki-integration#proxy-mode-setup-yomitan--texthooker)
|
||||
Reference in New Issue
Block a user