From 612134216e0258e76819a0240f9ca4bbcceb813b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:09:39 +0000 Subject: [PATCH] export: fix burn-in caption path escaping on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit escape_filter_path emitted a double backslash before the drive colon ('C\:/...'), which the ffmpeg option parser de-escapes to a bare backslash before a real separator — the quoted value splits mid-path and the subtitles filter fails with 'No option name near ...' on ffmpeg 8.1. Inside the single-quoted value the parser strips exactly one escape level, so emit a single backslash ('C\:/...'), verified to parse and render on ffmpeg 8.1.2. Also emit '\'' for an embedded single quote. Co-Authored-By: Jean de Dieu HAGENIMANA --- apps/desktop/src-tauri/src/exports/captions.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/desktop/src-tauri/src/exports/captions.rs b/apps/desktop/src-tauri/src/exports/captions.rs index 09d6584..a9c1ef2 100644 --- a/apps/desktop/src-tauri/src/exports/captions.rs +++ b/apps/desktop/src-tauri/src/exports/captions.rs @@ -123,14 +123,16 @@ pub(super) fn subtitles_filter( ) } -/// Escapes a filesystem path for use inside a quoted value of a -/// `-/filter_complex` script entry. The script file and the option parser each -/// run one unescape pass, so separators need double backslashes (`\\:`). +/// Escapes a filesystem path for use inside a single-quoted option value of a +/// `-/filter_complex` script entry. The option parser runs one unescape pass +/// on the quoted value, so a `:` separator needs a single backslash (`\:`) and +/// an embedded quote needs `\'`. A doubled backslash there de-escapes to a +/// bare `\` before a real separator and the value splits mid-path. fn escape_filter_path(path: &Path) -> String { path.to_string_lossy() .replace('\\', "/") - .replace('\'', "\\\\'") - .replace(':', "\\\\:") + .replace('\'', "\\'") + .replace(':', "\\:") } fn build_ass_script( @@ -275,10 +277,10 @@ mod tests { #[test] fn filter_path_escapes_drive_colon() { - // The -/filter_complex script and the option parser each strip one - // escape level, so a literal colon needs a double backslash. + // Inside a single-quoted option value the option parser strips one + // escape level, so a literal colon needs a single backslash. let escaped = escape_filter_path(Path::new("C:\\Users\\rf\\captions.ass")); - assert_eq!(escaped, "C\\\\:/Users/rf/captions.ass"); + assert_eq!(escaped, "C\\:/Users/rf/captions.ass"); } #[test]