Skip to content

fix(ffmpeg): escape filter and concat paths for FFmpeg's parsers - #43

Merged
yultyyev merged 10 commits into
mainfrom
fix/ffmpeg-path-escaping
Oct 29, 2026
Merged

yultyyev merged 10 commits into
mainfrom
fix/ffmpeg-path-escaping

Conversation

@yultyyev

@yultyyev yultyyev commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

What

Fixes how file paths and other values are escaped inside FFmpeg filtergraphs and concat lists. This builds on #40, by @eliasruntime, which is merged into this branch. Thanks for finding the Windows drive-colon bug and the concat quoting split.

The bug. FFmpeg reads a filter option value twice, and each read (av_get_token) strips one layer of quoting:

  1. The filtergraph parser splits on [],;.
  2. The filter's option parser splits on :.

The old quoteFilterArg handled only one layer, so:

  • a Windows path (C:\…) split at the drive colon, and add_text failed with "Both text and text file provided";
  • an apostrophe in a path broke the quoting;
  • a value like x',drawtext=… could close its quote and add a filter.

Concat lists reused the same quoting even though the concat demuxer reads its list once, so apostrophes and backslashes in clip paths broke.

The fix (src/ffmpeg/escape.ts):

  • quoteFilterArg escapes \ ' : for the option parser, then single-quotes the result for the graph parser.
  • On top of fix(ffmpeg): preserve paths through filter and concat parsers #40, it also escapes leading and trailing whitespace, which the option parser otherwise trims. With fix(ffmpeg): preserve paths through filter and concat parsers #40's version, a file name ending in a space isn't found; verified against the real FFmpeg binary.
  • quoteConcatPath does single-layer quoting for concat lists. A path with a line break or NUL throws ConcatPathError naming the path.
  • quoteFilterArg is exported, and its output changes for values containing :, ' or edge whitespace. Plain values stay readable ('/tmp/x.txt'), so existing assertions like fontcolor='white' are unchanged.

Tests:

  • tests/ffmpeg-parse.ts ports av_get_token, the graph-then-option parse, and the concat-line parse.
  • The unit tests read escaped output back through that port instead of comparing strings. 21 adversarial filter values must come back unchanged with no extra options and nothing left unread; 7 concat paths must round-trip.
  • This replaces fix(ffmpeg): preserve paths through filter and concat parsers #40's literal-string tests, including the compile test that built its expected value with quoteFilterArg itself.
  • tests/integration/filter-escape.test.ts runs awkward file names through the real FFmpeg binary. It's CI-only (skips unless CI=true), like fix(ffmpeg): preserve paths through filter and concat parsers #40's version.
    • The paths go through the movie filter, which every FFmpeg build has and which reads its path through the same two parse steps.
    • They also go through drawtext, but only where the binary has it. The Linux ffmpeg-static binary in CI has no libharfbuzz, which drawtext needs since FFmpeg 6.1, so it has no drawtext.
    • fix(ffmpeg): fail fast when ffmpeg lacks the drawtext filter #44, merged into this branch, makes text tools on Linux stop with MissingFilterError and its fix (MAKEMYCLIP_FFMPEG_PATH) instead of FFmpeg's No such filter. Swapping in a Linux build with drawtext was decided against for now; see docs/backlog/drawtext-on-linux.md.
  • AGENTS.md and CONTRIBUTING.md mention tests/integration/ again.

Release

Merging this cuts v0.3.1: the fix(ffmpeg) commits trigger a patch release. It also ships #44's drawtext check, plus the dependency bump (#42) and the docs refresh (#41), which merged without releasing. This is the first real npm publish through the bumped release chain (setup-node v7, pnpm 12, semantic-release 25), so watch that run.

Checks

