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