Two bugs in the same code path, both reproducible from a clean install following install.md. Happy to send a PR for either.
1. brew install ffmpeg gives you an ffmpeg without libass, so --build-subtitles crashes
install.md says brew install ffmpeg. On current Homebrew (ffmpeg 8.1.2, arm64 macOS) the default bottle is not built with --enable-libass, so the subtitles, ass, and drawtext filters do not exist at all:
$ ffmpeg -hide_banner -version | grep -o enable-libass
(no output)
$ ffmpeg -hide_banner -filters | grep -E ' (subtitles|ass|drawtext) '
(no output)
Any run with --build-subtitles (or an EDL with a subtitles field) dies with an unhandled CalledProcessError:
subprocess.CalledProcessError: Command '['ffmpeg', '-y', '-i', '.../base.mp4',
'-filter_complex', "[0:v]subtitles='.../master.srt':force_style='FontName=Helvetica,...'[outv]",
...]' returned non-zero exit status 234.
The underlying ffmpeg error is a filter-graph parse failure, which is confusing because it looks like a quoting problem rather than a missing filter:
[AVFilterGraph] No option name near '/.../master.srt:force_style=FontName=Helvetica,FontSize=18'
[AVFilterGraph] Error parsing a filter description around: [outv]
Note that master.srt is generated correctly before this — only the burn-in step fails, so everything up to the last stage looks like it worked.
Suggested fix: preflight the filter before rendering, and fail with an actionable message instead of a traceback. Something like:
def has_subtitles_filter() -> bool:
out = subprocess.run(["ffmpeg", "-hide_banner", "-filters"],
capture_output=True, text=True).stdout
return bool(re.search(r"^\s*\S+\s+subtitles\s", out, re.M))
and in install.md, document that the default Homebrew bottle lacks libass. Working options on macOS are brew install ffmpeg-full (bottled, has libass — but it is keg-only, so /opt/homebrew/opt/ffmpeg-full/bin has to come first on PATH for helpers to pick it up), or building ffmpeg from source against libass.
2. SUB_FORCE_STYLE hardcodes FontName=Helvetica, so any non-Latin script renders as tofu
render.py:51:
SUB_FORCE_STYLE = (
"FontName=Helvetica,FontSize=18,Bold=1,"
...
)
Because this goes through force_style, it overrides whatever the subtitle file specifies, and libass does no glyph fallback here. With a Chinese transcript the cues render as empty boxes.
This is worth fixing because it is an internal inconsistency rather than a niche gap: Scribe transcribes 90+ languages, and the README lists travel and interview footage as target use cases, but the subtitle layer only renders Latin script. Anyone editing CJK, Thai, Arabic, Hebrew, or Devanagari footage gets a clean transcript and unreadable subtitles.
Suggested fix: let the EDL override the style, e.g. an optional subtitle_style or subtitle_font key merged over the default, so the skill can pick a font per project without patching the module constant. A sensible default on macOS would be a font with wide coverage that is present out of the box.
One gotcha worth documenting if you do add font selection: on macOS, PingFang SC is not resolvable through fontconfig (fc-match "PingFang SC" silently returns Verdana), so it still renders tofu. Heiti SC resolves to /System/Library/Fonts/STHeiti Medium.ttc and works. Arial Unicode MS also has broad coverage.
For reference, this is the only change I needed — same geometry, font swapped:
force_style='FontName=Heiti SC,FontSize=18,Bold=1,PrimaryColour=&H00FFFFFF,
OutlineColour=&H00000000,BorderStyle=1,Outline=2,Shadow=0,Alignment=2,MarginV=90'
FontSize=18 and MarginV=90 are correct as-is, by the way — libass resolves them against its default script resolution, which lands the baseline right about where the comment above the constant says it should.
Minor, related: the caption chunker assumes word-delimited Latin text
build_master_srt groups into 2-word chunks and calls .upper() (render.py:370). For CJK, .upper() is a no-op, and 2-token chunks cut across utterance boundaries, so unrelated phrases get merged into one cue. On a 31s clip with 47 word tokens I got 24 cues, several of which joined two separate utterances.
Grouping on inter-word gaps instead (plus a per-cue character cap) gave 14 cues that matched what was actually said. Probably worth gating the chunking rule on language_code rather than changing the English default.
Environment
- macOS arm64 (Darwin 27.0), Homebrew ffmpeg 8.1.2 (default bottle), Python 3.12
- Source: 720x1280 H.265 portrait clip, Chinese speech
Two bugs in the same code path, both reproducible from a clean install following
install.md. Happy to send a PR for either.1.
brew install ffmpeggives you an ffmpeg without libass, so--build-subtitlescrashesinstall.mdsaysbrew install ffmpeg. On current Homebrew (ffmpeg 8.1.2, arm64 macOS) the default bottle is not built with--enable-libass, so thesubtitles,ass, anddrawtextfilters do not exist at all:Any run with
--build-subtitles(or an EDL with asubtitlesfield) dies with an unhandledCalledProcessError:The underlying ffmpeg error is a filter-graph parse failure, which is confusing because it looks like a quoting problem rather than a missing filter:
Note that
master.srtis generated correctly before this — only the burn-in step fails, so everything up to the last stage looks like it worked.Suggested fix: preflight the filter before rendering, and fail with an actionable message instead of a traceback. Something like:
and in
install.md, document that the default Homebrew bottle lacks libass. Working options on macOS arebrew install ffmpeg-full(bottled, has libass — but it is keg-only, so/opt/homebrew/opt/ffmpeg-full/binhas to come first onPATHfor helpers to pick it up), or buildingffmpegfrom source againstlibass.2.
SUB_FORCE_STYLEhardcodesFontName=Helvetica, so any non-Latin script renders as tofurender.py:51:Because this goes through
force_style, it overrides whatever the subtitle file specifies, and libass does no glyph fallback here. With a Chinese transcript the cues render as empty boxes.This is worth fixing because it is an internal inconsistency rather than a niche gap: Scribe transcribes 90+ languages, and the README lists travel and interview footage as target use cases, but the subtitle layer only renders Latin script. Anyone editing CJK, Thai, Arabic, Hebrew, or Devanagari footage gets a clean transcript and unreadable subtitles.
Suggested fix: let the EDL override the style, e.g. an optional
subtitle_styleorsubtitle_fontkey merged over the default, so the skill can pick a font per project without patching the module constant. A sensible default on macOS would be a font with wide coverage that is present out of the box.One gotcha worth documenting if you do add font selection: on macOS,
PingFang SCis not resolvable through fontconfig (fc-match "PingFang SC"silently returns Verdana), so it still renders tofu.Heiti SCresolves to/System/Library/Fonts/STHeiti Medium.ttcand works.Arial Unicode MSalso has broad coverage.For reference, this is the only change I needed — same geometry, font swapped:
FontSize=18andMarginV=90are correct as-is, by the way — libass resolves them against its default script resolution, which lands the baseline right about where the comment above the constant says it should.Minor, related: the caption chunker assumes word-delimited Latin text
build_master_srtgroups into 2-word chunks and calls.upper()(render.py:370). For CJK,.upper()is a no-op, and 2-token chunks cut across utterance boundaries, so unrelated phrases get merged into one cue. On a 31s clip with 47 word tokens I got 24 cues, several of which joined two separate utterances.Grouping on inter-word gaps instead (plus a per-cue character cap) gave 14 cues that matched what was actually said. Probably worth gating the chunking rule on
language_coderather than changing the English default.Environment