From 406ab1079b82c3178d66c76c09fa630cee9f30f2 Mon Sep 17 00:00:00 2001 From: Pcecil21 Date: Fri, 19 Jun 2026 10:04:33 -0500 Subject: [PATCH] fix: make CLI helpers run on Windows (cp1252 console + ffmpeg path) Two cross-platform issues surfaced running the helpers on a Windows (cp1252) console: 1. Status print() calls contain non-ASCII glyphs (U+2192 right-arrow, U+2014 em-dash). Windows stdout defaults to cp1252, which can't encode them, so the helpers raise UnicodeEncodeError -- grade.py before it grades anything, pack_transcripts.py after writing its file. Fix: reconfigure stdout/stderr to UTF-8 at import in the four helpers that print non-ASCII (a no-op where the stream is already UTF-8). 2. grade.py --analyze embedded a Windows temp path in the ffmpeg 'metadata=print:file=...' filter, where ':' and '\' are filtergraph delimiters, so ffmpeg failed to parse it. Fix: pass a bare filename and run ffmpeg with cwd set to the temp file's directory; the input video is resolved to an absolute path first so the cwd change can't break a relative '-i' input. Adds tests/test_grade_path.py: cross-platform assertions for the metadata path handling (pass on Windows and Linux) plus a regression for the cp1252 print path. Verified manually on Windows -- pack_transcripts.py, grade.py --analyze (incl. a relative input), render.py --preview, and transcribe_batch.py all run clean with no PYTHONUTF8 workaround. No behavior change on macOS/Linux. --- helpers/grade.py | 43 ++++++++++++++++++- helpers/pack_transcripts.py | 10 +++++ helpers/render.py | 10 +++++ helpers/transcribe_batch.py | 10 +++++ tests/test_grade_path.py | 83 +++++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 tests/test_grade_path.py diff --git a/helpers/grade.py b/helpers/grade.py index becf6fb4..0fadbb95 100644 --- a/helpers/grade.py +++ b/helpers/grade.py @@ -34,6 +34,16 @@ import tempfile from pathlib import Path +# Windows consoles default to cp1252, which raises UnicodeEncodeError on the +# Unicode arrows/em-dashes in the status prints below. Force UTF-8 on stdout/ +# stderr so this helper behaves identically on Windows, macOS, and Linux. +# No-op where the stream is already UTF-8 or can't be reconfigured. +for _stream in (sys.stdout, sys.stderr): + try: + _stream.reconfigure(encoding="utf-8") + except (AttributeError, ValueError): + pass + PRESETS: dict[str, str] = { # Subtle baseline — barely perceptible cleanup. No color shift. @@ -75,6 +85,25 @@ def get_preset(name: str) -> str: # -------- Auto grade (data-driven, per-clip) -------------------------------- +def _metadata_filter_target(path: str) -> tuple[str, str]: + r"""Return (cwd, filter_value) for writing an ffmpeg metadata file safely. + + ffmpeg filtergraphs treat ':' and '\' as delimiters, so embedding a Windows + path like C:\Users\me\tmp.txt in 'metadata=print:file=...' breaks parsing + and ffmpeg exits with an error. Instead we pass the bare filename and run + ffmpeg with cwd set to the file's directory, so no special character ever + reaches the filtergraph parser. + + Split on either separator ('/' or '\') rather than host-native pathlib, so + the bare filename is extracted the same way on any OS — the filter value + must never contain ':' or a path separator regardless of where this runs. + """ + idx = max(path.rfind("/"), path.rfind("\\")) + if idx == -1: + return ".", path + return path[:idx], path[idx + 1:] + + def _sample_frame_stats( video: Path, start: float, @@ -93,6 +122,11 @@ def _sample_frame_stats( "sat_mean": mean saturation in 0..1, } """ + # Resolve to an absolute path before the ffmpeg call below: that call sets + # cwd to the temp dir (for the metadata file), so a relative `-i