Decode stdin as UTF-8 and sanitize export filenames#67
Conversation
Two problems that full-length titles made more likely to hit, since em dashes and
smart quotes now reach code that previously saw a 40 or 55 character slice.
stdin encoding. main.py reconfigured stdout and stderr to UTF-8 but not stdin, and
stdin is where the task JSON arrives from Node. Python falls back to the host locale
encoding, cp1252 on most Windows installs. U+201D encodes to E2 80 9D and cp1252
leaves 0x9D undefined, so the request failed to decode before it was ever parsed:
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 29
Fixed at both layers, because either alone leaves a hole. main.py and cli.py now
reconfigure stdin, so the backend is correct however it is spawned. And the
launcher's nodeEnv() sets PYTHONIOENCODING and PYTHONUTF8, which only Run() did
before, so Python spawned by the Node MCP server inherited a locale default.
On the Node side the four spawns each rebuilt env by hand and all four omitted the
encoding; they now share a pythonEnv() helper so a new call site cannot forget.
Export filenames. The DaVinci exporter interpolated the raw title into a path as
`{args.title.replace(' ', '_')}.fcpxml`, which breaks on path separators, on the
characters Windows rejects, and on titles long enough to exceed the path limit.
clip_generator had a correct-but-private sanitizer; it is now the shared
safe_filename, used by both. Punctuation is dropped rather than substituted and
whitespace re-collapsed afterwards, since removing a padded em dash otherwise
leaves "__".
The FCPXML body itself was already safe: emitter.py builds it with ElementTree,
which escapes attribute values.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
📝 WalkthroughWalkthroughThis PR extends UTF-8 stream reconfiguration to include stdin in backend entry points, adds PYTHONIOENCODING/PYTHONUTF8 environment variables in Node-spawned Python processes, introduces a shared pythonEnv() helper adopted across TypeScript subprocess spawns, and adds a safe_filename utility replacing manual filename sanitization in clip generation and DaVinci Resolve CLI output. ChangesStdin/UTF-8 Encoding Propagation
Safe Filename Generation
Estimated code review effort: 2 (Simple) | ~15 minutes Sequence Diagram(s)sequenceDiagram
participant NodeEngine as Node (engine.go)
participant PythonExecutor as pythonEnv()/PythonExecutor
participant Backend as backend/main.py
NodeEngine->>NodeEngine: nodeEnv() sets PYTHONIOENCODING, PYTHONUTF8
NodeEngine->>PythonExecutor: spawn(python, env=pythonEnv())
PythonExecutor->>Backend: send JSON task via stdin (UTF-8)
Backend->>Backend: reconfigure stdin/stdout/stderr to UTF-8
Backend-->>PythonExecutor: JSON response via stdout
PythonExecutor-->>NodeEngine: parsed result
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Two Windows problems that full-length titles make more likely to hit, since em dashes and smart quotes now reach code that previously only ever saw a 40- or 55-character slice.
stdin was never reconfigured
main.pyreconfiguredstdoutandstderrto UTF-8 but notstdin— the one stream that receives the title. Python falls back to the host locale encoding, cp1252 on most Windows installs.U+201D(curly closing quote) encodes toE2 80 9D, and cp1252 leaves0x9Dundefined, so the request failed to decode before it was ever parsed.Reproduced directly:
The encoding could be lost in three places, so fixing one would have left the hole open:
main.py/cli.pydid not reconfigurestdin. They do now, so the backend is correct however it is spawned.nodeEnv()omittedPYTHONIOENCODINGandPYTHONUTF8, which onlyRun()set. Sopodcli studiowas protected while the MCP server path was not — the asymmetry that made this look intermittent.envby hand, and all four setPYTHONUNBUFFEREDwhile omitting the encoding. That duplication is why it was missed; they now share apythonEnv()helper.Verified that Go's
execuses last-wins for duplicate env keys, otherwise thenodeEnv()change would silently no-op on a host that already setsPYTHONIOENCODING. Verifiedreconfigure()is safe on a TTY, sincecli.pyis interactive.Export filenames
The DaVinci exporter interpolated the raw title into a path as
{args.title.replace(' ', '_')}.fcpxml, which breaks on path separators, on characters Windows rejects, and on titles long enough to exceed the path limit.clip_generatoralready had a correct-but-private sanitizer. It is now the sharedsafe_filename, used by both. Punctuation is dropped rather than substituted, whitespace is re-collapsed afterwards (removing a padded em dash otherwise leaves__), and Windows reserved device names are escaped — Windows rejectsCON,NUL,COM1… as a stem whatever the extension, soCON.fcpxmlfails just asCONdoes.Scope note
davinci_resolve/cli.pyis a spike CLI, invoked by hand and documented in its own README. The shipped MCP export (integration.py:93) takes a caller-suppliedoutput_path, which is required in the tool schema, so no production path derives a.fcpxmlname from a title. This is hardening, not a user-facing break. The FCPXML body was already safe:emitter.pybuilds it withElementTree, which escapes attribute values.clip_generator's filenames do change, and it is a hot path. Every difference is an improvement — old names carried__from dropped punctuation and a trailing_, and an empty title produced_short.mp4.Verification
main.pywithPYTHONIOENCODING=cp1252andPYTHONUTF8=0(so a UTF-8-mode host cannot mask the bug) and assertsIn the end — your “shit” has to workround-trips intact.tsc --noEmit,go build/vet/testall pass.tests/test_ai_fallback.pyhas 3 failures that pre-exist on a clean tree (confirmed by stashing); unrelated to this change.No release. These reach users on the next tag.
Summary by CodeRabbit
New Features
Bug Fixes
Tests