Skip to content

Configuration

Saitenka reads its settings from a single TOML file, overlay.toml. The shipped overlay.example.toml is the exhaustive, self-documenting reference: every recognized key appears there at its built-in default, commented, sshd_config style. This page is the curated tour of the handful of settings most people actually touch — what each does and why you'd change it. For the full list, read the example file.

Where the config lives

overlay.toml sits in your platform config directory (~/.config/saitenka/ on Linux, ~/Library/Application Support/saitenka/ on macOS, %APPDATA%\saitenka\ on Windows). You never have to guess the exact path:

$ saitenka doctor

prints it, along with mpv/ffmpeg, the dictionary cache, fonts, and AnkiConnect status. Point elsewhere with --config <path> or the $SAITENKA_CONFIG environment variable.

Precedence

Values resolve built-in defaults → config file → explicit CLI flag (Saitenka loads the TOML via cyclopts' Toml config). A flag like --no-mine always wins over the file for that one run; the file always wins over the shipped defaults. Anything you don't set stays at its default — you only write the keys you want to change.

Coloring known vs. new words

Saitenka tints each word by how well you know it, using FSRS maturity state pulled from Anki. Two tables shape that.

[palette] sets the band colors. Change them to match your theme or to widen the visual gap between maturity levels:

[palette]
learning = "#eed49f"   # words still in learning
young    = "#8bd5ca"   # young (not yet mature) cards

[fsrs] optionally reads maturity from a copy of your Anki collection so coloring reflects real review state. Set this when you want per-card maturity rather than a plain known/unknown split — and point it at a copy, never the live collection.anki2:

[fsrs]
collection = "~/anki-copies/collection-copy.anki2"

Tip

The known-word source itself lives in [known] — a map of Anki decks to the fields that hold the expression. The default is "Saitenka::Known" = ["Expression"]; change it to match the deck and field where your known vocabulary already lives.

Mining to Anki

[mine] is the one-key card-maker. Mining is on whenever this table is present; Ctrl+M mines the hovered word into the configured deck. Edit it to target your own deck and note type, and to map onto your note's fields:

[mine]
enabled = true
deck  = "Saitenka::Mining"   # destination deck
model = "Lapis"              # Anki note type / model (must already exist)
preset = "Lapis"             # "Lapis" or "Kiku" — fills the field map + card_kind for a known type
card_kind = "word-and-sentence"  # card template marked: word-and-sentence · sentence · audio · click · none
key       = "Ctrl+m"         # mine the hovered word
video_key = "Ctrl+Shift+m"   # mine the hovered word with an animated (motion) clip
all_key   = "Shift+m"        # bulk-mine the current line
preview_key = "p"            # replay the last card preview
preview = true               # auto-pop the card-preview panel after a mine (--no-mine-preview off)
normalize_audio = false      # −23 LUFS loudnorm on the mined clip (off by default)
animated_screenshot = false  # capture a motion clip as the card image instead of a still (off by default)
animated_format = "webp"     # "webp" (prefer WebP → GIF fallback) or "gif" (force universal GIF)
animated_height = 480        # clip height in px — the main quality↔storage lever
animated_fps = 12            # clip frame rate
animated_quality = 75        # WebP quality, 0–100 (ignored for GIF)
animated_max_secs = 4.0      # cap clip length so a long cue can't produce a huge file

Set enabled = false to keep the config but turn mining off (the --no-mine flag does the same for a single run). preview = false (or --no-mine-preview) mines silently — a toast confirms instead of the verify panel popping up; mining and the ⊕→✓ flip are unaffected. normalize_audio runs an EBU R128 loudnorm pass (−23 LUFS) so cards mined from quiet and loud lines play back at an even volume; it adds one ffmpeg pass per mine, so it's off by default.

Note type, card kind, and field map. model is your Anki note type — it must already exist (a deck is auto-created on the first mine, a note type can't be). preset ("Lapis" or "Kiku") fills the field map and card kind for those known types, so you don't spell them out; the two share field names, differing only in card template. card_kind picks which template the card is marked as — word-and-sentence (the Lapis/Kiku default), sentence (Saitenka's historical marker), audio, click, or none — setting exactly one Is…Card flag. For any other note type, override the logical→real field map in a [mine.fields] sub-table (only mapped fields are written):

[mine.fields]              # e.g. an Animecards note type
expression = "Word"
reading    = "Reading"

saitenka doctor validates the effective field map against the note type and warns about names it lacks (those values would silently go nowhere); unknown fields are also dropped at mine time so the note still adds.

Field templates ([mine.card_format]). For full control — Yomitan's model — give each note field a template of {marker} tokens instead of a single entity. When present it wins wholesale over [mine.fields] (only the fields you list here are written; the entity map is ignored), so one field can combine markers and one marker can fill several fields:

[mine.card_format]
Word     = "{expression}"
Reading  = "{furigana}"
Sentence = "{cloze-prefix}<b>{cloze-body}</b>{cloze-suffix}"
Pitch    = "{pitch-accents}"
Image    = "{screenshot}"

All markers are filled from real data — nothing fabricated. saitenka doctor warns about a {marker} Saitenka can't fill (it would render empty) and about a field name the note type lacks.

Marker Fills with
{expression} the mined headword (dictionary form)
{reading} kana reading
{furigana} Anki ruby form, 漢字[かんじ]
{glossary} definitions, HTML
{glossary-plain} definitions, plain text (;-joined)
{sentence} the example sentence, mined surface bolded
{cloze-prefix} sentence text before the surface
{cloze-body} the mined surface
{cloze-suffix} sentence text after the surface
{screenshot} scene image, <img>-wrapped
{sentence-audio} clipped line audio, [sound:]-wrapped
{frequencies} frequency pills, HTML
{frequency-rank} numeric frequency rank
{pitch-accents} pitch-accent notation, HTML
{pitch-accent-positions} downstep position number(s)
{part-of-speech} part of speech (English)
{document-title} video / source title
{misc} source · episode · timestamp line
{ent-seq} JMdict entry id
{tags} note tags, space-joined

Not yet supported — named so a blank field isn't a surprise:

Marker Would fill with
{audio} word audio — needs #93 (sentence audio only for now)
{pitch-accent-graphs} SVG pitch-accent graph
{sentence-furigana} whole-sentence furigana (every token)
{furigana-plain} reading as plain furigana (no ruby brackets)

Animated (motion) screenshots. animated_screenshot = true captures a short animated clip of the scene as the card image instead of a still frame — the scene reads better in motion and pairs with the cue audio you already clip. It's opt-in (larger media + an extra ffmpeg pass). It prefers WebP (small and sharp) and automatically falls back to an animated GIF where your ffmpeg lacks libwebp (Homebrew's ffmpeg and the Windows "essentials" build often do) — GIF's encoder is native to every ffmpeg, so a clip is produced out of the box on macOS, Linux, and Windows; saitenka doctor reports which. Force GIF with animated_format = "gif". animated_height (plus animated_fps/animated_quality/animated_max_secs) trade clip quality against file size. You don't have to turn it on globally: Ctrl+Shift+M (video_key) mines the hovered word with a clip for that one card. If your AnkiConnect endpoint isn't the stock 127.0.0.1:8765, override it in the separate [anki] table.

Anki starts itself

When mining (or Anki-backed coloring) is on, Saitenka launches Anki for you if it's closed.

Tooltip

[tooltip] geometry and feel actually live as top-level keys (above the first [table] header). The ones worth touching:

  • dicts / freq / pitch — the dictionaries shown, by title (not path), in tooltip order. These are the keys you edit most: list the definition, frequency, and pitch-accent dictionaries you imported. saitenka import fills them in; saitenka doctor shows what's available.
  • ui_scale (default 1.0) — overall size of help, sidebar, and analysis; raise it on a high-DPI display.
  • tip_height (default 0.4) — max tooltip height as a fraction of the video, if the default crops long entries.
  • pause_on_tooltip (default true) — freeze the frame while a tooltip is open (the mining default); set false if you'd rather keep playing.
dicts = ["Bilingual Dict", "Monolingual Dict A"]
freq  = ["Frequency General"]
pitch = ["Pitch Accent"]
ui_scale = 1.0

jimaku

[jimaku] fetches Japanese subtitles from jimaku.cc when a file has none embedded. It's safe to leave enabled — attach prefers an embedded JP track and only falls back to jimaku on a miss:

[jimaku]
enabled = true
resync  = true   # auto-align fetched subs to the video (alass/ffsubsync)

The API key does not belong in this file in plaintext. Store it in your OS keychain instead:

$ saitenka set-jimaku-key <your-key>

Key resolution order

The [jimaku] key value → $JIMAKU_API_KEY → OS keyring. set-jimaku-key writes to the keyring (or an owner-only jimaku.key beside the config on Linux without the linux-keyring extra), so a GUI-launched mpv — which can't inherit a shell-only variable — can still read it.

Rebinding keys

Most in-player shortcuts are configurable — translate_key, sub_next_key, the [mine] keys, and more take any mpv key name. Rather than list them here, see the Keyboard shortcuts page, which has the full rebindable-key table with defaults.

The full reference

Everything is in the example file

Every recognized key, at its default, with an inline comment, is in the commented overlay.example.toml. Uncomment a line only to change it. This page covers the common cases; that file is the source of truth for prefetch tuning, mpv coexistence, telemetry, stats, [dictdb], and the rest.

For getting the file in place at all, see Install.