Skip to content

v0.5.0 — Embedded fonts

Choose a tag to compare

@Shironex Shironex released this 17 May 20:25
· 78 commits to main since this release

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:
    xattr -cr /Applications/Moekoder.app
    to clear the Gatekeeper quarantine.
  • 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-extractor module runs ffmpeg -dump_attachment:t '' -i <video> against a per-job os.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 null when 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 onto EncodeJob.fontsDir, and both terminal callbacks (onComplete and onError / CANCELLED) run cleanupFontsDir(...) so the temp dir is removed whether the encode succeeded, errored, or was user-cancelled.

libass fontsdir= wiring

  • buildFilterChain learns an optional fontsDir argument and emits subtitles='<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.
  • escapeSubtitlePath is now escapeLibassPath — same helper handles both subtitles= and fontsdir= arguments. The old name is kept as a @deprecated alias for one release.

Settings → Embedded fonts

  • New section in apps/web/src/screens/Settings.tsx ("字 · ji"), wired to the new useEmbeddedFonts setting 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 a warn per 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 in os.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.
  • fontStem switched to path.parse(...).name — replaces the regex-based extension strip; correctly handles compound names like Bauhaus 93.ttf so the missing-font diagnostic matches the right filename.

Changed

  • EncodeJob gains fontsDir?: string — opt-in field; callers that don't set it produce the v0.4 filter chain byte-for-byte.
  • OrchestratorDeps seams — probeAttachments, extractFonts, cleanupFontsDir, getUseEmbeddedFonts, and readSubtitleFile are 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.