Skip to content

fix(helpers): UTF-8 and correct ffmpeg escaping for non-ASCII paths on Windows - #116

Open
gocc0 wants to merge 1 commit into
browser-use:mainfrom
gocc0:fix/windows-utf8-and-ffmpeg-paths
Open

fix(helpers): UTF-8 and correct ffmpeg escaping for non-ASCII paths on Windows#116
gocc0 wants to merge 1 commit into
browser-use:mainfrom
gocc0:fix/windows-utf8-and-ffmpeg-paths

Conversation

@gocc0

@gocc0 gocc0 commented Aug 3, 2026

Copy link
Copy Markdown

Rendering fails outright on a non-English Windows whenever the footage
directory contains non-ASCII characters, and --grade auto fails on Windows
even with pure-ASCII paths. Four distinct defects, one root cause: the helpers
assume the locale codepage is UTF-8, and assume ffmpeg parses a path the same
way the OS does.

Repro for the original symptom — footage in C:\Users\me\Videos\Gravações de Tela:

[in#0] Impossible to open '...\Gravações de Tela\edit\clips_graded\seg_00.mp4'
Error opening input: Invalid argument

1. concat_segments() wrote the concat list in the locale codepage

concat_list.write_text(...) with no encoding uses the locale codepage
(cp1252 on pt-BR Windows), while ffmpeg's concat demuxer reads the list as
UTF-8. Gravações was written as the bytes Grava\347\365es, so every entry
resolved to a nonexistent file and ffmpeg exited -22 / EINVAL.

Fixed by writing UTF-8, and additionally by writing entries relative to
edit_dir and running ffmpeg with cwd=edit_dir, which keeps non-ASCII out of
the list file altogether. Segments outside edit_dir fall back to an absolute
entry, which is safe now that the file is UTF-8.

2. subtitles= was passed an unescaped Windows path

build_final_composite() escaped : and ' but left backslashes intact.
Backslash is the filtergraph escape character, so C:\Users\me\master.srt
reached the filter as C:Usersmemaster.srt:

[Parsed_subtitles_0] Unable to open C:UsersmeAppData...master.srt
[AVFilterGraph] Error initializing filters

Worth noting for anyone who has worked around this: accents were never the
problem here.
Verified on ffmpeg 8.1.2 that Latin-1, CJK and Cyrillic
subtitle paths all load correctly once the path uses forward slashes and an
escaped drive colon — no ASCII copy required. The new escape_filter_path()
does exactly that.

The one character ffmpeg's two-level parser genuinely cannot carry in a
filename is a literal ' — every quoting and escaping form of it fails to
initialize the filter. filtergraph_safe_path() stages a temp copy for that
case only.

3. grade.py passed a raw Windows path to metadata=print:file=

Same class, different filter, and this one is not accent-related at all — it
breaks --grade auto on every Windows machine:

[AVFilterGraph] Error parsing filterchain
'fps=5.00,signalstats,metadata=print:file=C:\Users\...\tmp.txt'

Same quoting fix applied.

4. UnicodeEncodeError when printing progress

Windows selects the console codepage for sys.stdout, so a progress line
containing — or an accented source filename — raises UnicodeEncodeError
and kills the run before any ffmpeg work starts. use_utf8_stdio()
reconfigures stdout/stderr to UTF-8; it is a no-op on Linux and macOS.

Also

Explicit encoding="utf-8" on every read_text / write_text / open in
helpers/ that can touch a user path or transcript text, including the master
SRT write (libass reads SRT as UTF-8, and captions carry non-ASCII text even
when the paths are ASCII).

Testing

Windows 11, ffmpeg 8.1.2, Python 3.14, pt-BR locale (cp1252).

End-to-end render.py --build-subtitles from a directory named
Gravações de Tela, with an accented source filename, accented caption text,
and "grade": "auto":

  • after: exits 0, output written, extracted frame confirms OLÁ MUNDO
    renders with the accent intact
  • before: fails at auto-grade (-22), at concat (-22), and at subtitles
    (-2)

ASCII-path renders were checked for regression and are unaffected. Also
verified a directory containing both an apostrophe and accents
(Gui's Gravações) now renders, via the staged-copy path.


Summary by cubic

Fixes Windows rendering failures with non-ASCII paths by using UTF-8 consistently and proper ffmpeg filtergraph path escaping. Restores subtitles and --grade auto, and prevents console encoding crashes.

  • Bug Fixes
    • Concat: write _concat.txt as UTF-8, use edit-relative entries, and run ffmpeg with cwd=edit_dir.
    • Subtitles: escape filter paths (forward slashes, escape drive colon) via escape_filter_path(); stage a temp copy if the path contains ' with filtergraph_safe_path().
    • Auto-grade: quote/escape metadata=print:file= path inside the filtergraph.
    • IO/console: reconfigure stdout/stderr to UTF-8 with use_utf8_stdio(); read/write JSON, SRT, and .env with encoding="utf-8".

Written for commit 03dea21. Summary will update on new commits.

Review in cubic

…s on Windows

Rendering failed outright whenever the footage directory contained non-ASCII
characters (e.g. "Gravações de Tela"), and auto-grade failed on Windows even
with pure-ASCII paths. Four distinct defects, one root cause: the helpers
assumed the locale codepage and assumed ffmpeg parses paths like the OS does.

1. concat_segments() wrote _concat.txt with write_text() and no encoding. That
   uses the locale codepage (cp1252 on pt-BR Windows) while ffmpeg's concat
   demuxer reads the list as UTF-8, so "Gravações" landed in the file as the
   bytes Grava\347\365es, every entry resolved to a missing file, and ffmpeg
   exited -22 / EINVAL. Now written as UTF-8, with entries relative to edit_dir
   and ffmpeg run with cwd=edit_dir so non-ASCII stays out of the list entirely.

2. build_final_composite() escaped ':' and "'" in the subtitles= path but left
   backslashes intact. Backslash is the filtergraph escape character, so
   C:\Users\me\master.srt reached the filter as C:Usersmemaster.srt ("Unable to
   open"). New escape_filter_path() normalizes to forward slashes and escapes
   the drive colon. Accents/CJK/Cyrillic then work with no copy needed; a
   literal "'" is the one character the parser cannot carry in any quoting form,
   so filtergraph_safe_path() stages a temp copy for that case only.

3. grade.py passed a raw Windows path to metadata=print:file= inside a
   filtergraph, which fails to parse on every Windows machine regardless of
   accents ("Error parsing a filter description") and took --grade auto down.
   Same quoting fix applied.

4. Progress lines carrying '→' — or an accented source filename — raised
   UnicodeEncodeError on a cp1252 console and killed the run before any ffmpeg
   work started. use_utf8_stdio() reconfigures stdout/stderr to UTF-8.

Also adds explicit encoding="utf-8" to every read_text/write_text/open in
helpers/ that can touch a user path or transcript text.

Verified on Windows 11 / ffmpeg 8.1.2 with an end-to-end render from an
accented directory, an accented source filename, accented caption text and
auto-grade: passes after, fails before at concat (-22), subtitles (-2) and
auto-grade. ASCII paths unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="helpers/grade.py">

<violation number="1" location="helpers/grade.py:109">
P2: Auto-grade still fails when the temp-directory path contains an apostrophe (for example, a Windows profile named `O'Connor`): this closes `file=` quoting before FFmpeg parses the filter. Use the same staged safe-path approach as `filtergraph_safe_path()` for the metadata output path, rather than embedding this pathname directly.</violation>
</file>

<file name="helpers/render.py">

<violation number="1" location="helpers/render.py:65">
P3: `use_utf8_stdio()` is now copy-pasted verbatim into five files (render.py, grade.py, transcribe.py, timeline_view.py, pack_transcripts.py), while transcribe_batch.py correctly imports it from transcribe (`from transcribe import ... use_utf8_stdio`). This 12-line helper is identical everywhere and fixing one copy (e.g. choosing `errors="replace"` vs `strict`) won't propagate to the others, which is exactly how encoding bugs spread. Consider centralizing it in a shared module (e.g. transcribe.py already exports it, or a small `_utf8.py` util) and importing it in the other four scripts, mirroring transcribe_batch.py.</violation>

<violation number="2" location="helpers/render.py:146">
P2: Subtitles still fail for users whose Windows temp path contains an apostrophe: staging inherits that apostrophe and the helper only warns before invoking ffmpeg. Select or create a quote-free staging root, or fail before launching with a clear actionable error.</violation>

<violation number="3" location="helpers/render.py:358">
P2: Renders with an apostrophe in an EDL source key still fail during concat because the generated `file '…'` record has an unescaped quote. Escape single quotes using concat-demuxer quoting when emitting each entry.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread helpers/grade.py
# ("Error parsing a filter description"), which took auto-grade down on
# every Windows machine regardless of accents. Quote it, use forward
# slashes, and escape the drive colon.
meta_arg = "'" + metadata_path.replace("\\", "/").replace(":", r"\:") + "'"

@cubic-dev-ai cubic-dev-ai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Auto-grade still fails when the temp-directory path contains an apostrophe (for example, a Windows profile named O'Connor): this closes file= quoting before FFmpeg parses the filter. Use the same staged safe-path approach as filtergraph_safe_path() for the metadata output path, rather than embedding this pathname directly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/grade.py, line 109:

<comment>Auto-grade still fails when the temp-directory path contains an apostrophe (for example, a Windows profile named `O'Connor`): this closes `file=` quoting before FFmpeg parses the filter. Use the same staged safe-path approach as `filtergraph_safe_path()` for the metadata output path, rather than embedding this pathname directly.</comment>

<file context>
@@ -101,12 +101,18 @@ def _sample_frame_stats(
+        # ("Error parsing a filter description"), which took auto-grade down on
+        # every Windows machine regardless of accents. Quote it, use forward
+        # slashes, and escape the drive colon.
+        meta_arg = "'" + metadata_path.replace("\\", "/").replace(":", r"\:") + "'"
         cmd = [
             "ffmpeg", "-y", "-hide_banner", "-nostats",
</file context>
Fix with cubic

Comment thread helpers/render.py
yield path
return

tmp_dir = Path(tempfile.mkdtemp(prefix="video_use_"))

@cubic-dev-ai cubic-dev-ai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Subtitles still fail for users whose Windows temp path contains an apostrophe: staging inherits that apostrophe and the helper only warns before invoking ffmpeg. Select or create a quote-free staging root, or fail before launching with a clear actionable error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/render.py, line 146:

<comment>Subtitles still fail for users whose Windows temp path contains an apostrophe: staging inherits that apostrophe and the helper only warns before invoking ffmpeg. Select or create a quote-free staging root, or fail before launching with a clear actionable error.</comment>

<file context>
@@ -92,6 +108,55 @@ def resolve_path(maybe_path: str, base: Path) -> Path:
+        yield path
+        return
+
+    tmp_dir = Path(tempfile.mkdtemp(prefix="video_use_"))
+    try:
+        staged = tmp_dir / path.name.replace("'", "_")
</file context>
Fix with cubic

Comment thread helpers/render.py
else:
entries.append(rel.as_posix())
concat_list.write_text(
"".join(f"file '{e}'\n" for e in entries), encoding="utf-8"

@cubic-dev-ai cubic-dev-ai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Renders with an apostrophe in an EDL source key still fail during concat because the generated file '…' record has an unescaped quote. Escape single quotes using concat-demuxer quoting when emitting each entry.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/render.py, line 358:

<comment>Renders with an apostrophe in an EDL source key still fail during concat because the generated `file '…'` record has an unescaped quote. Escape single quotes using concat-demuxer quoting when emitting each entry.</comment>

<file context>
@@ -265,21 +330,47 @@ def extract_all_segments(
+        else:
+            entries.append(rel.as_posix())
+    concat_list.write_text(
+        "".join(f"file '{e}'\n" for e in entries), encoding="utf-8"
+    )
 
</file context>
Suggested change
"".join(f"file '{e}'\n" for e in entries), encoding="utf-8"
"".join(f"file '{e.replace(\"'\", r\"'\\''\")}'\n" for e in entries), encoding="utf-8"
Fix with cubic

Comment thread helpers/render.py
# -------- Helpers ------------------------------------------------------------


def use_utf8_stdio() -> None:

@cubic-dev-ai cubic-dev-ai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: use_utf8_stdio() is now copy-pasted verbatim into five files (render.py, grade.py, transcribe.py, timeline_view.py, pack_transcripts.py), while transcribe_batch.py correctly imports it from transcribe (from transcribe import ... use_utf8_stdio). This 12-line helper is identical everywhere and fixing one copy (e.g. choosing errors="replace" vs strict) won't propagate to the others, which is exactly how encoding bugs spread. Consider centralizing it in a shared module (e.g. transcribe.py already exports it, or a small _utf8.py util) and importing it in the other four scripts, mirroring transcribe_batch.py.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/render.py, line 65:

<comment>`use_utf8_stdio()` is now copy-pasted verbatim into five files (render.py, grade.py, transcribe.py, timeline_view.py, pack_transcripts.py), while transcribe_batch.py correctly imports it from transcribe (`from transcribe import ... use_utf8_stdio`). This 12-line helper is identical everywhere and fixing one copy (e.g. choosing `errors="replace"` vs `strict`) won't propagate to the others, which is exactly how encoding bugs spread. Consider centralizing it in a shared module (e.g. transcribe.py already exports it, or a small `_utf8.py` util) and importing it in the other four scripts, mirroring transcribe_batch.py.</comment>

<file context>
@@ -58,6 +62,18 @@ def auto_grade_for_clip(video, start=0.0, duration=None, verbose=False):  # type
 # -------- Helpers ------------------------------------------------------------
 
 
+def use_utf8_stdio() -> None:
+    """Print through UTF-8 rather than the locale codepage.
+
</file context>
Fix with cubic

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant