From 5e74209b613e4d0f81964d6082e980020bbb20ba Mon Sep 17 00:00:00 2001 From: sudacode Date: Sun, 1 Mar 2026 00:03:09 -0800 Subject: [PATCH] docs: remove obsolete subtitle position edit mode references --- docs/configuration.md | 891 +++++++++++++++++++++------------------- docs/shortcuts.md | 12 - docs/troubleshooting.md | 4 +- docs/usage.md | 4 - 4 files changed, 466 insertions(+), 445 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index be3eb71..d15ba1c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -72,27 +72,466 @@ Restart-required changes: The configuration file includes several main sections: -- [**AnkiConnect**](#ankiconnect) - Automatic Anki card creation with media +**Core Settings** + +- [**Logging**](#logging) - Runtime log level - [**Auto-Start Overlay**](#auto-start-overlay) - Automatically show overlay on MPV connection -- [**Visible Overlay Subtitle Binding**](#visible-overlay-subtitle-binding) - Link visible overlay toggles to MPV subtitle visibility -- [**Auto Subtitle Sync**](#auto-subtitle-sync) - Sync current subtitle with `alass`/`ffsubsync` -- [**Subtitle Position Edit**](#subtitle-position-edit) - Fine-tune subtitle alignment in overlay +- [**Startup Warmups**](#startup-warmups) - Control what preloads on startup vs first-use defer +- [**WebSocket Server**](#websocket-server) - Built-in subtitle broadcasting server +- [**Texthooker**](#texthooker) - Control browser opening behavior + +**Subtitle Display** + +- [**Subtitle Style**](#subtitle-style) - Appearance customization +- [**Subtitle Position**](#subtitle-position) - Overlay vertical positioning +- [**Secondary Subtitles**](#secondary-subtitles) - Dual subtitle track support + +**Keyboard & Controls** + +- [**Keybindings**](#keybindings) - MPV command shortcuts +- [**Shortcuts Configuration**](#shortcuts-configuration) - Overlay keyboard shortcuts +- [**Manual Card Update Shortcuts**](#manual-card-update-shortcuts) - Shortcuts for manual Anki card workflows +- [**Session Help Modal**](#session-help-modal) - In-overlay shortcut reference +- [**Runtime Option Palette**](#runtime-option-palette) - Live, session-only option toggles + +**Anki Integration** + +- [**AnkiConnect**](#ankiconnect) - Automatic Anki card creation with media +- [**N+1 Word Highlighting**](#n1-word-highlighting) - Known-word cache and single-target highlighting +- [**Field Grouping Modes**](#field-grouping-modes) - Kiku/Lapis duplicate card merging + +**External Integrations** + - [**Jimaku**](#jimaku) - Jimaku API configuration and defaults +- [**Auto Subtitle Sync**](#auto-subtitle-sync) - Sync current subtitle with `alass`/`ffsubsync` - [**AniList**](#anilist) - Optional post-watch progress updates - [**Jellyfin**](#jellyfin) - Optional Jellyfin auth, library listing, and playback launch - [**Discord Rich Presence**](#discord-rich-presence) - Optional Discord activity card updates -- [**Keybindings**](#keybindings) - MPV command shortcuts -- [**Runtime Option Palette**](#runtime-option-palette) - Live, session-only option toggles -- [**Secondary Subtitles**](#secondary-subtitles) - Dual subtitle track support -- [**Shortcuts Configuration**](#shortcuts-configuration) - Overlay keyboard shortcuts -- [**Subtitle Position**](#subtitle-position) - Overlay vertical positioning -- [**Subtitle Style**](#subtitle-style) - Appearance customization -- [**Texthooker**](#texthooker) - Control browser opening behavior -- [**WebSocket Server**](#websocket-server) - Built-in subtitle broadcasting server -- [**Startup Warmups**](#startup-warmups) - Control what preloads on startup vs first-use defer - [**Immersion Tracking**](#immersion-tracking) - Track subtitle sessions and mining activity in SQLite - [**YouTube Subtitle Generation**](#youtube-subtitle-generation) - Launcher defaults for yt-dlp + local whisper fallback +## Core Settings + +### Logging + +Control the minimum log level for runtime output: + +```json +{ + "logging": { + "level": "info" + } +} +``` + +| Option | Values | Description | +| ------- | ----------------------------------- | ------------------------------------------------ | +| `level` | `"debug"`, `"info"`, `"warn"`, `"error"` | Minimum log level for runtime logging (default: `"info"`) | + +### Auto-Start Overlay + +Control whether the overlay automatically becomes visible when it connects to mpv: + +```json +{ + "auto_start_overlay": false +} +``` + +| Option | Values | Description | +| -------------------- | --------------- | ------------------------------------------------------ | +| `auto_start_overlay` | `true`, `false` | Auto-show overlay on mpv connection (default: `false`) | + +The mpv plugin controls startup overlay visibility via `auto_start_visible_overlay` in `subminer.conf`. +For wrapper-driven playback, `subminer.conf` can also enable startup pause gating with +`auto_start_pause_until_ready` (requires `auto_start=yes` + `auto_start_visible_overlay=yes`). +Current plugin defaults in `subminer.conf` are: + +- `auto_start=yes` +- `auto_start_visible_overlay=yes` +- `auto_start_pause_until_ready=yes` + +### Startup Warmups + +Control which startup warmups run in the background versus deferring to first real usage: + +```json +{ + "startupWarmups": { + "lowPowerMode": false, + "mecab": true, + "yomitanExtension": true, + "subtitleDictionaries": true, + "jellyfinRemoteSession": true + } +} +``` + +| Option | Values | Description | +| ----------------------- | --------------- | ------------------------------------------------------------------------------------------------- | +| `lowPowerMode` | `true`, `false` | Defer all warmups except Yomitan extension | +| `mecab` | `true`, `false` | Warm up MeCab tokenizer at startup | +| `yomitanExtension` | `true`, `false` | Warm up Yomitan extension at startup | +| `subtitleDictionaries` | `true`, `false` | Warm up JLPT + frequency dictionaries at startup | +| `jellyfinRemoteSession` | `true`, `false` | Warm up Jellyfin remote session at startup (still requires Jellyfin remote auto-connect settings) | + +Defaults warm everything (`true` for all toggles, `lowPowerMode: false`). Setting a warmup toggle to `false` defers that work until first usage. + +### WebSocket Server + +The overlay includes a built-in WebSocket server that broadcasts subtitle text to connected clients (such as texthooker-ui) for external processing. + +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. + +```json +{ + "websocket": { + "enabled": "auto", + "port": 6677 + } +} +``` + +| Option | Values | Description | +| --------- | ------------------------- | -------------------------------------------------------- | +| `enabled` | `true`, `false`, `"auto"` | `"auto"` (default) disables if mpv_websocket is detected | +| `port` | number | WebSocket server port (default: 6677) | + +### Texthooker + +Control whether the browser opens automatically when texthooker starts: + +See `config.example.jsonc` for detailed configuration options. + +```json +{ + "texthooker": { + "openBrowser": true + } +} +``` + +## Subtitle Display + +### Subtitle Style + +Customize the appearance of primary and secondary subtitles: + +See `config.example.jsonc` for detailed configuration options. + +```json +{ + "subtitleStyle": { + "fontFamily": "M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP", + "fontSize": 35, + "fontColor": "#cad3f5", + "fontWeight": "600", + "lineHeight": 1.35, + "letterSpacing": "-0.01em", + "wordSpacing": 0, + "fontKerning": "normal", + "textRendering": "geometricPrecision", + "textShadow": "0 3px 10px rgba(0,0,0,0.69)", + "fontStyle": "normal", + "backgroundColor": "rgb(30, 32, 48, 0.88)", + "backdropFilter": "blur(6px)", + "secondary": { + "fontFamily": "Inter, Noto Sans, Helvetica Neue, sans-serif", + "fontSize": 24, + "fontColor": "#cad3f5", + "backgroundColor": "transparent" + } + } +} +``` + +| Option | Values | Description | +| ---------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------- | +| `fontFamily` | string | CSS font-family value (default: `"M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP"`) | +| `fontSize` | number (px) | Font size in pixels (default: `35`) | +| `fontColor` | string | Any CSS color value (default: `"#cad3f5"`) | +| `fontWeight` | string | CSS font-weight, e.g. `"bold"`, `"normal"`, `"600"` (default: `"600"`) | +| `fontStyle` | string | `"normal"` or `"italic"` (default: `"normal"`) | +| `backgroundColor` | string | Any CSS color, including `"transparent"` (default: `"rgb(30, 32, 48, 0.88)"`) | +| `enableJlpt` | boolean | Enable JLPT level underline styling (`false` by default) | +| `preserveLineBreaks` | boolean | Preserve line breaks in visible overlay subtitle rendering (`false` by default). Enable to mirror mpv line layout. | +| `autoPauseVideoOnHover` | boolean | Pause playback while mouse hovers subtitle text, then resume on leave (`true` by default). | +| `hoverTokenColor` | string | Hex color used for hovered subtitle token highlight in mpv (default: catppuccin mauve) | +| `hoverTokenBackgroundColor` | string | CSS color used for hovered subtitle token background highlight (default: semi-transparent dark) | +| `frequencyDictionary.enabled` | boolean | Enable frequency highlighting from dictionary lookups (`false` by default) | +| `frequencyDictionary.sourcePath` | string | Path to a local frequency dictionary root. Leave empty or omit to use installed/default frequency-dictionary search paths. | +| `frequencyDictionary.topX` | number | Only color tokens whose frequency rank is `<= topX` (`1000` by default) | +| `frequencyDictionary.mode` | string | `"single"` or `"banded"` (`"single"` by default) | +| `frequencyDictionary.matchMode` | string | `"headword"` or `"surface"` (`"headword"` by default) | +| `frequencyDictionary.singleColor` | string | Color used for all highlighted tokens in single mode | +| `frequencyDictionary.bandedColors` | string[] | Array of five hex colors used for ranked bands in banded mode | +| `nPlusOneColor` | string | Existing n+1 highlight color (default: `#c6a0f6`) | +| `knownWordColor` | string | Existing known-word highlight color (default: `#a6da95`) | +| `jlptColors` | object | JLPT level underline colors object (`N1`..`N5`) | +| `secondary` | object | Override any of the above for secondary subtitles (optional) | + +JLPT underlining is powered by offline term-meta bank files at runtime. See [`docs/jlpt-vocab-bundle.md`](jlpt-vocab-bundle.md) for required files, source/version refresh steps, and deterministic fallback behavior. + +Frequency dictionary highlighting uses the same dictionary file format as JLPT bundle lookups (`term_meta_bank_*.json` under discovered dictionary directories). A token is highlighted when it has a positive integer `frequencyRank` (lower is more common) and the rank is within `topX`. + +Lookup behavior: + +- Set `frequencyDictionary.sourcePath` to a directory containing `term_meta_bank_*.json` for a fully custom source. +- If `sourcePath` is missing or empty, SubMiner searches default install/runtime locations for `frequency-dictionary` directories (for example app resources, user data paths, and current working directory). +- In both cases, only terms with a valid `frequencyRank` are used; everything else falls back to no highlighting. +- `frequencyDictionary.matchMode` controls which token text is used for frequency lookups: `headword` (dictionary form) or `surface` (visible subtitle text). +- Frequency highlighting skips tokens that look like non-lexical SFX/interjection noise (for example kana reduplication or short kana endings like `っ`), even when dictionary ranks exist. + +In `single` mode all highlights use `singleColor`; in `banded` mode tokens map to five ascending color bands from most common to least common inside the topX window. + +Secondary subtitle defaults: `fontFamily: "Inter, Noto Sans, Helvetica Neue, sans-serif"`, `fontSize: 24`, `fontColor: "#cad3f5"`, `backgroundColor: "transparent"`. Any property not set in `secondary` falls back to the CSS defaults. + +**See `config.example.jsonc`** for the complete list of subtitle style configuration options. + +`jlptColors` keys are: + +| Key | Default | Description | +| ---- | --------- | ----------------------- | +| `N1` | `#ed8796` | JLPT N1 underline color | +| `N2` | `#f5a97f` | JLPT N2 underline color | +| `N3` | `#f9e2af` | JLPT N3 underline color | +| `N4` | `#a6e3a1` | JLPT N4 underline color | +| `N5` | `#8aadf4` | JLPT N5 underline color | + +**Image Quality Notes:** + +- `imageQuality` affects JPG and WebP only; PNG is lossless and ignores this setting +- JPG quality is mapped to FFmpeg's scale (2-31, lower = better) +- WebP quality uses FFmpeg's native 0-100 scale + +### Subtitle Position + +Set the initial vertical subtitle position (measured from the bottom of the screen): + +```json +{ + "subtitlePosition": { + "yPercent": 10 + } +} +``` + +| Option | Values | Description | +| ---------- | ---------------- | ---------------------------------------------------------------------- | +| `yPercent` | number (0 - 100) | Distance from the bottom as a percent of screen height (default: `10`) | +In the overlay, you can fine-tune subtitle position at runtime with `Right-click + drag` on subtitle text. + +### Secondary Subtitles + +Display a second subtitle track (e.g., English alongside Japanese) in the overlay: + +See `config.example.jsonc` for detailed configuration options. + +```json +{ + "secondarySub": { + "secondarySubLanguages": ["eng", "en"], + "autoLoadSecondarySub": true, + "defaultMode": "hover" + } +} +``` + +| Option | Values | Description | +| ----------------------- | ---------------------------------- | ------------------------------------------------------ | +| `secondarySubLanguages` | string[] | Language codes to auto-load (e.g., `["eng", "en"]`) | +| `autoLoadSecondarySub` | `true`, `false` | Auto-detect and load matching secondary subtitle track | +| `defaultMode` | `"hidden"`, `"visible"`, `"hover"` | Initial display mode (default: `"hover"`) | + +**Display modes:** + +- **hidden** — Secondary subtitles not shown +- **visible** — Always visible at top of overlay +- **hover** — Only visible when hovering over the subtitle area (default) + +**See `config.example.jsonc`** for additional secondary subtitle configuration options. + +## Keyboard & Controls + +### Keybindings + +Add a `keybindings` array to configure keyboard shortcuts that send commands to mpv: + +See `config.example.jsonc` for detailed configuration options and more examples. + +**Default keybindings:** + +| Key | Command | Description | +| ----------------- | ---------------------------- | ------------------------------------- | +| `Space` | `["cycle", "pause"]` | Toggle pause | +| `KeyJ` | `["cycle", "sid"]` | Cycle primary subtitle track | +| `Shift+KeyJ` | `["cycle", "secondary-sid"]` | Cycle secondary subtitle track | +| `ArrowRight` | `["seek", 5]` | Seek forward 5 seconds | +| `ArrowLeft` | `["seek", -5]` | Seek backward 5 seconds | +| `ArrowUp` | `["seek", 60]` | Seek forward 60 seconds | +| `ArrowDown` | `["seek", -60]` | Seek backward 60 seconds | +| `Shift+KeyH` | `["sub-seek", -1]` | Jump to previous subtitle | +| `Shift+KeyL` | `["sub-seek", 1]` | Jump to next subtitle | +| `Ctrl+Shift+KeyH` | `["__replay-subtitle"]` | Replay current subtitle, pause at end | +| `Ctrl+Shift+KeyL` | `["__play-next-subtitle"]` | Play next subtitle, pause at end | +| `KeyQ` | `["quit"]` | Quit mpv | +| `Ctrl+KeyW` | `["quit"]` | Quit mpv | + +**Custom keybindings example:** + +```json +{ + "keybindings": [ + { "key": "ArrowRight", "command": ["seek", 5] }, + { "key": "ArrowLeft", "command": ["seek", -5] }, + { "key": "Shift+ArrowRight", "command": ["seek", 30] }, + { "key": "KeyR", "command": ["script-binding", "immersive/auto-replay"] }, + { "key": "KeyA", "command": ["script-message", "ankiconnect-add-note"] } + ] +} +``` + +**Key format:** Use `KeyboardEvent.code` values (`Space`, `ArrowRight`, `KeyR`, etc.) with optional modifiers (`Ctrl+`, `Alt+`, `Shift+`, `Meta+`). + +**Disable a default binding:** Set command to `null`: + +```json +{ "key": "Space", "command": null } +``` + +**Special commands:** Commands prefixed with `__` are handled internally by the overlay rather than sent to mpv. `__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.) + +For subtitle-position and subtitle-track proxy commands (`sub-pos`, `sid`, `secondary-sid`), SubMiner also shows an mpv OSD notification after the command runs. + +**See `config.example.jsonc`** for more keybinding examples and configuration options. + +### Shortcuts Configuration + +Customize or disable the overlay keyboard shortcuts: + +See `config.example.jsonc` for detailed configuration options. + +```json +{ + "shortcuts": { + "toggleVisibleOverlayGlobal": "Alt+Shift+O", + "copySubtitle": "CommandOrControl+C", + "copySubtitleMultiple": "CommandOrControl+Shift+C", + "updateLastCardFromClipboard": "CommandOrControl+V", + "triggerFieldGrouping": "CommandOrControl+G", + "triggerSubsync": "Ctrl+Alt+S", + "mineSentence": "CommandOrControl+S", + "mineSentenceMultiple": "CommandOrControl+Shift+S", + "markAudioCard": "CommandOrControl+Shift+A", + "openRuntimeOptions": "CommandOrControl+Shift+O", + "openJimaku": "Ctrl+Shift+J", + "multiCopyTimeoutMs": 3000 + } +} +``` + +| Option | Values | Description | +| ----------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `toggleVisibleOverlayGlobal` | string \| `null` | Global accelerator for toggling visible subtitle overlay (default: `"Alt+Shift+O"`) | +| `copySubtitle` | string \| `null` | Accelerator for copying current subtitle (default: `"CommandOrControl+C"`) | +| `copySubtitleMultiple` | string \| `null` | Accelerator for multi-copy mode (default: `"CommandOrControl+Shift+C"`) | +| `updateLastCardFromClipboard` | string \| `null` | Accelerator for updating card from clipboard (default: `"CommandOrControl+V"`) | +| `triggerFieldGrouping` | string \| `null` | Accelerator for Kiku field grouping on last card (default: `"CommandOrControl+G"`; only active when `behavior.autoUpdateNewCards` is `false`) | +| `triggerSubsync` | string \| `null` | Accelerator for running Subsync (default: `"Ctrl+Alt+S"`) | +| `mineSentence` | string \| `null` | Accelerator for creating sentence card from current subtitle (default: `"CommandOrControl+S"`) | +| `mineSentenceMultiple` | string \| `null` | Accelerator for multi-mine sentence card mode (default: `"CommandOrControl+Shift+S"`) | +| `multiCopyTimeoutMs` | number | Timeout in ms for multi-copy/mine digit input (default: `3000`) | +| `toggleSecondarySub` | string \| `null` | Accelerator for cycling secondary subtitle mode (default: `"CommandOrControl+Shift+V"`) | +| `markAudioCard` | string \| `null` | Accelerator for marking last card as audio card (default: `"CommandOrControl+Shift+A"`) | +| `openRuntimeOptions` | string \| `null` | Opens runtime options palette for live session-only toggles (default: `"CommandOrControl+Shift+O"`) | +| `openJimaku` | string \| `null` | Opens the Jimaku search modal (default: `"Ctrl+Shift+J"`) | + +**See `config.example.jsonc`** for the complete list of shortcut configuration options. + +Set any shortcut to `null` to disable it. + +Feature-dependent shortcuts/keybindings only run when their related integration is enabled. For example, Anki/Kiku shortcuts require `ankiConnect.enabled` (and Kiku-specific behavior where applicable), and Jellyfin remote startup behavior requires Jellyfin to be enabled. + +### Manual Card Update Shortcuts + +When `behavior.autoUpdateNewCards` is set to `false`, new cards are detected but not automatically updated. Use these keyboard shortcuts for manual control: + +| Shortcut | Action | +| -------------- | ------------------------------------------------------------------------------------------------------------------ | +| `Ctrl+C` | Copy the current subtitle line to clipboard (preserves line breaks) | +| `Ctrl+Shift+C` | Enter multi-copy mode. Press `1-9` to copy that many recent lines, or `Esc` to cancel. Timeout: 3 seconds | +| `Ctrl+V` | Update the last added Anki card using subtitles from clipboard | +| `Ctrl+G` | Trigger Kiku duplicate field grouping for the last added card (only when `behavior.autoUpdateNewCards` is `false`) | +| `Ctrl+S` | Create a sentence card from the current subtitle line | +| `Ctrl+Shift+S` | Enter multi-mine mode. Press `1-9` to create a sentence card from that many recent lines, or `Esc` to cancel | +| `Ctrl+Shift+V` | Cycle secondary subtitle display mode (hidden → visible → hover) | +| `Ctrl+Shift+A` | Mark the last added Anki card as an audio card (sets IsAudioCard, SentenceAudio, Sentence, Picture) | +| `Ctrl+Shift+O` | Open runtime options palette (session-only live toggles) | +| `Ctrl/Cmd+A` | Append clipboard video path to MPV playlist (fixed, not currently configurable) | + +**Multi-line copy workflow:** + +1. Press `Ctrl+Shift+C` +2. Press a number key (`1-9`) within 3 seconds +3. The specified number of most recent subtitle lines are copied +4. Press `Ctrl+V` to update the last added card with the copied lines + +These shortcuts are only active when the overlay window is visible and automatically disabled when hidden. + +### Session Help Modal + +The session help modal is opened with `Y-H` by default (falls back to `Y-K` if needed) and shows the current session keybindings and color legend. + +You can filter the modal quickly with `/`: + +- Type any part of the action name or shortcut in the search bar. +- Search is case-insensitive and ignores spaces/punctuation (`+`, `-`, `_`, `/`) so `ctrl w`, `ctrl+w`, and `ctrl+s` all match. +- Results are filtered across active MPV shortcuts, configured overlay shortcuts, and color legend items. + +While the modal is open: + +- `Esc`: close the modal (or clear the filter when text is entered) +- `↑/↓`, `j/k`: move selection +- Mouse/trackpad: click to select and activate rows + +The list is generated at runtime from: + +- Your active mpv keybindings (`keybindings`). +- Your configured overlay shortcuts (`shortcuts`, including runtime-loaded config values). +- Current subtitle color settings from `subtitleStyle`. + +When config hot-reload updates shortcut/keybinding/style values, close and reopen the help modal to refresh the displayed entries. + +### Runtime Option Palette + +Use the runtime options palette to toggle settings live while SubMiner is running. These changes are session-only and reset on restart. + +Current runtime options: + +- `ankiConnect.behavior.autoUpdateNewCards` (`On` / `Off`) +- `ankiConnect.nPlusOne.highlightEnabled` (`On` / `Off`) +- `subtitleStyle.enableJlpt` (`On` / `Off`) +- `subtitleStyle.frequencyDictionary.enabled` (`On` / `Off`) +- `ankiConnect.nPlusOne.matchMode` (`headword` / `surface`) +- `ankiConnect.isKiku.fieldGrouping` (`auto` / `manual` / `disabled`) + +Annotation toggles (`nPlusOne`, `enableJlpt`, `frequencyDictionary.enabled`) only apply to new subtitle lines after the toggle. The currently displayed line is not re-tokenized in place. + +Default shortcut: `Ctrl+Shift+O` + +Palette controls: + +- `Arrow Up/Down`: select option +- `Arrow Left/Right`: change selected value +- `Enter`: apply selected value +- `Esc`: close + +## Anki Integration + ### AnkiConnect Enable automatic Anki card creation and updates with media generation: @@ -281,84 +720,27 @@ To refresh roughly once per day, set: Open demo in a new tab -**Image Quality Notes:** +## External Integrations -- `imageQuality` affects JPG and WebP only; PNG is lossless and ignores this setting -- JPG quality is mapped to FFmpeg's scale (2-31, lower = better) -- WebP quality uses FFmpeg's native 0-100 scale +### Jimaku -### Manual Card Update Shortcuts - -When `behavior.autoUpdateNewCards` is set to `false`, new cards are detected but not automatically updated. Use these keyboard shortcuts for manual control: - -| Shortcut | Action | -| -------------- | ------------------------------------------------------------------------------------------------------------------ | -| `Ctrl+C` | Copy the current subtitle line to clipboard (preserves line breaks) | -| `Ctrl+Shift+C` | Enter multi-copy mode. Press `1-9` to copy that many recent lines, or `Esc` to cancel. Timeout: 3 seconds | -| `Ctrl+V` | Update the last added Anki card using subtitles from clipboard | -| `Ctrl+G` | Trigger Kiku duplicate field grouping for the last added card (only when `behavior.autoUpdateNewCards` is `false`) | -| `Ctrl+S` | Create a sentence card from the current subtitle line | -| `Ctrl+Shift+S` | Enter multi-mine mode. Press `1-9` to create a sentence card from that many recent lines, or `Esc` to cancel | -| `Ctrl+Shift+V` | Cycle secondary subtitle display mode (hidden → visible → hover) | -| `Ctrl+Shift+A` | Mark the last added Anki card as an audio card (sets IsAudioCard, SentenceAudio, Sentence, Picture) | -| `Ctrl+Shift+O` | Open runtime options palette (session-only live toggles) | -| `Ctrl/Cmd+A` | Append clipboard video path to MPV playlist (fixed, not currently configurable) | - -**Multi-line copy workflow:** - -1. Press `Ctrl+Shift+C` -2. Press a number key (`1-9`) within 3 seconds -3. The specified number of most recent subtitle lines are copied -4. Press `Ctrl+V` to update the last added card with the copied lines - -These shortcuts are only active when the overlay window is visible and automatically disabled when hidden. - -### Session help modal - -The session help modal is opened with `Y-H` by default (falls back to `Y-K` if needed) and shows the current session keybindings and color legend. - -You can filter the modal quickly with `/`: - -- Type any part of the action name or shortcut in the search bar. -- Search is case-insensitive and ignores spaces/punctuation (`+`, `-`, `_`, `/`) so `ctrl w`, `ctrl+w`, and `ctrl+s` all match. -- Results are filtered across active MPV shortcuts, configured overlay shortcuts, and color legend items. - -While the modal is open: - -- `Esc`: close the modal (or clear the filter when text is entered) -- `↑/↓`, `j/k`: move selection -- Mouse/trackpad: click to select and activate rows - -The list is generated at runtime from: - -- Your active mpv keybindings (`keybindings`). -- Your configured overlay shortcuts (`shortcuts`, including runtime-loaded config values). -- Current subtitle color settings from `subtitleStyle`. - -When config hot-reload updates shortcut/keybinding/style values, close and reopen the help modal to refresh the displayed entries. - -### Auto-Start Overlay - -Control whether the overlay automatically becomes visible when it connects to mpv: +Configure Jimaku API access and defaults: ```json { - "auto_start_overlay": false + "jimaku": { + "apiKey": "YOUR_API_KEY", + "apiKeyCommand": "cat ~/.jimaku_key", + "apiBaseUrl": "https://jimaku.cc", + "languagePreference": "ja", + "maxEntryResults": 10 + } } ``` -| Option | Values | Description | -| -------------------- | --------------- | ------------------------------------------------------ | -| `auto_start_overlay` | `true`, `false` | Auto-show overlay on mpv connection (default: `false`) | +Jimaku is rate limited; if you hit a limit, SubMiner will surface the retry delay from the API response. -The mpv plugin controls startup overlay visibility via `auto_start_visible_overlay` in `subminer.conf`. -For wrapper-driven playback, `subminer.conf` can also enable startup pause gating with -`auto_start_pause_until_ready` (requires `auto_start=yes` + `auto_start_visible_overlay=yes`). -Current plugin defaults in `subminer.conf` are: - -- `auto_start=yes` -- `auto_start_visible_overlay=yes` -- `auto_start_pause_until_ready=yes` +Set `openBrowser` to `false` to only print the URL without opening a browser. ### Auto Subtitle Sync @@ -385,35 +767,6 @@ Sync the active subtitle track using `alass` (preferred) or `ffsubsync`: Default trigger is `Ctrl+Alt+S` via `shortcuts.triggerSubsync`. Customize it there, or set it to `null` to disable. -### Subtitle Position Edit - -Subtitle positioning can be adjusted directly in the overlay: - -- `Ctrl/Cmd+Shift+P` toggles position edit mode. -- Use arrow keys to move subtitle text. -- Press `Enter` or `Ctrl/Cmd+S` to save, or `Esc` to cancel. -- This edit-mode shortcut is fixed (not currently configurable in `shortcuts`/`keybindings`). - -### Jimaku - -Configure Jimaku API access and defaults: - -```json -{ - "jimaku": { - "apiKey": "YOUR_API_KEY", - "apiKeyCommand": "cat ~/.jimaku_key", - "apiBaseUrl": "https://jimaku.cc", - "languagePreference": "ja", - "maxEntryResults": 10 - } -} -``` - -Jimaku is rate limited; if you hit a limit, SubMiner will surface the retry delay from the API response. - -Set `openBrowser` to `false` to only print the URL without opening a browser. - ### AniList AniList integration is opt-in and disabled by default. Enable it to allow SubMiner to update watched episode progress after playback. @@ -561,322 +914,6 @@ Troubleshooting: - If images do not render, confirm asset keys exactly match uploaded Discord asset names. - If Discord is closed/not installed/disconnects, SubMiner continues running and quietly skips presence updates. -### Keybindings - -Add a `keybindings` array to configure keyboard shortcuts that send commands to mpv: - -See `config.example.jsonc` for detailed configuration options and more examples. - -**Default keybindings:** - -| Key | Command | Description | -| ----------------- | ---------------------------- | ------------------------------------- | -| `Space` | `["cycle", "pause"]` | Toggle pause | -| `KeyJ` | `["cycle", "sid"]` | Cycle primary subtitle track | -| `Shift+KeyJ` | `["cycle", "secondary-sid"]` | Cycle secondary subtitle track | -| `ArrowRight` | `["seek", 5]` | Seek forward 5 seconds | -| `ArrowLeft` | `["seek", -5]` | Seek backward 5 seconds | -| `ArrowUp` | `["seek", 60]` | Seek forward 60 seconds | -| `ArrowDown` | `["seek", -60]` | Seek backward 60 seconds | -| `Shift+KeyH` | `["sub-seek", -1]` | Jump to previous subtitle | -| `Shift+KeyL` | `["sub-seek", 1]` | Jump to next subtitle | -| `Ctrl+Shift+KeyH` | `["__replay-subtitle"]` | Replay current subtitle, pause at end | -| `Ctrl+Shift+KeyL` | `["__play-next-subtitle"]` | Play next subtitle, pause at end | -| `KeyQ` | `["quit"]` | Quit mpv | -| `Ctrl+KeyW` | `["quit"]` | Quit mpv | - -**Custom keybindings example:** - -```json -{ - "keybindings": [ - { "key": "ArrowRight", "command": ["seek", 5] }, - { "key": "ArrowLeft", "command": ["seek", -5] }, - { "key": "Shift+ArrowRight", "command": ["seek", 30] }, - { "key": "KeyR", "command": ["script-binding", "immersive/auto-replay"] }, - { "key": "KeyA", "command": ["script-message", "ankiconnect-add-note"] } - ] -} -``` - -**Key format:** Use `KeyboardEvent.code` values (`Space`, `ArrowRight`, `KeyR`, etc.) with optional modifiers (`Ctrl+`, `Alt+`, `Shift+`, `Meta+`). - -**Disable a default binding:** Set command to `null`: - -```json -{ "key": "Space", "command": null } -``` - -**Special commands:** Commands prefixed with `__` are handled internally by the overlay rather than sent to mpv. `__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.) - -For subtitle-position and subtitle-track proxy commands (`sub-pos`, `sid`, `secondary-sid`), SubMiner also shows an mpv OSD notification after the command runs. - -**See `config.example.jsonc`** for more keybinding examples and configuration options. - -### Runtime Option Palette - -Use the runtime options palette to toggle settings live while SubMiner is running. These changes are session-only and reset on restart. - -Current runtime options: - -- `ankiConnect.behavior.autoUpdateNewCards` (`On` / `Off`) -- `ankiConnect.nPlusOne.highlightEnabled` (`On` / `Off`) -- `subtitleStyle.enableJlpt` (`On` / `Off`) -- `subtitleStyle.frequencyDictionary.enabled` (`On` / `Off`) -- `ankiConnect.nPlusOne.matchMode` (`headword` / `surface`) -- `ankiConnect.isKiku.fieldGrouping` (`auto` / `manual` / `disabled`) - -Annotation toggles (`nPlusOne`, `enableJlpt`, `frequencyDictionary.enabled`) only apply to new subtitle lines after the toggle. The currently displayed line is not re-tokenized in place. - -Default shortcut: `Ctrl+Shift+O` - -Palette controls: - -- `Arrow Up/Down`: select option -- `Arrow Left/Right`: change selected value -- `Enter`: apply selected value -- `Esc`: close - -### Secondary Subtitles - -Display a second subtitle track (e.g., English alongside Japanese) in the overlay: - -See `config.example.jsonc` for detailed configuration options. - -```json -{ - "secondarySub": { - "secondarySubLanguages": ["eng", "en"], - "autoLoadSecondarySub": true, - "defaultMode": "hover" - } -} -``` - -| Option | Values | Description | -| ----------------------- | ---------------------------------- | ------------------------------------------------------ | -| `secondarySubLanguages` | string[] | Language codes to auto-load (e.g., `["eng", "en"]`) | -| `autoLoadSecondarySub` | `true`, `false` | Auto-detect and load matching secondary subtitle track | -| `defaultMode` | `"hidden"`, `"visible"`, `"hover"` | Initial display mode (default: `"hover"`) | - -**Display modes:** - -- **hidden** — Secondary subtitles not shown -- **visible** — Always visible at top of overlay -- **hover** — Only visible when hovering over the subtitle area (default) - -**See `config.example.jsonc`** for additional secondary subtitle configuration options. - -### Shortcuts Configuration - -Customize or disable the overlay keyboard shortcuts: - -See `config.example.jsonc` for detailed configuration options. - -```json -{ - "shortcuts": { - "toggleVisibleOverlayGlobal": "Alt+Shift+O", - "copySubtitle": "CommandOrControl+C", - "copySubtitleMultiple": "CommandOrControl+Shift+C", - "updateLastCardFromClipboard": "CommandOrControl+V", - "triggerFieldGrouping": "CommandOrControl+G", - "triggerSubsync": "Ctrl+Alt+S", - "mineSentence": "CommandOrControl+S", - "mineSentenceMultiple": "CommandOrControl+Shift+S", - "markAudioCard": "CommandOrControl+Shift+A", - "openRuntimeOptions": "CommandOrControl+Shift+O", - "openJimaku": "Ctrl+Shift+J", - "multiCopyTimeoutMs": 3000 - } -} -``` - -| Option | Values | Description | -| ----------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| `toggleVisibleOverlayGlobal` | string \| `null` | Global accelerator for toggling visible subtitle overlay (default: `"Alt+Shift+O"`) | -| `copySubtitle` | string \| `null` | Accelerator for copying current subtitle (default: `"CommandOrControl+C"`) | -| `copySubtitleMultiple` | string \| `null` | Accelerator for multi-copy mode (default: `"CommandOrControl+Shift+C"`) | -| `updateLastCardFromClipboard` | string \| `null` | Accelerator for updating card from clipboard (default: `"CommandOrControl+V"`) | -| `triggerFieldGrouping` | string \| `null` | Accelerator for Kiku field grouping on last card (default: `"CommandOrControl+G"`; only active when `behavior.autoUpdateNewCards` is `false`) | -| `triggerSubsync` | string \| `null` | Accelerator for running Subsync (default: `"Ctrl+Alt+S"`) | -| `mineSentence` | string \| `null` | Accelerator for creating sentence card from current subtitle (default: `"CommandOrControl+S"`) | -| `mineSentenceMultiple` | string \| `null` | Accelerator for multi-mine sentence card mode (default: `"CommandOrControl+Shift+S"`) | -| `multiCopyTimeoutMs` | number | Timeout in ms for multi-copy/mine digit input (default: `3000`) | -| `toggleSecondarySub` | string \| `null` | Accelerator for cycling secondary subtitle mode (default: `"CommandOrControl+Shift+V"`) | -| `markAudioCard` | string \| `null` | Accelerator for marking last card as audio card (default: `"CommandOrControl+Shift+A"`) | -| `openRuntimeOptions` | string \| `null` | Opens runtime options palette for live session-only toggles (default: `"CommandOrControl+Shift+O"`) | -| `openJimaku` | string \| `null` | Opens the Jimaku search modal (default: `"Ctrl+Shift+J"`) | - -**See `config.example.jsonc`** for the complete list of shortcut configuration options. - -Set any shortcut to `null` to disable it. - -Feature-dependent shortcuts/keybindings only run when their related integration is enabled. For example, Anki/Kiku shortcuts require `ankiConnect.enabled` (and Kiku-specific behavior where applicable), and Jellyfin remote startup behavior requires Jellyfin to be enabled. - -### Subtitle Position - -Set the initial vertical subtitle position (measured from the bottom of the screen): - -```json -{ - "subtitlePosition": { - "yPercent": 10 - } -} -``` - -| Option | Values | Description | -| ---------- | ---------------- | ---------------------------------------------------------------------- | -| `yPercent` | number (0 - 100) | Distance from the bottom as a percent of screen height (default: `10`) | - -### Subtitle Style - -Customize the appearance of primary and secondary subtitles: - -See `config.example.jsonc` for detailed configuration options. - -```json -{ - "subtitleStyle": { - "fontFamily": "M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP", - "fontSize": 35, - "fontColor": "#cad3f5", - "fontWeight": "600", - "lineHeight": 1.35, - "letterSpacing": "-0.01em", - "wordSpacing": 0, - "fontKerning": "normal", - "textRendering": "geometricPrecision", - "textShadow": "0 3px 10px rgba(0,0,0,0.69)", - "fontStyle": "normal", - "backgroundColor": "rgb(30, 32, 48, 0.88)", - "backdropFilter": "blur(6px)", - "secondary": { - "fontFamily": "Manrope, Inter", - "fontSize": 24, - "fontColor": "#cad3f5", - "backgroundColor": "transparent" - } - } -} -``` - -| Option | Values | Description | -| ---------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------- | -| `fontFamily` | string | CSS font-family value (default: `"M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP"`) | -| `fontSize` | number (px) | Font size in pixels (default: `35`) | -| `fontColor` | string | Any CSS color value (default: `"#cad3f5"`) | -| `fontWeight` | string | CSS font-weight, e.g. `"bold"`, `"normal"`, `"600"` (default: `"600"`) | -| `fontStyle` | string | `"normal"` or `"italic"` (default: `"normal"`) | -| `backgroundColor` | string | Any CSS color, including `"transparent"` (default: `"rgb(30, 32, 48, 0.88)"`) | -| `enableJlpt` | boolean | Enable JLPT level underline styling (`false` by default) | -| `preserveLineBreaks` | boolean | Preserve line breaks in visible overlay subtitle rendering (`false` by default). Enable to mirror mpv line layout. | -| `autoPauseVideoOnHover` | boolean | Pause playback while mouse hovers subtitle text, then resume on leave (`true` by default). | -| `frequencyDictionary.enabled` | boolean | Enable frequency highlighting from dictionary lookups (`false` by default) | -| `frequencyDictionary.sourcePath` | string | Path to a local frequency dictionary root. Leave empty or omit to use installed/default frequency-dictionary search paths. | -| `frequencyDictionary.topX` | number | Only color tokens whose frequency rank is `<= topX` (`1000` by default) | -| `frequencyDictionary.mode` | string | `"single"` or `"banded"` (`"single"` by default) | -| `frequencyDictionary.matchMode` | string | `"headword"` or `"surface"` (`"headword"` by default) | -| `frequencyDictionary.singleColor` | string | Color used for all highlighted tokens in single mode | -| `frequencyDictionary.bandedColors` | string[] | Array of five hex colors used for ranked bands in banded mode | -| `nPlusOneColor` | string | Existing n+1 highlight color (default: `#c6a0f6`) | -| `knownWordColor` | string | Existing known-word highlight color (default: `#a6da95`) | -| `jlptColors` | object | JLPT level underline colors object (`N1`..`N5`) | -| `secondary` | object | Override any of the above for secondary subtitles (optional) | - -JLPT underlining is powered by offline term-meta bank files at runtime. See [`docs/jlpt-vocab-bundle.md`](jlpt-vocab-bundle.md) for required files, source/version refresh steps, and deterministic fallback behavior. - -Frequency dictionary highlighting uses the same dictionary file format as JLPT bundle lookups (`term_meta_bank_*.json` under discovered dictionary directories). A token is highlighted when it has a positive integer `frequencyRank` (lower is more common) and the rank is within `topX`. - -Lookup behavior: - -- Set `frequencyDictionary.sourcePath` to a directory containing `term_meta_bank_*.json` for a fully custom source. -- If `sourcePath` is missing or empty, SubMiner searches default install/runtime locations for `frequency-dictionary` directories (for example app resources, user data paths, and current working directory). -- In both cases, only terms with a valid `frequencyRank` are used; everything else falls back to no highlighting. -- `frequencyDictionary.matchMode` controls which token text is used for frequency lookups: `headword` (dictionary form) or `surface` (visible subtitle text). -- Frequency highlighting skips tokens that look like non-lexical SFX/interjection noise (for example kana reduplication or short kana endings like `っ`), even when dictionary ranks exist. - -In `single` mode all highlights use `singleColor`; in `banded` mode tokens map to five ascending color bands from most common to least common inside the topX window. - -Secondary subtitle defaults: `fontFamily: "Manrope, Inter"`, `fontSize: 24`, `fontColor: "#cad3f5"`, `backgroundColor: "transparent"`. Any property not set in `secondary` falls back to the CSS defaults. - -**See `config.example.jsonc`** for the complete list of subtitle style configuration options. - -`jlptColors` keys are: - -| Key | Default | Description | -| ---- | --------- | ----------------------- | -| `N1` | `#ed8796` | JLPT N1 underline color | -| `N2` | `#f5a97f` | JLPT N2 underline color | -| `N3` | `#f9e2af` | JLPT N3 underline color | -| `N4` | `#a6e3a1` | JLPT N4 underline color | -| `N5` | `#8aadf4` | JLPT N5 underline color | - -### Texthooker - -Control whether the browser opens automatically when texthooker starts: - -See `config.example.jsonc` for detailed configuration options. - -```json -{ - "texthooker": { - "openBrowser": true - } -} -``` - -### WebSocket Server - -The overlay includes a built-in WebSocket server that broadcasts subtitle text to connected clients (such as texthooker-ui) for external processing. - -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. - -```json -{ - "websocket": { - "enabled": "auto", - "port": 6677 - } -} -``` - -| Option | Values | Description | -| --------- | ------------------------- | -------------------------------------------------------- | -| `enabled` | `true`, `false`, `"auto"` | `"auto"` (default) disables if mpv_websocket is detected | -| `port` | number | WebSocket server port (default: 6677) | - -### Startup Warmups - -Control which startup warmups run in the background versus deferring to first real usage: - -```json -{ - "startupWarmups": { - "lowPowerMode": false, - "mecab": true, - "yomitanExtension": true, - "subtitleDictionaries": true, - "jellyfinRemoteSession": true - } -} -``` - -| Option | Values | Description | -| ----------------------- | --------------- | ------------------------------------------------------------------------------------------------- | -| `lowPowerMode` | `true`, `false` | Defer all warmups except Yomitan extension | -| `mecab` | `true`, `false` | Warm up MeCab tokenizer at startup | -| `yomitanExtension` | `true`, `false` | Warm up Yomitan extension at startup | -| `subtitleDictionaries` | `true`, `false` | Warm up JLPT + frequency dictionaries at startup | -| `jellyfinRemoteSession` | `true`, `false` | Warm up Jellyfin remote session at startup (still requires Jellyfin remote auto-connect settings) | - -Defaults warm everything (`true` for all toggles, `lowPowerMode: false`). Setting a warmup toggle to `false` defers that work until first usage. - ### Immersion Tracking Enable or disable local immersion analytics stored in SQLite for mined subtitles and media sessions: diff --git a/docs/shortcuts.md b/docs/shortcuts.md index 5c4de0f..12a0233 100644 --- a/docs/shortcuts.md +++ b/docs/shortcuts.md @@ -67,18 +67,6 @@ Mouse-hover playback behavior is configured separately from shortcuts: `subtitle | `Ctrl+Shift+J` | Open Jimaku subtitle search modal | `shortcuts.openJimaku` | | `Ctrl+Alt+S` | Open subtitle sync (subsync) modal | `shortcuts.triggerSubsync` | -## Subtitle Position Edit Mode - -Enter edit mode to fine-tune subtitle alignment. - -| Shortcut | Action | -| --------------------- | -------------------------------- | -| `Ctrl/Cmd+Shift+P` | Toggle position edit mode | -| `ArrowKeys` or `hjkl` | Nudge position by 1 px | -| `Shift+Arrow` | Nudge position by 4 px | -| `Enter` or `Ctrl+S` | Save position and exit edit mode | -| `Esc` | Cancel and discard changes | - ## MPV Plugin Chords When the mpv plugin is installed, all commands use a `y` chord prefix — press `y`, then the second key within 1 second. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index bf424dd..a2af017 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -159,7 +159,7 @@ SubMiner positions the overlay by tracking the mpv window. If tracking fails: - Sway: Ensure `swaymsg` is available. - X11: Ensure `xdotool` and `xwininfo` are installed. -If the overlay position is slightly off, use subtitle position edit mode (`Ctrl/Cmd+Shift+P`) to fine-tune the offset with arrow keys, then save with `Enter` or `Ctrl+S`. +If the overlay position is slightly off, right-click and drag on subtitle text to fine-tune the overlay subtitle offset. ## Yomitan @@ -279,5 +279,5 @@ The Jimaku API has rate limits. If you see 429 errors, wait for the retry durati ### macOS - **Accessibility permission**: Required for window tracking. Grant it in System Settings > Privacy & Security > Accessibility. -- **Font rendering**: macOS uses a 0.87x font compensation factor for subtitle alignment between mpv and the overlay. If text alignment looks off, adjust subtitle offset in position edit mode. +- **Font rendering**: macOS uses a 0.87x font compensation factor for subtitle alignment between mpv and the overlay. If text alignment looks off, adjust subtitle offset by right-click dragging subtitle text. - **Gatekeeper**: If macOS blocks SubMiner, right-click the app and select "Open" to bypass the warning, or remove the quarantine attribute: `xattr -d com.apple.quarantine /path/to/SubMiner.app` diff --git a/docs/usage.md b/docs/usage.md index 7b91b8b..44905b5 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -192,10 +192,6 @@ Notes: | `Ctrl+W` | Quit mpv | | `Right-click` | Toggle MPV pause (outside subtitle area) | | `Right-click + drag` | Move subtitle position (on subtitle) | -| `Ctrl/Cmd+Shift+P` | Toggle subtitle position edit mode | -| `Arrow keys` | Move subtitles while edit mode is active | -| `Enter` / `Ctrl+S` | Save subtitle position in edit mode | -| `Esc` | Cancel subtitle position edit mode | | `Ctrl/Cmd+A` | Append clipboard video path to MPV playlist | These keybindings only work when the overlay window has focus. See [Configuration](/configuration) for customization.