You are browsing as a guest. Sign up (or log in) to start making projects!

3h 35m 46s logged

Devlog #2

Last time: a parser pipeline that couldn’t bake anything. Now SubBake actually muxes subtitles into video — and I collected three of the dumbest bugs I’ve ever fought along the way.

The muxer was the easy part

core/muxer.py runs FFmpeg as a subprocess with -progress pipe:1 and reads out_time_us lines off stdout for live progress. Video and audio are stream-copied, so a 24-minute episode remuxes in seconds. The sharp edges were plumbing: stderr needs its own drain thread or the pipe buffer fills and FFmpeg just stops, and the container picks your subtitle codec — MKV copies the track as-is, MP4 demands mov_text, WebM demands webvtt.

One ffmpeg -i later: Stream #0:2(kor): Subtitle: subrip (default), title “Korean”. It works.

SRT ate my colors

Then I actually watched the result. The original fansub had per-speaker colors; my output was all white. Of course — my SMI parser stripped every HTML tag, including <font color="#FF80C0">, and SRT has no styling anyway. So the plan changed: SMI now converts to ASS. The parser keeps font tags, and a new core/ass_converter.py writes a proper ASS document.

Writing the font regex by hand nearly broke me

This regex took longer than the entire muxer:

_FONT_COLOR_RE = re.compile(
    r'<font\s+[^>]*color\s*=\s*["\']?([^"\'>\s]+)["\']?[^>]*>',
    re.IGNORECASE,
)

The job sounds trivial: find opening <font> tags, capture the color. My first draft, <font color="(.*?)">, fell apart immediately — real fansub files write color="Red" (a name, not hex), color=#FF80C0 (no quotes), and <font face="Arial" color="..."> (color isn’t the first attribute). The class [^"\'>\s]+ exists because the value must stop at a quote, a space, or the tag end — and nesting quote escapes inside a Python raw string is its own hell. I built it the way everyone builds regexes: one character class at a time in a tester, pasting real SMI lines until nothing exploded. Nobody writes this in one shot; anyone who claims otherwise is lying.

Then the colors came out wrong — red and blue swapped. ASS stores colors as &HBBGGRR&, a byte order from the 80s, so every RGB value gets flipped first. That cost me a long “why is the pink line blue” stare.

The giant subtitle

First ASS render: billboard-sized subtitles. Fontsize 36 sounds fine until you learn what PlayResX/PlayResY do — ASS declares a design resolution and the renderer scales it to the real video. I designed for 640×360 and played at 1080p: a 3× upscale, 108-pixel letters. Now I declare PlayRes 1920×1080 and the size holds at any resolution. I verified by burning test frames, because I no longer trust my own reasoning about this format.

The mystery box

Last ghost: a hollow rectangle between the pink Japanese line and the Korean translation. Every codepoint in those lines was clean, and re-rendering the same structure in isolation showed nothing. The culprit was the blank line — the SMI source separates the two languages with an empty line, my converter turned it into \N\N, and my player draws an empty subtitle line as a hollow box. Collapsing consecutive newlines fixed it, and as a bonus the output now looks exactly like the original presentation. Sometimes the bug is a blank line. That sentence is going on my gravestone.

Where SubBake stands

  • Muxing works end to end: stream copy, live progress, clean cancel, per-container codecs
  • SMI→ASS with per-speaker colors, verified on a real episode
  • Sane subtitle size at any resolution; phantom box gone
  • Still no GUI — everything runs through python -c one-liners

Next up

The interface: a PySide6 shell with drag-and-drop, a queue table, and worker threads — the point where this becomes an app instead of modules I poke from a terminal. Qt will be perfectly calm and reasonable. (It will not be.)

0
15

Comments 0

No comments yet. Be the first!