Repository navigation
v0.5.0 — Embedded fonts
MKV embedded-font extraction release. When the source is an MKV with attached fonts — standard practice for anime fansubs that ship \fn(CustomFont) typesetting in their ASS scripts — MoeKoder now extracts every font attachment into a per-job temp dir and feeds the path to libass via the subtitles=...:fontsdir= option. Burned output finally renders typeset cues with the fonts the author intended instead of silently falling back to Arial.
Headline feature: drop in an MKV with attached fonts, hit burn, get the typesetting the release group actually shipped. No system-font install dance, no manual font extraction, no Arial fallback surprise.
Install
- Windows —
Moekoder-0.5.0-win-x64.exe. Still unsigned; SmartScreen will show More info → Run anyway on first launch. - macOS —
Moekoder-0.5.0-mac-arm64.dmg. Still unsigned; after dragging into/Applications, run:to clear the Gatekeeper quarantine.xattr -cr /Applications/Moekoder.app
- Linux — not shipped this release. Still on the roadmap.
If you're upgrading from v0.4.0, the auto-updater on Windows pulls this release in the background; the next launch comes up on v0.5.0. macOS users grab the DMG manually until code-signing lands.
MKV attachment extraction
- New
font-extractormodule runsffmpeg -dump_attachment:t '' -i <video>against a per-jobos.tmpdir()/mkfont-*directory, accepts the well-known exit-code-1-on-success quirk, filters the dump to font extensions (.ttf .otf .ttc .woff .woff2) plus mime hints, and returns the dir + basenames. - Returns
nullwhen there are no font-shaped attachments to dump (cover-art-only MKVs short-circuit before invoking ffmpeg). - The orchestrator probes attachments before encode, calls
extractFonts(...), threads the resulting dir ontoEncodeJob.fontsDir, and both terminal callbacks (onCompleteandonError/ CANCELLED) runcleanupFontsDir(...)so the temp dir is removed whether the encode succeeded, errored, or was user-cancelled.
libass fontsdir= wiring
buildFilterChainlearns an optionalfontsDirargument and emitssubtitles='<sub>':fontsdir='<dir>'ahead of the codec-aware pixel-format filter when set.- v0.4 byte-for-byte regression locked — no
fontsDir⇒ the exact same arg array as v0.4. escapeSubtitlePathis nowescapeLibassPath— same helper handles bothsubtitles=andfontsdir=arguments. The old name is kept as a@deprecatedalias for one release.
Settings → Embedded fonts
- New section in
apps/web/src/screens/Settings.tsx("字 · ji"), wired to the newuseEmbeddedFontssetting key. - Defaults to on so anime fansubs render correctly out of the box; flip off to restore v0.4 behaviour (libass falls back to system fonts only).
Missing-font diagnostic
- After a successful extraction the orchestrator regex-scans the ASS subtitle for
\fn(Name)overrides and emits awarnper reference not present among the extracted basenames (case-insensitive, stem-matched). - Surfaces in the same job-log channel that feeds both the Single-route log panel and the queue card's expand drawer.
- Matches both libass forms — bare (
\fnFontName) and parenthesized (\fn(FontName)).
Fixed
- Font tempdir cleanup on every failure path — the extractor now wraps the ffmpeg spawn + readdir in try/catch so spawn errors, non-0/1 exit codes, signal kills, readdir throws, and zero-files-produced all remove the temp dir before propagating. Previously a mid-extraction failure could leave
mkfont-*directories behind inos.tmpdir(). - Signal-killed ffmpeg surfaces a distinct error — null exit code (process killed by signal) is now distinguished from a non-zero code so failures don't masquerade as "exit code null" in the job log.
fontStemswitched topath.parse(...).name— replaces the regex-based extension strip; correctly handles compound names likeBauhaus 93.ttfso the missing-font diagnostic matches the right filename.
Changed
EncodeJobgainsfontsDir?: string— opt-in field; callers that don't set it produce the v0.4 filter chain byte-for-byte.OrchestratorDepsseams —probeAttachments,extractFonts,cleanupFontsDir,getUseEmbeddedFonts, andreadSubtitleFileare now injectable so every branch (toggle on/off, attachments yes/no, extractor success/failure, missing-font diagnostic, cleanup-on-cancel) runs in vitest without spawning ffmpeg or touching disk.
Tests
39 new vitest tests — 2 in probe.test.ts (multi-font fixture + missing-tag fallback), 3 in path-escape.test.ts (fontsdir-style inputs + alias), 4 in args.test.ts (fontsdir token + v0.4 regression lock + NVENC pixel-format ordering + libx264 path), 21 in font-extractor.test.ts (filter + extract + cleanup + \fn scan + diff), and 9 in orchestrator.test.ts (toggle off, no attachments, extract + cleanup on success / cancel / error, soft-fail on extractor throw, missing-font warn, subtitle-read failure tolerated). Test count: 229 (was 190).
Known limitations
- The missing-font diagnostic is best-effort string matching on the ASS file (
\fn(Name)overrides, stem-matched against extracted basenames). It does not enumerate system fonts and cannot distinguish "missing on disk" from "present elsewhere on the system" — every warning reads as "may fall back to system default". Full system-font enumeration is deferred to v0.6. - Soft-subbed mux (no burn-in, font attachments preserved in the output MKV) is still v0.6 territory.
- Embedded-font extraction runs per-job. A queue of 24 episodes from the same release will re-extract identical fonts 24 times — a
<userData>/fonts/<sourceHash>/cache is on the v0.6 roadmap. - Same as v0.4.0: binaries are unsigned, auto-updater is Windows-only, no Linux build.
Source under the Moekoder Source Available License.