feat(publish): FcpxmlExport — editorial timeline hand-off to DaVinci Resolve - #451
Open
prasanthsasikumar wants to merge 2 commits into
Open
feat(publish): FcpxmlExport — editorial timeline hand-off to DaVinci Resolve#451prasanthsasikumar wants to merge 2 commits into
prasanthsasikumar wants to merge 2 commits into
Conversation
Promote the standalone tools/video/fcpxml_export.py script to a registered publish-capability BaseTool (provider "fcpxml") alongside export_bundle, so it's registry-discoverable, appears in preflight, and is routable. Emits an FCPXML timeline (grid scenes -> multi-track connected clips) for reopening a cut in Resolve/FCP. build_fcpxml logic unchanged; CLI preserved via a shared export_project_to_fcpxml() used by both execute() and __main__. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The tool worked for the project it was built against but carried
assumptions from it. Six fixes, each with a test written first:
- fps/resolution were hardcoded to 30 and 1920x1080, silently retiming
24/25/60fps footage and mislabelling vertical sequences. They are now
probed from the first source clip, with explicit overrides on the tool.
- Grid widths beyond 3 had no verified Resolve transform, so every cell
got zero adjustments and rendered full-frame stacked — a timeline that
imports cleanly and is silently wrong. Now raises instead.
- ffprobe failures escaped execute(): a missing binary or a hang raised
through the contract, and a non-zero exit was worse than that — ffprobe
still prints '{}', so a bad file produced a zero-duration asset and a
broken timeline reported as success. All three are ToolResult errors now.
- The Dolby Vision SDR path swap was invisible; substituted filenames are
reported in ToolResult.data['sdr_substitutions'] and by the CLI.
- Added tests pinning the reverse-engineered Resolve grid constants, which
cannot be re-derived analytically if someone "simplifies" them.
- Declared the tool in cinematic.yaml's publish stage and documented the
editorial hand-off in the publish-director skill; the registry already
discovered it, but no manifest offered it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
fcpxml_export, a secondpublishprovider alongsideexport_bundle. Whereexport_bundlepackages a finished render for platform upload, this emits an editable editorial timeline (FCPXML) so a cut can be reopened, graded, and finished in DaVinci Resolve or Final Cut Pro instead of being locked to a flat file.Resolve imports FCPXML reliably; plain EDL (CMX3600) has no multi-track or transform support, so side-by-side grid scenes can't be represented at all. FCPXML's connected clips (the
laneattribute) let simultaneous videos stack on one anchor clip, which is how grid scenes survive the hand-off as separately editable cells.On the grid transform constants: two attempts at generic Motion-style
<adjust-transform>(position + scale) failed to composite in Resolve — a non-uniform scale skewed the video, and a corrected uniform scale sat on an opaque background hiding the layers beneath. The working recipe (<adjust-crop>trim +<adjust-conform type="fit">+<adjust-transform>with scale always"1 1") was reverse-engineered from a timeline positioned by hand in Resolve and exported back out. Resolve's internal position unit space isn't the 1920x1080 canvas and its conversion isn't publicly documented, so those numbers cannot be re-derived analytically — hence the tests that pin them.Related issue
No linked issue.
Changes
tools/publishers/fcpxml_export.py— the tool, plus a CLI entry point (python -m tools.publishers.fcpxml_export <project_dir>).fps/width/heightoverrides on the tool. They were previously hardcoded to 30 and 1920x1080, which silently retimed 24/25/60fps footage on import and mislabelled vertical sequences.ffprobefailure modes returnToolResult(success=False): missing binary, timeout, and non-zero exit. The last one mattered most — ffprobe still prints{}on a bad file, so the tool used to build a zero-duration asset and report success on a broken timeline.ToolResult.data["sdr_substitutions"](and by the CLI) instead of being swapped invisibly, so an editor knows to relink before grading.cinematic.yaml's publish stage and documented in the publish-director skill — the registry already discovered the tool, but no manifest offered it, so the documented agent path couldn't reach it.Testing
tests/tools/test_fcpxml_export.py, written test-first — each fix had a test that failed for the right reason before the code existed.test_video_compose_vertical.py), so the ffprobe paths exercise the real binary rather than a mock. The missing-binary case stripsPATHrather than patching.scale="1 1", lane structure, and the connected-clip-offset-is-anchor-relative regression.frameDuration="1/24s" width="720" height="1280"andduration="48/24s", and the SDR substitution note prints.tests/contracts/test_phase3_contracts.pyare pre-existing and environmental (No module named 'google.genai'locally); they reproduce identically on unmodifiedmain, and CI installs the package viamake install-dev.Known limits, deliberately not hidden: only 2-up and 3-up grids have measured Resolve transforms (wider raises rather than emitting a wrong timeline), and the tool is marked
BETA. The publish stage in the other 9 pipeline manifests still hastools_available: []— that predates this change and felt like a separate concern, so this PR only wirescinematic, where grid scenes live.Checklist
make test-contracts/make test) where applicable.🤖 Generated with Claude Code