diff --git a/packages/tests-shared/compile/test_compile_cli.py b/packages/tests-shared/compile/test_compile_cli.py index eb592c17c..add0d2163 100644 --- a/packages/tests-shared/compile/test_compile_cli.py +++ b/packages/tests-shared/compile/test_compile_cli.py @@ -132,3 +132,18 @@ def test_parse_ffmpeg_commands(snapshot: SnapshotAssertion, command: str) -> Non assert snapshot( name="build-ffmpeg-commands", extension_class=VersionedAmberExtension ) == compile(parsed) + + +def test_vf_single_quoted_params_with_commas() -> None: + """Single-quoted filter option values containing commas must not split the filterchain (issue #593).""" + cmd = ( + "ffmpeg -i input.mkv" + " -vf \"scale=-1:-1,pad=1920:1080:-1:-1,subtitles='subtitles.srt':force_style='Fontname=Arial,Fontsize=17'\"" + " output.mp4" + ) + compiled = compile(parse(cmd)) + # All three filters must appear in the output; commas inside the + # single-quoted force_style value must not split the filterchain. + assert "scale" in compiled + assert "pad" in compiled + assert "subtitles" in compiled diff --git a/packages/ts-core/src/__tests__/parse.test.ts b/packages/ts-core/src/__tests__/parse.test.ts index 482c91bfc..d144fbd3d 100644 --- a/packages/ts-core/src/__tests__/parse.test.ts +++ b/packages/ts-core/src/__tests__/parse.test.ts @@ -93,6 +93,17 @@ beforeAll(() => { ]), makeFilter("split", ["video"], ["video", "video"]), makeFilter("amix", ["audio", "audio"], ["audio"]), + makeFilter("pad", ["video"], ["video"], [ + { name: "width", default: null }, + { name: "height", default: null }, + { name: "x", default: null }, + { name: "y", default: null }, + ]), + makeFilter("subtitles", ["video"], ["video"], [ + { name: "filename", default: null }, + { name: "stream_index", default: null }, + { name: "force_style", default: null }, + ]), ]; filtersMap = new Map(filters.map(f => [f.name, f])); optionsMap = new Map(baseOptions.map(o => [o.name, o])); @@ -387,4 +398,16 @@ describe("parse()", () => { expect(compiled).toMatch(/1920/); expect(compiled).toMatch(/1080/); }); + + it("-vf filterchain with single-quoted option value containing commas (issue #593)", () => { + // Single-quoted force_style contains commas that must not split the filterchain + const cmd = + `ffmpeg -i input.mkv -vf "scale=-1:-1:flags=lanczos,pad=1920:1080:-1:-1,subtitles='subtitles.srt':force_style='Fontname=Arial,Fontsize=17'" output.mp4`; + // Should not throw; the three filters are scale → pad → subtitles + const stream = parse(cmd, filtersMap, optionsMap); + const compiled = compile(stream); + expect(compiled).toContain("scale"); + expect(compiled).toContain("pad"); + expect(compiled).toContain("subtitles"); + }); }); diff --git a/packages/ts-core/src/compile/compileCli.ts b/packages/ts-core/src/compile/compileCli.ts index aa69b5ce5..a1707803b 100644 --- a/packages/ts-core/src/compile/compileCli.ts +++ b/packages/ts-core/src/compile/compileCli.ts @@ -302,10 +302,37 @@ function shlexSplit(cli: string): string[] { return tokens; } -/** Split text on an unescaped separator character. */ +/** + * Split text on an unescaped separator character. + * Respects single-quoted sections (FFmpeg filter-option escaping) and + * backslash-escaped characters — neither terminates a token. + */ function splitUnescaped(text: string, sep: string): string[] { - const escaped = sep.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return text.split(new RegExp(`(? list[str]: - """Split text on unescaped separator character.""" - pattern = _SEP_PATTERNS.get(sep) - if pattern is None: - pattern = re.compile(r"(? list[str]: - """Split text on unescaped separator character.""" - pattern = _SEP_PATTERNS.get(sep) - if pattern is None: - pattern = re.compile(r"(? list[str]: - """Split text on unescaped separator character.""" - pattern = _SEP_PATTERNS.get(sep) - if pattern is None: - pattern = re.compile(r"(? list[str]: - """Split text on unescaped separator character.""" - pattern = _SEP_PATTERNS.get(sep) - if pattern is None: - pattern = re.compile(r"(? list[str]: - """Split text on unescaped separator character.""" - pattern = _SEP_PATTERNS.get(sep) - if pattern is None: - pattern = re.compile(r"(? list[str]: - """Split text on unescaped separator character.""" - pattern = _SEP_PATTERNS.get(sep) - if pattern is None: - pattern = re.compile(r"(?