Skip to content

v0.15.0 β€” it can edit a music video now

Latest

Choose a tag to compare

@DareDev256 DareDev256 released this 04 Aug 14:28

Yesterday this tool could read a real project correctly and could not edit one. That is fixed.

snap_to_beats moved 0 cuts and called it success

A music video in Final Cut is built by laying an audio bed and hanging every visual off it as a connected clip. The spine holds one <gap>. Every edit handler looked for cuts on the spine, found nothing, and reported success.

Measured on a real 164-second music video: 128 cuts across 14 lanes, 71 moved, 22 already on a beat, 35 skipped as collisions. Previously: 0 clips, 0 cuts, "synced to the beat!"

Non-rippling, and lanes move independently. Moving one connected clip does not shift the rest of its lane β€” connected clips are not magnetic to each other, and rippling would rearrange an edit you already made. A move that would overlap a neighbour is skipped and reported, never forced. Negative (audio) lanes are excluded by default, since on a music video that is the track the beats came from.

Four bugs surfaced underneath and had to be fixed for any of it to work: import_beat_markers raised on every music video, a connected clip's offset is in its host's time frame rather than the timeline's, _timeline_duration reported a 164s timeline as 170.96s, and the validator flagged Final Cut's own 1001/24000s timebase as non-standard.

detect_flash_frames was also spine-only and missed a 1-frame connected clip on the real project. Fixed. rapid_trim, fix_flash_frames and fill_gaps have the same blind spot and are deliberately unchanged β€” a wrong write on someone's edit costs more than a missing feature. Tracked.

detect_beats returned onsets, not a grid

librosa reports beats it hears, so an intro with no percussion and a drum-out before the outro came back empty β€” exactly the sections an editor is eyeballing, and you cannot snap a cut to a beat that was never reported.

When enough observed beats agree on one fixed grid, the period and phase are now solved from them and the grid is extended across the whole file. Rubato still returns observed beats untouched; inventing beats on a track that genuinely moves is worse than reporting none. source says which path was taken.

The load-bearing detail: the median inter-onset interval is not the period. librosa quantises to ~11.6ms frames, so a true 0.5s grid lands on 43 frames more often than 44 and the median comes back 0.4992s. That 0.8ms error compounds to 0.23s across 286 beats. It needs a least-squares refit over beat index.

before after independent reference tracker
BPM 120.185 119.996 120.005
Beats 286 329 328
Coverage 16.0-158.6s 0.03-164.03s full

Security: roots that confine reads, and three caps that were only ever claimed

FCP_PROJECTS_DIRS (new, opt-in, colon-separated) confines reads, not just listing. FCP_PROJECTS_DIR is unchanged and still confines listing only, so nobody breaks on upgrade β€” which matters, because the README tells every user to set it.

A path is allowed if the path as given or its resolved target is inside a root. Final Cut imports media leave-files-in-place, so Original Media/ is full of symlinks; resolving them first would reject the media Final Cut itself put in your library. Traversal still normalises before the check.

Three caps that the security matrix used to claim and never had, all configurable, all returning an explicit truncation notice rather than silently returning a partial result:

  • FCP_MAX_DISCOVERY_FILES (10,000) β€” the walk stops, rather than collecting everything and slicing. A cap applied after list(rglob(...)) still walks the filesystem, which was the actual denial of service.
  • FCP_MAX_BATCH_MARKERS (10,000)
  • FCP_MAX_TRANSCRIPT_CHARS (1 MiB), cut on a line boundary so a timestamp is never split

Also

The minimum-mcp CI job now reads the dependency floor out of pyproject.toml instead of hardcoding it in a third place. A check that fails spuriously is a check somebody deletes.

1195 tests passing, up from 1108. Every new test was sabotage-verified β€” 23 separate mutations, each reverted after measuring which tests failed.

Full Changelog: v0.14.5...v0.15.0