Re-run after #44 merged, on tip fc79352 (same tree as #44's last commit 50d970f), Node 24.15.0 and pnpm 12.16.1:

  • pnpm lint: 0 errors, 7 warnings. pnpm type-check and pnpm build: ok.
  • CI=true pnpm test on macOS (FFmpeg 6.0): 587 passed, 1 skipped (the no-drawtext case).
  • GitHub CI on ubuntu-latest (FFmpeg 7.0.2, no drawtext): 583 passed, 5 skipped (the drawtext cases).
  • npm pack --dry-run: 11 files (LICENSE, README.md, SKILL.md, package.json and 7 in dist/), the same count as 0.3.0.
  • Version: commits since v0.3.0 hold 3 fix(ffmpeg) and no feat or BREAKING CHANGE, so semantic-release cuts 0.3.1.
  • Release chain: the last release run on main (6fe8d53) loaded every plugin and stopped at "no relevant changes". Its runner has Node 24.22.1 and npm 11.19.1; trusted publishing needs npm 11.5.1 or later, and @semantic-release/changelog 7 needs Node 24.15 or later. Prepare and publish haven't run since the bumps.
  • Codex reviewed this PR's diff against main before fix(ffmpeg): fail fast when ffmpeg lacks the drawtext filter #44 (below), and fix(ffmpeg): fail fast when ffmpeg lacks the drawtext filter #44's diff against this branch. Neither review had findings.

Run on e916515, before #44 merged, with Node 24.15.0 and pnpm 12.16.1:

  • Static checks:
    • pnpm lint: 0 errors, 7 warnings (the same 7 as main)
    • pnpm type-check: ok
    • pnpm build: ok
  • Unit tests: pnpm test passes 571, and skips 6, the CI-only integration tests.
  • Real FFmpeg (macOS, FFmpeg 6.0): CI=true vitest run tests/integration passes 11/11:
    • movie and drawtext each read 5 names: plain, O'Brien, [notes]; v1, drive:path, one ending in a space, and back\slash'quote;
    • the concat demuxer reads a list naming O'Brien, [clip]; take 2.mp4.
  • Negative check: with the old escaping, the movie case fails on both the apostrophe name and the colon name, so the CI test catches the bug even where drawtext is missing.
  • Old vs new: the parse-back check on 8 representative values gives:
  • Codex review (codex review --base origin/main, gpt-6.1-sol): no actionable regressions, no findings to fix or rebut.
  • Process note: fix(ffmpeg): preserve paths through filter and concat parsers #40's code was not run locally. Every file it touched was rewritten or reviewed line by line before any test ran. src/tools/concat.ts keeps its two-line change.

eliasruntime and others added 8 commits September 28, 2026 21:21
fix(ffmpeg): preserve paths through filter and concat parsers
Builds on #40. A filter option value is read twice: the filtergraph
parser strips one layer of quoting, then the option parser splits on
`:` and strips another. `quoteFilterArg` now escapes for the option
parser first and quotes that for the graph parser, so Windows drive
colons, apostrophes and `[],;` survive, and a value can no longer
close its quote and add a filter. It also escapes edge whitespace,
which the option parser otherwise trims (a file name ending in a
space was not found).

Concat-list paths get their own single-layer `quoteConcatPath`; a
path with a line break or NUL throws `ConcatPathError` naming it.

Tests read escaped output back through a port of FFmpeg's
`av_get_token` instead of comparing strings, and CI-only tests run
the cases through the real FFmpeg binary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Linux ffmpeg-static binary in CI has no drawtext filter, so every
drawtext case failed with "No such filter". The same paths now also go
through `movie`, which every FFmpeg build has and which reads its path
through the same two parse steps; the drawtext cases run only where the
binary has drawtext.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since FFmpeg 6.1, drawtext needs libharfbuzz as well as libfreetype, and
the Linux FFmpeg 7.0.2 build that ffmpeg-static 5.3.0 installs has no
harfbuzz. add_text, add_captions, add_title_card and timeline text clips
died there with FFmpeg's raw "No such filter: 'drawtext'". List the
resolved FFmpeg's filters once per process and throw MissingFilterError
before any text render, naming the binary and how to point
MAKEMYCLIP_FFMPEG_PATH at a build that has drawtext.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hoice

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yultyyev and others added 2 commits October 29, 2026 15:42
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(ffmpeg): fail fast when ffmpeg lacks the drawtext filter
@yultyyev
yultyyev merged commit 7820598 into main Oct 29, 2026
1 check passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.3.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants