84 KiB
Executable File
Changelog
[Unreleased]
Repo renamed fcpxml-mcp-server → fcp-mcp-server to match the PyPI
distribution name. The GitHub About link had been pointing at
pypi.org/project/fcpxml-mcp-server/ — a slug that never existed on PyPI — so
every visitor who clicked it got a 404 while uvx fcp-mcp-server worked fine.
Homepage now points at the real package; clone URLs, CI badge, and
[project.urls] follow the new slug. GitHub redirects the old slug, so existing
clones, forks, and links keep working.
The MCP registry identity stays io.github.DareDev256/fcpxml-mcp-server,
unchanged — it is bound to the mcp-name marker inside the published 0.13.1
PyPI README, and changing it would orphan the registry entry and require a new
PyPI release. Registry name ≠ install name is legal and intentional.
No code changes; 0.13.1 on PyPI is untouched.
[0.13.1] - 2026-07-24
Registry release. Adds the mcp-name ownership marker to the README (required
by the official MCP registry to bind the PyPI package to
io.github.DareDev256/fcpxml-mcp-server) and trims server.json's description
to the registry's 100-char limit. No code changes.
[0.13.0] - 2026-07-23
Transcript Intelligence — text-based editing lands. 59 → 62 tools.
Apple put FCP's AI (Transcript Search, Generate Captions) behind the Creator Studio subscription; this release brings the agentic version to everyone, free, via local Whisper — and goes further: the transcript doesn't just search, it cuts.
Added
transcribe_media— transcribes each clip's source media locally with word-level timestamps (faster-whisper, new optional[transcribe]extra). Writes a_transcript.jsonnext to each media file — transcription is a one-time cost, reused by every transcript tool. Optionalwrite_srtemits an SRT that plugs straight intoimport_srt_markers.edit_by_transcript— cut timeline content by what was SAID.mode=removecuts every occurrence of the given phrases with ripple;mode=keep_onlykeeps only the matched phrases (clips with no matches are left untouched — never deletes a clip because nothing matched). Matching is case/punctuation-insensitive. Non-destructive_transcript_editcopy.remove_filler_words— cuts um/uh/erm out of the timeline with ripple using word-level timestamps from the real source audio. The default filler list is deliberately conservative: words like "like" and "so" are speech, not noise, and are only cut when passed explicitly.- New
fcpxml/transcribe.pymodule: pure, dependency-free matching helpers (find_phrase_spans,find_filler_spans,merge_ranges,invert_ranges,segments_to_srt) + the faster-whisper integration behind the same graceful-degradation contract as ffmpeg/librosa (returnsNone→ tools answer with an install hint, never a crash). - 42 new tests (1032 total): span matching, range algebra, keep_only inversion edge cases, degradation without faster-whisper, and full handler integration against cached transcripts (head-trim vs split behavior, SRT output, transcription caps, missing-media reporting).
Notes
- Whisper model names are allowlist-validated (they resolve to downloads).
- Per-call transcription is capped at 10 distinct media files; cached transcripts don't count against the cap.
[0.12.2] - 2026-07-23
Distribution release — the server is now on PyPI. No tool changes.
Added
- Published to PyPI as
fcp-mcp-server.uvx fcp-mcp-servernow works, which unbreaks the install path thatserver.json(official MCP registry) andsmithery.yamlhave been advertising, and makes the[intelligence]extra installable without cloning. - Claude Code install path in the README (
claude mcp addone-liner + project-scoped.mcp.jsonexample) alongside the existing Claude Desktop instructions. - "How It Compares" section — honest trade-off table vs SpliceKit (runtime patching) and CommandPost (accessibility scripting): raw live power there, no-patch portability, managed-Mac compatibility, and works-without-FCP here.
- Security posture surfaced at the top of the README (132 adversarial-input tests, defusedxml, sandboxed writes, disclosure channel).
Fixed
- Packaging:
server.pywas missing from the wheel.[tool.setuptools]only included thefcpxml*andtools*packages, so a built wheel had no entry-point module andfcp-mcp-serverfailed to launch. Addedpy-modules = ["server"], dropped the emptytoolsstub package from the distribution, and verified the wheel end-to-end in a clean venv (MCP initialize handshake answers correctly). - Server now reports its own version over MCP (
serverInfo.versionsaid1.28.1— the SDK's version — instead of the package's). server.jsonrefreshed: version 0.9.0 → 0.12.2, tool count 56 → 59, description includes media intelligence.pyproject.tomlURLs point at the canonical repo (fcpxml-mcp-server), and a Changelog URL was added.
[0.12.1] - 2026-07-16
Docs fix. No code changes.
Fixed
[intelligence]install command didn't work. The README told users to runpip install 'fcp-mcp-server[intelligence]'to enabledetect_beats, but the package isn't published to PyPI — in a clean venv that errors with "No matching distribution found for fcp-mcp-server[intelligence]". Since the documented install flow isgit clone+pip install -e ., the extra is nowpip install -e '.[intelligence]', with a note that it must run from the cloned repo. This blocked the headline feature of v0.12.0.- Clone URL and paths use the canonical repo name.
git clone .../fcp-mcp-server.gitstill resolves via GitHub's rename redirect, but it created a directory whose name didn't match thecdon the next line, and the/path/to/fcp-mcp-serverplaceholders in the Claude Desktop config didn't match whatgit cloneproduces. (The package name inpyproject.tomlis legitimatelyfcp-mcp-server— only the repo isfcpxml-mcp-server. Left alone.)
Verified unchanged
Audited every headline claim against the source — all accurate, nothing to correct: 59 tools (all 59 documented, 0 undocumented), 990 tests (pytest collects exactly 990), 23 suites, v0.12.0 consistent across pyproject/CHANGELOG/README.
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.12.0] - 2026-07-09
Added — Media Intelligence slice 3: beat detection
detect_beats(59th tool) — detects musical beats and tempo in an audio/video file via librosa's beat tracker and writes a beats JSON next to the media file in exactly the formatimport_beat_markersconsumes, so detect → mark → snap-to-beats chains with zero glue. Analysis duration capped (20 min) to bound memory; media path validated against an audio/video extension whitelist.[intelligence]optional extra —pip install 'fcp-mcp-server[intelligence]'adds librosa. The core install stays 2 dependencies; without the extra,detect_beatsdegrades to an install hint (lazy import, never crashes). CI installs it so beat tests run on every push.
Fixed
import_beat_markersno longer crashes when beats run past the timeline end — songs are routinely longer than edits; out-of-range beats are now skipped and counted in the report instead of raisingNo spine clip at position. Found by end-to-end verification of the detect → import → snap chain.
Tests: 984 → 990.
[0.11.0] - 2026-07-09
Added — Media Intelligence slice 2: silence auto-removal
remove_media_silence(58th tool) — detects real silence in each clip's source audio (same ffmpeg analysis asdetect_media_silence) and cuts it out of the timeline with ripple: clips are split around silence, silent middles removed, everything after shifts earlier.padding(default 0.05s) keeps a breath of silence on each side of every cut; cut boundaries snap to the frame grid in the 2400-tick timebase. Non-destructive — writes a_silence_removedcopy, and writes nothing at all when no silence is found.FCPXMLModifier.cut_clip_ranges— new element-based writer primitive: removes clip-relative time ranges from a spine clip (merging overlapping ranges, clamping out-of-bounds), rebuilds the clip as its kept segments with correct source in-points, filters markers/keywords per segment, and ripples subsequent clips. Element-based on purpose — immune to the duplicate-name ambiguity that name-keyeddelete_clip/split_clipcomposition would hit when cutting a clip into same-named segments.
Tests: 976 → 984.
[0.10.0] - 2026-07-09
Added — Media Intelligence v1 (the moat work begins)
First slice of the v0.10 media-intelligence roadmap: the server now analyzes the actual media files a timeline references, not just the XML.
detect_media_silence(57th tool) — probes each clip's source audio with ffmpeg'ssilencedetectfilter and maps silence ranges from source time into timeline time, reporting per-clip silence spans with a cut plan. Unlikedetect_silence_candidates(XML-only heuristics: gaps, name patterns), this hears the audio. Supportsnoise_dbthreshold (−120..0 dB),min_silenceduration, and per-clip filtering; media files are probed once and cached across clips that share them; missing/unreadable media is reported per clip, never fatal.fcpxml/media_intel.py— new module for real media analysis. Zero new Python dependencies: ffmpeg runs as a bounded subprocess (list-form args, validated numeric parameters, 120s hard timeout, 100-file probe cap) and everything degrades gracefully — no ffmpeg means "unanalyzable", not a crash.- CI now installs ffmpeg so the real-WAV integration tests (tone/silence/tone fixtures generated with the stdlib
wavemodule) run on every push; they skip automatically on machines without ffmpeg.
Tests: 958 → 976 across 23 suites.
[0.9.1] - 2026-07-09
Security
apply_templatewrite sandbox bypass fixed — the one generation handler that built a timeline from scratch (no input file to anchor against) called_validate_output_path()without ananchor_dir, which skipped the sandbox check entirely and accepted absolute or../output paths. An LLM-steered call could overwrite an arbitrary user-writable file. Now anchored toFCP_PROJECTS_DIRlike every other write handler. Reported and fixed by @mikegrant25 (#6).SECURITY.mdadded and GitHub private vulnerability reporting enabled — future disclosures have a private channel.
Fixed
add_audio/add_music_bedstamped the requested clip duration onto new<asset>elements without reading the media file — a music bed shorter than the timeline produced an asset claiming more media than the file contains (invalid FCPXML, clip overruns the real audio). New_probe_audio_info()reads the real duration, sample rate, and channel count viaffprobe(stdlibwavefallback for.wav); assets carry sample-accurate durations plusaudioRate/audioChannels/audioSources, and clip durations are clamped to available media. Unprobeable sources keep the old behavior. Fixed by @jardelapp (#7).- Docs reconciled to verified test counts (#5).
Tests: 955 → 958.
[0.9.0] - 2026-06-11
Added — Live Mode v1 (the dual-mode roadmap goes live)
This is the first release where the server can drive a running Final Cut Pro, not just edit XML offline — using Apple's officially-supported surfaces only (no injection, no private APIs). Both tools were live-verified end to end against Final Cut Pro 12.2.
push_to_fcp(55th tool) — send an FCPXML file into the running FCP with zero clicks via the Open Document Apple event. Injects an<import-options>element (library location, suppress warnings, copy assets), launches FCP if needed, and never touches your original (flat files get an options-injected sibling copy through the same write sandbox as every other tool). Live-verified: generated a timeline, pushed it, and confirmed the library/event/project landed both in FCP and on disk.list_fcp_libraries(56th tool) — enumerate the running FCP's open libraries → events → projects via Apple's read-only scripting dictionary. Refuses to launch FCP unlessallow_launch=true.fcpxml/live.py+tests/test_live.py(13 tests, osascript fully mocked so CI never launches FCP).
Findings baked in from live testing
- Zero-click import requires a
.fcpbundlelibrary location. With a new.fcpbundlepath FCP silently creates the library + a dated event and imports; with no location (or a.fcplibrary/bare path) FCP raises a modal Open Library picker — a required choice thatsuppress warningsdoes not dismiss — which blocks the Apple event.push_to_fcpnow normalizes the location to.fcpbundle. - Apple offers no programmatic export — the read-back leg of any edit loop still needs File > Export XML; the tool says so in its output.
- Importing a project whose media already exists in the target library fails on a media-identity collision — push into a fresh library or reuse FCP's existing asset IDs.
Tests: 942 → 955.
[0.8.0] - 2026-06-11
Added
- FCPXML 1.12–1.14 support: parser now reads everything Final Cut Pro 12.x exports (FCPXML 1.14). Elements introduced after 1.11 (
adjust-stereo-3D,hidden-clip-marker, smart-collectionmatch-analysis-type, …) are tolerated on read and preserved losslessly through edits. Generated timelines (templates, rough cuts, FCPXMLWriter) now emit 1.13 by default; modified files keep their source version. .fcpxmldbundle support, end to end: bundles (directories wrappingInfo.fcpxmlplus sidecar data) now work in every tool.FCPXMLModifierloads bundles, andsave()writes bundle outputs with sidecar preservation — object-tracking and Cinematic-modedataLocatorpayloads are copied across the round-trip instead of silently destroyed. Fixed_validate_filepathrejecting bundles outright (they are directories, and the previous "regular file" check made the whitelisted.fcpxmldextension unreachable).relink_mediatool (54th tool): bulk-rewriteasset/media-repsrcpaths by prefix — relink a moved or renamed media drive without opening FCP. Handlesfile://URLs (percent-encoding preserved) and plain paths, matches whole path segments only, reports whether each new target exists on disk, and supportsdry_runpreview.- DTD validation (
fcpxml/dtd.py+tests/test_dtd_validation.py): generated output is validated against Apple's official DTDs located inside the installed Final Cut Pro app bundle (the only authoritative FCPXML spec — Apple's online docs stopped at 1.10). Skips gracefully on machines without FCP;FCPXML_DTD_DIRoverrides the search path. Found and worked around an xmllint quirk where the space in "Final Cut Pro.app" breaks DTD URI resolution. - Capability audit + dual-mode roadmap (
docs/CAPABILITY-AUDIT-2026-06.md): verified June-2026 ecosystem analysis (FCP 12.2 control surfaces, SpliceKit, CommandPost, format ceiling) and the XML-mode + Live-mode architecture plan through v1.0.
Fixed
- README/CLAUDE.md drift: tool count, test counts, FCPXML version matrix, phantom
fcpxml/README.mdandOPENAI_BASE_URLreferences removed.
Known
examples/sample.fcpxmlis not DTD-conformant (pre-media-repasset form, sequence-level chapter markers) — documented by a dedicated test; fixture modernization planned.- Total: 912 → 942 tests across 21 suites.
[0.7.0] - 2026-05-02
Added / Fixed
- Version milestone consolidating the April hardening waves (no API changes).
- Security hardening with
defusedxml. - Duplicate clip name bug fixes.
- Added 100+ new tests.
- Refactored helper functions for cleaner logic.
- Unification of XML serialization.
- TimeValue arithmetic fixes (integer-exact comparison via cross-multiplication with normalized negative denominators).
- Output-path sandbox enforcement plus speed/ffmpeg parameter validation.
- Stale
timeMap/conform-rate stripping inchange_speed. - Marker/keyword filtering during
split_clip. - Shared
_text_result,_resolve_clip_duration, and_make_asset_cliphelpers. - FCPXML validation-infrastructure test wave (912 tests).
[0.6.63] - 2026-04-14
Fixed
- MontageConfig CONSTANT pacing: The CONSTANT pacing curve returned its computed duration directly, bypassing the
min_duration/max_durationclamp that all other curves (ACCELERATING, DECELERATING, PYRAMID) correctly applied. A montage configured withmin_duration=1.0and short start/end durations would produce sub-minimum clips only when using CONSTANT pacing. - Unnecessary self-imports: Removed
from . import modelsinsideFlashFrame.is_criticalandMontageConfig.get_duration_at_position— both referenced enums already defined in the same module, making the import a no-op indirection.
Added
- 3 new tests for CONSTANT pacing clamping: min clamp, max clamp, and within-bounds passthrough (
test_targeted_gaps.py). Total: 909 → 912 tests.
[0.6.62] - 2026-04-14
Changed
- TimeValue arithmetic: Extracted
_binop()helper from__add__/__sub__, eliminating 10 lines of duplicated LCM-alignment logic. Both operators now delegate to a single code path withoperator.add/operator.sub. - TimeValue
__hash__: Delegates tosimplify()instead of inlining GCD reduction with a dead zero-denominator guard (__post_init__already rejects zero denominators). - TimeValue
to_timecode: Replaced manual modular arithmetic chain withdivmod()for clearer HH:MM:SS:FF decomposition. - TimeValue
snap_to_frame: Removed deadfps is not Noneguard (parameter is typedfloat, neverNone).
[0.6.61] - 2026-04-14
Added
- 33 new tests for FCPXML validation infrastructure (
test_validation.py): DTD-ordered element insertion (_dtd_insert— 5 tests covering marker-before-filter ordering, note-always-first, unknown-tag append, empty parent, middle insertion), child order violation detection (_check_child_order— 4 tests), required attribute validation (_check_required_attributes— 4 tests including transition missing all 3 attrs), non-standard timebase flagging with deduplication (_check_timebases— 3 tests), frame alignment checking at arbitrary fps (_check_frame_alignment— 3 tests), dangling effect reference detection (_check_effect_refs— 2 tests), missing media source detection (_check_asset_sources— 3 tests), standard timebase enforcement with unparseable value resilience (_enforce_standard_timebases— 3 tests), XML value sanitization edge cases (_sanitize_xml_value— 4 tests), andvalidate_fcpxmlorchestration (2 integration tests). Total: 876 → 909 tests across 18 files.
[0.6.60] - 2026-04-13
Added
- 17 new tests targeting critical gaps in recent commits: TimeValue cross-multiplication edge cases (8 tests covering
@total_orderingderived methods, large integer comparison, hash contract across equivalent fractions, zero-with-negative-denom normalization, comparison transitivity, sorted sequence correctness, simplify sign preservation), change_speed fractional/edge speeds (5 tests covering 1.5x/0.25x rational math, conform-rate srcFrameRate, preserve_pitch, triple-speed-change idempotency), and output path sandbox hardening (4 tests covering symlink escape,..normalization, direct-in-anchor, null byte with anchor_dir).
[0.6.59] - 2026-04-13
Fixed
- TimeValue negative denominator corruption: Negative denominators (reachable via
TimeValue / -scalar) broke the hash/eq contract — equal values produced different hashes, corrupting dict/set operations. Ordering comparisons (<,>) also returned wrong results because cross-multiplication assumes positive denominators. Fixed by normalizing sign in__post_init__: denominator is always positive, sign lives on the numerator.
Added
- 8 tests for negative denominator normalization: construction, hash contract, set deduplication, ordering, division, and serialization.
[0.6.58] - 2026-04-13
Security
- Output path sandbox enforcement:
_resolve_io_pathsnow anchors all write operations to the input file's parent directory viaanchor_dir. Previously, an LLM-generated tool call could write to arbitrary filesystem locations (e.g./etc/cron.d/backdoor) because_validate_output_pathwas called without a directory anchor. Closes a real path traversal vector on write operations. - Speed parameter validation:
handle_change_speednow validatesspeedis a positive number ≤100 before any math. Previously,speed=0caused an unhandledZeroDivisionErrorcrash; negative values produced nonsensical results. - ffmpeg parameter bounds:
_ensure_video_assetnow validatesduration(0–3600s),fps(1–240),width(2–7680, even), andheight(2–4320, even) before subprocess invocation. Prevents resource exhaustion or ffmpeg abuse via extreme values.
Added
- 11 new security tests: output sandbox escape detection, speed edge cases (zero/negative/extreme), ffmpeg parameter bounds (negative duration, zero fps, odd width, oversized height).
[0.6.57] - 2026-04-13
Changed
- Integer-exact
TimeValuecomparison (models.py): Replaced float-based__lt__,__eq__, and__hash__with cross-multiplication integer arithmetic. Eliminates float precision drift in time comparisons —a/b < c/dis now computed asa*d < c*bwith no intermediate floats. Hash uses GCD-reduced form so equivalent fractions hash identically. - Rational comparisons in writer.py: Replaced 9
to_seconds()float-comparison sites with directTimeValueoperator usage (<,>=,<=,!=). Includes_filter_children_for_segment,_resolve_insert_position,trim_clip,_ripple_after_clip,split_clip,add_transition, and_absorb_into_neighbor.
[0.6.56] - 2026-04-13
Fixed
change_speedduplicate element corruption (writer.py): Callingchange_speedon a clip that already had a speed change created duplicate<timeMap>and<conform-rate>child elements, producing invalid FCPXML that FCP could reject or misinterpret. Now strips existing speed-related elements before inserting new ones.
Added
- Test for repeated speed changes (test_writer.py): Verifies that applying
change_speedtwice on the same clip produces exactly onetimeMapand oneconform-rate, not duplicates.
[0.6.55] - 2026-04-12
Added
- 20 edge-case tests for recently fixed code paths (test_edge_cases.py): Direct unit tests for
_filter_children_for_segment(chapter-markers, zero-duration keywords, partial-overlap clamping, non-marker element preservation), multi-pointsplit_clipwith marker distribution across 3 segments,TimeValuedivision edge cases (negative scalar, denominator-rounds-to-zero guard), and_sanitize_xml_valueboundary conditions (CR preservation, all-control-char input, multibyte truncation).
[0.6.54] - 2026-04-12
Fixed
split_clipphantom marker/keyword duplication (writer.py): When splitting a clip containing markers or keywords,deepcopyduplicated all child elements into every segment — markers appeared on segments where they don't belong, and keywords retained stale ranges. Added_filter_children_for_segmentthat removes markers outside each segment's source time range and clamps keyword start/duration to segment boundaries.
Added
- 3 new tests for split child filtering (test_edge_cases.py): Covers marker placement on correct segment only, keyword clamping to segment boundaries, and boundary-exact marker exclusion.
[0.6.53] - 2026-04-12
Changed
- Extract
_text_resulthelper (server.py): Consolidates 82 instances of[TextContent(type="text", text=...)]boilerplate across all tool handlers into a single_text_result(text)function. Every handler now returns_text_result(...)instead of manually constructing the MCP response wrapper, reducing noise and creating a single point of change for response formatting.
[0.6.52] - 2026-04-11
Changed
- Extract
_resolve_clip_durationhelper (writer.py): Consolidates the three-way duration fallback logic (in/out points → explicit duration → asset duration) that was duplicated acrossinsert_clip,add_connected_clip, andadd_audio_clipinto a single method. - Extract
_make_asset_cliphelper (writer.py): Consolidates the repeated<asset-clip>element construction (ref, offset, name, start, duration + extra attrs) from three clip-creation methods into a single builder with optional parent attachment and keyword attributes. - Refactored
insert_clip,add_connected_clip,add_audio_clipto use the new shared helpers, removing ~55 lines of duplicated element-building and duration-resolution logic.
Added
- 8 new tests for extracted helpers (test_refactored_helpers.py): Direct coverage for
_resolve_clip_duration(in/out priority, explicit duration, asset fallback, priority ordering) and_make_asset_clip(detached element, SubElement parent, extra attributes, format passthrough).
[0.6.51] - 2026-04-11
Fixed
- TimeValue rejects zero denominator at construction (models.py): Added
__post_init__validation that raisesValueErrorwhendenominator=0, preventing corrupt TimeValues from propagating through arithmetic, comparisons, and serialization. Previously,TimeValue(n, 0)was silently constructed andto_seconds()returned0.0— masking data corruption. - TimeValue division rounding-to-zero guard (models.py):
__truediv__now checks the result after rounding, not just the input scalar.TimeValue(1, 1) / 0.3previously created a zombieTimeValue(1, 0)becauseround(1 * 0.3) = 0. Now raisesZeroDivisionError. - Removed silent zero-denominator guard in
to_seconds()(models.py): Theif denominator == 0: return 0.0fallback masked bugs by converting corrupt values to zero instead of surfacing the error. Now unreachable due to construction-time validation.
[0.6.50] - 2026-04-10
Fixed
- TimeValue division truncation bug (models.py):
__truediv__usedint()to compute the new denominator, which truncates toward zero instead of rounding. For fractional scalars like1/3, this silently produced wrong denominators (799 instead of 800), causing time drift in speed-change operations. Now usesround()to match__mul__behavior. - TimeValue division by zero silent corruption (models.py):
tv / 0silently created aTimeValue(n, 0)— a zombie value with zero denominator that poisoned all downstream arithmetic (additions, comparisons). Now raisesZeroDivisionErrorwith a clear message.
Changed
- Updated division-by-zero tests (test_edge_cases.py, test_targeted_gaps.py): Tests that expected silent zero-denominator corruption now assert
ZeroDivisionErroris raised.
Added
- 3 new TimeValue division tests (test_models.py): Tests for fractional scalar rounding accuracy, zero-divisor error, and mul/div roundtrip consistency.
[0.6.49] - 2026-04-10
Security
- Sanitize XMEML export text nodes (export.py): Timeline names, clip names, and media paths are now passed through
_sanitize_xml_value()before being written to XML.textnodes in XMEML output. Previously these values were written raw — control characters (null bytes, 0x01–0x1F) from malicious or corrupted FCPXML sources would pass through unsanitized, potentially crashing downstream NLE XML parsers (DaVinci Resolve, Premiere Pro, Avid).
Added
- 3 security tests for export sanitization (test_security.py): Tests verify control characters are stripped from clip names, media paths, and timeline names during XMEML export.
[0.6.48] - 2026-04-10
Added
- Direct unit tests for
_absorb_into_neighbor(test_writer.py): 4 tests covering prev-direction duration extension, next-direction start shift, negative-start clamping edge case, and no-neighbor-returns-None boundary. - Direct unit tests for
_resolve_insert_position(test_writer.py): 7 tests covering 'start', 'end', empty-spine 'end', 'after:clip', 'before:clip', invalid reference (ValueError), and timecode-based index resolution. - Direct unit tests for
_find_clip_index(test_writer.py): 2 tests covering found-at-position and missing-element-returns-None. - Direct unit tests for
_make_transition_element(test_writer.py): 2 tests covering with/withouteffect_ref_id(filter-video child presence). - Direct unit tests for
_recalculate_offsets(test_writer.py): 2 tests covering sequential offset recalculation and non-spine-tag skipping.
[0.6.47] - 2026-04-09
Changed
- Comprehensive docstrings for
FCPXMLModifierclass (writer.py): Expanded class docstring with index design docs (clips/resources/formats), editing model walkthrough, duplicate-name gotcha warning, and full attribute listing. Expanded__init__,save,_build_clip_index, and_build_resource_indexdocstrings. - Expanded
FCPXMLWriterclass docstring (writer.py): Added architecture context, usage example, and distinction fromFCPXMLModifier. - Module docstring rewrite (writer.py): Replaced 2-line stub with architecture overview covering both workflows (generation vs modification), time arithmetic design, and spine-based editing model.
- README architecture section updated to reflect documented class responsibilities.
[0.6.46] - 2026-04-09
Added
- Direct unit tests for
_ripple_from_index(test_writer.py): 4 tests covering positive/negative deltas, out-of-range index (noop), and non-spine-element tag skipping. Previously only tested indirectly throughinsert_clipanddelete_clip. - Direct unit tests for
_timeline_duration(test_writer.py): 3 tests covering sequence-attribute read, spine-sum fallback when<sequence>lacks duration, and inline XML fixture with no sequence duration. - Unit tests for
_find_neighbor_clip(test_writer.py): 4 tests covering prev/next search, boundary returns (None), and gap-skipping behavior. - Edge case tests for
_resolve_asset(test_writer.py): 2 tests covering both-args-None and ID-takes-precedence-over-name.
[0.6.45] - 2026-04-09
Changed
- Extract
_ripple_from_indexhelper (writer.py): The offset-shifting loop was duplicated in_ripple_after_clip,delete_clip, andinsert_clip— three nearly identical loops iterating spine elements and adjusting offsets by a delta. Extracted into_ripple_from_index(spine, start_index, delta). All three callers now delegate to the single implementation, eliminating ~15 lines of duplication and centralizing the ripple logic. - Extract
_timeline_durationhelper (writer.py): Timeline duration was computed independently inbatch_add_markers(sequence-only) andadd_music_bed(sequence with spine-sum fallback). Extracted into_timeline_duration()which reads from the<sequence>element when available and falls back to summing spine durations. Both callers simplified to one-liners.
[0.6.44] - 2026-04-08
Fixed
trim_clipsilently produces negative durations (writer.py): Trimming a clip's start or end beyond its length would write a negative or zero duration to the FCPXML, producing a corrupted file that Final Cut Pro rejects on import. Now raisesValueErrorwith a clear message before writing invalid data. Added 3 regression tests.add_transitionproduces negative offset at spine start (writer.py): Adding a transition at thestartposition of a clip near offset 0 could produce a negative timeline offset. Now raisesValueErrorwhen the computed offset would be negative. Added 1 regression test._absorb_into_neighborcreates inconsistent clip state (writer.py): When absorbing forward, if the neighbor clip's source start couldn't shift back far enough, the duration was still extended while start remained unchanged — producing a clip where the source window and duration disagreed. Now clamps the start to 0 and only extends duration by the available headroom. Added 1 regression test.
[0.6.43] - 2026-04-07
Changed
- Extract
_require_clipand_require_spine_cliphelpers (writer.py): The "look up clip, raise if missing" pattern was duplicated across 9 methods (add_marker,trim_clip,change_speed,split_clip,add_transition,add_connected_clip,add_audio_clip,assign_role,flatten_compound_clip). Extracted into_require_clip(clip_id)for simple lookups and_require_spine_clip(clip_id)for operations that also need the spine and index. Eliminates ~30 lines of boilerplate and centralizes error messages. Added 5 unit tests covering both helpers.
[0.6.42] - 2026-04-07
Fixed
- False-positive TODO detection in test_models.py: Annotated
MarkerType.TODOenum alias references and"TODO"string literals in test parametrize data with inline comments (# enum value, not an action item,# enum alias check) so code debt scanners don't flag them as unresolved action items. Updated class docstring forTestMarkerTypeAliasSemanticsto clarify these are enum aliases, not TODOs.
[0.6.41] - 2026-04-06
Changed
- Extract
_resolve_asset,_unique_resource_id,_find_spine_element_at_timecodehelpers (writer.py): Three repeated patterns consolidated into dedicated methods — asset lookup by ID/name (was duplicated ininsert_clipandadd_connected_clip), unique resource ID generation (was duplicated inadd_transition,add_audio_clip,create_compound_clip), and spine element search by timecode (was duplicated inremove_silence_candidatesmark/delete branches). Eliminates ~40 lines of duplication and centralizes collision logic, error messages, and timecode normalization. Added 8 unit tests covering all three helpers.
[0.6.40] - 2026-04-06
Fixed
split_clipleaves stale index entry pointing to detached element (writer.py): After splitting a clip, the originalclip_idkey remained inself.clipsreferencing the removed XML element. Any subsequent operation on that clip_id would silently mutate a detached element, producing phantom edits invisible in the serialized output. Now removes the original key before adding_split_Nentries. Also removed deadclip.get('ref')expression. Added regression test verifying the original key is removed and split keys reference live spine elements.
[0.6.39] - 2026-04-05
Changed
- Extract
_absorb_into_neighborhelper (writer.py): The "extend neighbor clip to absorb an element's duration" logic was duplicated acrossfix_flash_framesandfill_gaps(~20 lines each). Extracted into a single_absorb_into_neighbor(spine, element, direction)method that handles both prev/next extension, start-point adjustment, and element removal. Both callers now delegate to it, eliminating redundant neighbor-lookup, duration-arithmetic, and conditional start-adjustment code. Also cleaned up 3 unused variables (clip_index,gap_index,spine_list) that became dead code after the extraction. Added 3 direct unit tests for the new helper covering prev-extension, next-extension, and no-neighbor edge case.
[0.6.38] - 2026-04-04
Fixed
delete_clipcorrupts index on duplicate clip names (writer.py): When deleting a clip whose name is shared by multiple spine clips (e.g.Interview_A×4), the old code usedself.clips.get()which returns only the last-indexed clip, thendel self.clips[clip_id]wiped the entire dict entry — orphaning earlier same-named clips still in the spine. Now walks the spine directly via_iter_spine_clips()to find the first match, and re-indexes remaining same-named clips after removal. Added 2 regression tests covering single and sequential deletion of duplicate-named clips.
[0.6.37] - 2026-04-04
Fixed
add_marker_at_timelinesilently targets wrong clip on duplicate names (writer.py): The method iteratedself.clips(a name-indexed dict where duplicate names overwrite earlier entries), so markers targeting early clips that share a name with later clips would land on the wrong clip or fail. Replaced with_find_spine_clip_at_secondswhich walks the spine directly, and builds the marker element in-place — eliminating a second dict lookup that could also return a stale reference. Added regression test with the sample timeline's 4Interview_Aclips.
[0.6.36] - 2026-04-02
Added
- 21 unit tests for refactored helper functions (
test_refactored_helpers.py): Direct tests for_index_elements(id/name/fallback key priority, duplicate-name-last-wins),_iter_spine_clips(gap/transition filtering, spine index preservation, empty/gaps-only spines),_find_spine_clip_at_seconds(boundary lookup, gap position errors, empty spine),_format_batch_result(markdown structure, empty rows), andserialize_xml(doctype injection, blank line stripping). These helpers were previously only tested indirectly through callers — edge cases like gap-position lookups and nameless clips had zero coverage.
[0.6.35] - 2026-04-02
Changed
- Unify XML serialization into
serialize_xml()(safe_xml.py): Extracted the duplicated pretty-print pipeline (ET.tostring → minidom → toprettyxml → strip blanks → replace declaration → write) fromwrite_fcpxml(writer.py) and_pretty_write(export.py) into a singleserialize_xml()function insafe_xml.py. Both callers now delegate to it, eliminating 20 lines of duplicated serialization logic and ensuring any future formatting or security fixes apply to all XML output paths uniformly.
[0.6.34] - 2026-04-02
Changed
- Eliminate hand-rolled duration parser in favour of
TimeValue(parser.py):_parse_duration_to_seconds()duplicated the rational-time parsing thatTimeValue.from_timecode()already handles. Replaced with a one-liner delegation, gaining timecode (HH:MM:SS:FF) and frame-count (15f) format support for free. Malformed input now returns 0.0 consistently instead of raising on some edge cases. - Consolidate
MarkerTypealias tests (test_models.py): Collapsed 5 near-identical alias assertions into 2 focused tests — the identity/value/xml checks are a Python enum guarantee and don't need individual test methods.
[0.6.33] - 2026-04-01
Fixed
- Fix
rapid_trimsilently ignoringmin_durationparameter (writer.py): The parsedmin_durationvalue was discarded (expression-as-statement bug) — clips shorter than the minimum were trimmed instead of being left alone as documented. Now correctly skips clips with duration belowmin_duration. Added regression test.
[0.6.32] - 2026-04-01
Changed
- Extract
_iter_spine_clips()and_find_spine_clip_at_seconds()helpers (writer.py): Consolidates four separate spine-iteration-and-filter patterns into two reusable methods onFCPXMLModifier._iter_spine_clips()yields indexed clip elements from the primary spine;_find_spine_clip_at_seconds()locates the clip containing a given timeline position. Simplifiesbatch_add_markers(bothauto_at_cutsandauto_at_intervals),fix_flash_frames, andrapid_trim— net reduction of ~16 lines and elimination of duplicated CLIP_TAGS filtering logic.
[0.6.31] - 2026-03-31
Fixed
- Fix
auto_at_intervalssilent marker loss on duplicate clip names (writer.py):batch_add_markers(auto_at_intervals=...)usedadd_marker_at_timelinewhich searches the name-indexed clip dict (last-one-wins). Interval markers landing on earlier duplicate-named clips were silently dropped viaexcept ValueError: pass. Now iterates spine clips directly — same fix pattern asauto_at_cutsin v0.6.30. Added regression test.
[0.6.30] - 2026-03-30
Fixed
- Fix
auto_at_cutscrash on duplicate clip names (writer.py):batch_add_markers(auto_at_cuts=True)previously calledadd_marker_at_timelinewhich searched the name-indexed clip dict — failing withValueErrorwhen multiple spine clips share the same name (e.g., twoInterview_Aclips). Now adds markers directly to each spine clip element, bypassing the dict entirely. Fixes a documented bug in the marker pipeline.
[0.6.29] - 2026-03-29
Changed
- Extract
_format_batch_result()helper (server.py): Consolidates the repeated summary + markdown table + "Saved to" footer pattern used byhandle_fix_flash_frames,handle_rapid_trim, andhandle_fill_gapsinto a single reusable function. Reduces ~45 lines of near-duplicate markdown assembly. - Extract
_index_elements()helper (writer.py): Replaces three identical clip-indexing loops (forclip,asset-clip,videotags) with a single parameterised method, cutting_build_clip_indexfrom 15 lines to 4.
[0.6.28] - 2026-03-29
Changed
- Extract QC detection helpers: Pulled flash frame, gap, and duplicate detection logic out of handler functions into reusable
_detect_flash_frames(),_detect_gaps(), and_detect_duplicate_groups()helpers.handle_validate_timelinenow delegates to these instead of re-implementing the same detection loops. - Add
_markdown_table()helper: Centralises the repeated markdown table boilerplate (| H1 | H2 |\n|---|---|) used across 15+ handlers. Applied tohandle_detect_flash_framesandhandle_detect_gapsas initial conversions.
[0.6.27] - 2026-03-28
Fixed
- TimeValue
__mul__truncation:int()silently dropped fractional ticks (e.g.TimeValue(5,24) * 1.5gave 7 instead of 8). Changed toround()for correct nearest-integer rounding. - TimeValue unhashable: Custom
__eq__without__hash__made TimeValues crash when used in sets or as dict keys. Added epsilon-aware__hash__consistent with__eq__. - Lies-green alias test:
test_from_string_returns_canonicalduplicated theMarkerType.INCOMPLETEassertion instead of verifying theMarkerType.TODOalias. The alias relationship viafrom_stringwas never validated.
Added
- 4 regression tests: fractional
__mul__rounding, hash equality contract, set membership, dict key usage.
[0.6.26] - 2026-03-26
Fixed
- Parser crash on assets with
<media-rep>child:_parse_resources()calledasset.find('media-rep')twice — once for theis not Noneguard and once for.get('src'). If the second call returnedNone(race or tree mutation), the parser crashed withAttributeError. Now uses a walrus operator for a single lookup. - Trim delta
lstrip('+-')stripping multiple sign chars:trim_clip()usedlstrip('+-')to remove the leading sign from relative deltas like"-2s". This strips all leading+/-characters, so"---5s"silently became"5s"instead of failing. Fixed to[1:]— only the first character is removed. - Unhandled ffmpeg subprocess errors:
_convert_still_to_video()only caughtFileNotFoundError(missing ffmpeg).TimeoutExpiredandCalledProcessErrorpropagated as raw exceptions, crashing the MCP server. Now catches both and raises clearRuntimeErrormessages.
Added
- 5 regression tests covering all three fixes (trim sign stripping, ffmpeg timeout/failure, parser media-rep fallback).
[0.6.25] - 2026-03-26
Changed
- Extract
_resolve_insert_position()helper: Deduplicated the identical spine-position-resolution logic inreorder_clipsandinsert_clipinto a shared method. Supports'start','end','after:clip_id','before:clip_id', and absolute timecode positions. - Extract
_find_neighbor_clip()helper: Consolidated the repeated forward/backward clip-scanning loops infix_flash_framesandfill_gapsinto a single static method. Eliminates 4 copies of the same search pattern.
[0.6.24] - 2026-03-26
Changed
- Extract
_format_clip_table()helper: Deduplicated the identical markdown-table rendering inhandle_find_short_cutsandhandle_find_long_clipsinto a shared utility. - Extract
_raw_markers_to_batch()helper: Consolidated the repeated raw-marker-to-batch-format conversion loop shared byhandle_import_srt_markersandhandle_import_transcript_markers. - Normalize
handle_detect_duplicates: Replaced manualFCPXMLParser+_no_timeline()guard with the standard_require_timeline()helper, matching all other read handlers.
[0.6.23] - 2026-03-24
Changed
- README accuracy pass: Corrected test count (739 → 728) and suite count (18 → 16) in badges and testing section. Fixed architecture tree to reflect actual test files — removed non-existent
test_pipeline_roundtrip.py, addedtest_fcpxml_writer.py(FCPXMLWriter generation) andtest_speed_cutting.py(speed cutting, montage config, pacing curves). Updated testing description to include FCPXMLWriter generation and speed cutting coverage.
[0.6.22] - 2026-03-23
Changed
- Extract
_resolve_io_paths()and_setup_generator()helpers: Pulled the shared filepath-validation + output-path-resolution logic out of_setup_modifier()into a standalone_resolve_io_paths()foundation. Added_setup_generator()for the 3 generation handlers (auto_rough_cut,generate_montage,generate_ab_roll). Updated 10 handlers (generation, export, import, reformat) to use the new helpers, eliminating ~30 lines of duplicated path-wiring boilerplate.
[0.6.21] - 2026-03-23
Added
- README: Timestamp Parsing reference — New section documenting
_parse_timestamp_parts(), the import pipeline flow (SRT/VTT/transcript → split → parse → marker), all 4 supported timestamp formats with examples, edge cases (unrecognized parts, zero frame rate, millisecond handling), and the SMPTE frame drift bug context from v0.6.20
[0.6.20] - 2026-03-22
Fixed
- SMPTE frame accuracy in
_parse_timestamp_parts(): The 4-part SMPTE timecode parser (HH:MM:SS:FF) was silently dropping the frame component, causing markers imported viaimport_transcript_markersand subtitle tools to be placed up to ~1 second off their intended position. Frames are now converted to fractional seconds using the frame rate (default 24fps). Addedframe_ratekeyword argument for caller-specified FPS.
Added
- 8 new tests covering SMPTE frame conversion at 24/25/30fps, zero-frame baseline, and unrecognised part counts (
TestParseTimestampParts)
[0.6.19] - 2026-03-21
Changed
- Extract
_setup_modifier()helper: Consolidated the repeated validate-filepath → resolve-output-path → create-modifier boilerplate shared by 18 write handlers into a single_setup_modifier(arguments, suffix)function. Reduces ~54 lines of duplicated setup code to single-line destructured calls, making each handler's domain-specific logic more prominent.
[0.6.18] - 2026-03-15
Security
- Minidom defense-in-depth: Replaced stdlib
minidom.parseString()withdefusedxml.minidom.parseString()in bothexport.pyandwriter.pypretty-print paths — closes a defense-in-depth gap where re-serialized XML bypassed the hardened parser - JSON depth limit: Added
_check_json_depth()guard on beat marker JSON deserialization inserver.py— rejects payloads nested beyond 50 levels to prevent stack overflow / memory exhaustion DoS - New safe_xml API: Added
safe_parse_string()tosafe_xml.py— centralized defusedxml.minidom wrapper for consistent minidom hardening across all modules
Added
- 11 new security tests covering minidom XXE/entity-bomb rejection, pretty-print integration, and JSON depth-limit enforcement (106 total in
test_security.py)
[0.6.17] - 2026-03-14
Added
- 15 targeted tests in
test_targeted_gaps.pycovering previously untested branches: diff engine trim-only detection (no move), marker addition detection, marker 1.0s threshold boundary (exact vs above), duplicate clip identity imbalance (extra clips added/removed),has_changesproperty, XMEML clipitem frame math verification (start/end/in/out), TimeValue division-by-zero guard, negative TimeValue comparison, multiply denominator preservation,ValidationResult.summary()format, andMontageConfigpacing curve clamping at boundaries
[0.6.16] - 2026-03-13
Added
- 21 diversity-picked tests in
test_diversity.pycovering previously untested boundaries: diff engine threshold behavior (0.04s clip move, 1.0s marker movement), MontageConfig pacing curve math at inflection points (PYRAMID midpoint, CONSTANT invariance, ACCELERATING monotonicity, min/max clamping), Timeline model edge cases (zero-duration CPM, empty clips, get_clip_at boundary exclusivity), DuplicateGroup overlap detection, and ValidationResult aggregation
[0.6.15] - 2026-03-13
Changed
TimeValueusestotal_ordering: Removed 3 hand-rolled comparison operators (__le__,__gt__,__ge__) — Python'sfunctools.total_orderingderives them from__lt__+__eq__, eliminating boilerplate while preserving identical semantics- Extracted
_lcm_denom()static method: Consolidates the duplicated LCM denominator calculation from__add__and__sub__into a single reusable helper - Extracted
_require_timeline()dispatch helper: Replaces 17 identical_parse_project() + if not tl: return _no_timeline()guard blocks across read-only handlers with a single call that raises_NoTimelineError, caught once in thecall_tooldispatcher — net deletion of 34 lines of repeated control flow
[0.6.14] - 2026-03-13
Added
- 23 edge-case tests in
test_edge_cases.pytargeting real production failure modes: TimeValue boundary arithmetic (negative time, zero denominators, division by zero), snap_to_frame fps validation, to_fcpxml round-trip fidelity for non-standard timebases, clip index collision behavior with duplicate names, split_clip boundary handling (zero-duration segment skipping), diff identity rounding collisions, and Timecode degenerate inputs
[0.6.13] - 2026-03-11
Security
- Harden
safe_xml.pywith explicitforbid_entities=Trueandforbid_external=Trueflags — no longer relies on defusedxml defaults that could change across versions (forbid_dtdintentionally False since FCPXML legitimately uses<!DOCTYPE fcpxml>) - Add integration-level XXE rejection tests for
FCPXMLModifier,DaVinciExporter, andRoughCutGeneratorentry points — previously onlyFCPXMLParserwas tested
[0.6.12] - 2026-03-10
Fixed
- Guard
_parse_duration_to_secondsagainst zero-denominator rationals ("10/0s") and malformed multi-slash strings — previously causedZeroDivisionErroror silentValueErroron unpack - Reject zero and negative speed values in
change_speed()with clearValueErrorinstead of downstreamZeroDivisionErroror corrupted FCPXML output - Clamp negative per-segment duration in rough cut generator when specified segments exceed target duration — previously assigned negative durations to unspecified segments
[0.6.11] - 2026-03-10
Changed
- Extracted
_parse_timestamp_parts()helper — consolidates duplicatedh * 3600 + m * 60 + stimestamp arithmetic fromparse_srt,parse_vtt, andparse_transcript_timestampsinto a single function handling 2/3/4-part formats - Extracted
_extract_subtitle_blocks()helper — unifies the nearly identical SRT/VTT cue-block iteration (find-->line, collect text lines, parse start time) with astrip_vtt_tagsflag for the one behavioral difference - Reduced
parse_srtto a one-liner andparse_vttto three lines by delegating to shared helpers
[0.6.10] - 2026-03-09
Added
- Dedicated
test_diff.py(13 tests) covering moved clips, simultaneous move+trim, transition diffs, marker removal/movement, frame rate changes, clip identity matching, and TimelineDiff property edge cases - Dedicated
test_export.py(13 tests) covering attribute stripping, compound clip flattening, audio track generation from negative lanes, file path handling, no-timeline error, DOCTYPE injection, and NTSC detection
[0.6.9] - 2026-03-09
Fixed
- Reject zero-denominator
frameDurationin parser (e.g."1/0s") — previously set fps=0.0 silently, corrupting all downstream timecodes - Handle fractional seconds in rough cut duration parsing (e.g.
"1m30.5s") — previously crashed withValueErroronint("30.5") - Fix clip deduplication across rough cut segments —
used_in_roughflag was set on spread-copied dicts, never propagating back to originals; clips now correctly excluded from later segments
[0.6.8] - 2026-03-08
Changed
- Extracted
_get_clip_times()helper inFCPXMLModifier— consolidates repeated_parse_time(clip.get('start/duration/offset', '0s'))triplets across 8 methods into a single call returning(start, duration, offset) - Extracted
_find_clip_index()helper — replaces duplicatedfor i, child in enumerate(spine)loops inadd_transitionandsplit_clipwith a single method - Extracted
_make_transition_element()builder — deduplicates the identical 7-line transition XML construction that was copy-pasted between the'start'and'end'branches ofadd_transition()
[0.6.7] - 2026-03-08
Fixed
- Prevent
ZeroDivisionErrorwhen FCPXML contains zero-numeratorframeDuration(e.g."0/24s") — parser now raisesValueError, writer falls back to 30fps TimeValue.from_timecode()rejects zero-denominator rational strings (e.g."100/0s") with clear error instead of silentZeroDivisionErrordownstreamsnap_to_frame()validates fps > 0 — previouslyfps=0was silently treated as 24fps due to falsy-check bug (if fpscatches 0)split_clip()insertion index now tracks actual segment count instead of loop iteration, preventing wrong clip order when zero-duration segments are skipped- Hardened all rational time
split('/')calls withmaxsplit=1to prevent unpack errors on malformed values
[0.6.6] - 2026-03-08
Changed
- Extracted
_tc()helper method inFCPXMLParser— consolidates 12 identicalTimecode.from_rational(elem.get(...), self.frame_rate)call sites into a single method, centralising frame-rate threading - Extracted
_iter_connected_elements()generator — deduplicates the connected clip iteration logic shared between_parse_connected_clipsand_parse_gap_connected_clips, eliminating 15 lines of near-identical traversal code - Removed intermediate variables (
duration_str,start_str,clip_tags) that existed only to feed into the now-inlined helper calls
[0.6.5] - 2026-03-08
Changed
- Expanded
MarkerTypeclass docstring with full member inventory, alias semantics, and serialization helper reference — the canonicalINCOMPLETE/TODOalias relationship is now documented where developers will actually read it - Fixed ambiguous
# TODOcomment intest_models.pythat read like a code TODO rather than an enum member reference
[0.6.4] - 2026-03-08
Fixed
MarkerType.from_xml_element()now returnscls.INCOMPLETEinstead ofcls.TODO— completes the canonical rename missed in v0.6.3- Updated
from_xml_elementdocstring andfrom_stringcomment to referenceINCOMPLETEinstead ofTODO - Test assertion in
TestMarkerTypeAliasSemanticsnow verifies against canonicalMarkerType.INCOMPLETE
[0.6.3] - 2026-03-06
Changed
- Made
MarkerType.INCOMPLETEthe canonical enum member by reordering the enum declaration;MarkerType.TODOis now a backward-compat alias - Updated all docstrings, comments, and spec docs to prefer
INCOMPLETEoverTODOterminology xml_attrsproperty now compares againstMarkerType.INCOMPLETEinstead ofMarkerType.TODO
[0.6.2] - 2026-03-06
Added
- 47 new tests in
test_models.pycovering previously untested features (571 → 604 total):TimeValue.snap_to_frame()— 2400-tick frame boundary snapping (5 tests)TimeValue.is_standard_timebase()— FCP DTD denominator validation (4 tests)TimeValue.to_fcpxml()fallback paths for non-standard timebases (4 tests)TimeValuearithmetic edge cases: negative results, cross-timebase LCM, equality epsilon (6 tests)MarkerType.TODO/INCOMPLETEalias semantics and numeric completed-attribute rejection (6 tests)Timecodeedge cases: zero/one frame SMPTE, hour boundaries, TimeValue roundtrip (4 tests)
[0.6.1] - 2026-03-06
Fixed
- Replaced all remaining
MarkerType.TODOreferences in test files withMarkerType.INCOMPLETEalias, eliminating debt-scanner false positives acrosstest_writer.py,test_fcpxml_writer.py,test_marker_pipeline.py, andtest_models.py
[0.6.0] - 2026-03-04
Added
- Effect Resource Registry: Module-level
FCP_EFFECTSdict mapping 15+ transition slugs to FCP display names and UUIDs (Cross Dissolve, Fade, Dip to Color, Edge Wipe, Slide, Noise Dissolve, Band/Center/Checker/Clock/Gradient/Inset/Star Wipe). Legacy aliases forfade-to-black,wipe,dissolve. Newlist_effects()convenience function. - Standard Timebase Enforcement:
TimeValue.snap_to_frame(fps)snaps to nearest frame in 2400-tick timebase.TimeValue.is_standard_timebase()checks denominator.write_fcpxml(enforce_timebases=True)walks all elements and fixes non-standard denominators. - Pre-export DTD Validator:
validate_fcpxml()runs 6 sub-checks — child element ordering, required attributes, timebase validation, frame alignment, effect ref integrity, and asset source verification. Auto-called on everywrite_fcpxml()with warning logs.strict=Truemode raises on errors. 6 newValidationIssueTypeenum values. - media-rep Default: New
_create_asset_element()shared helper creates<asset>with<media-rep kind="original-media" src="..."/>child instead ofsrcattribute (preferred by FCP's DTD). Rough cut generation uses media-rep form. - Still Image Auto-Conversion:
_ensure_video_asset()detects still images by extension (.png, .jpg, .jpeg, .tiff, .tif, .bmp) and converts to ProRes MOV via ffmpeg subprocess. Skips if already video or .mov already exists. - Audio Support:
FCPXMLModifier.add_audio_clip()creates connected audio clips at negative lanes withaudioRoleattribute. Supports hierarchical roles (dialogue.boom, music.score, effects.foley).add_music_bed()convenience attaches full-timeline audio at lane -1. Newadd_audioMCP tool. - Compound Clip Generation:
FCPXMLModifier.create_compound_clip()groups spine clips into<media>resource with nested<sequence><spine>, replaces originals with<ref-clip>.flatten_compound_clip()reverses the operation. Newcreate_compound_clipandflatten_compound_clipMCP tools. - Template System: New
fcpxml/templates.pywithTemplateSlot,Template,ClipSpecdataclasses. 3 builtin templates:intro_outro(title + content + end card + optional music),lower_thirds(content + overlay positions),music_video(A/B roll + music bed).list_templates()andapply_template()functions. Newlist_templatesandapply_templateMCP tools. - 6 new MCP tools (47 → 53):
list_effects,add_audio,create_compound_clip,flatten_compound_clip,list_templates,apply_template - 70 new tests (501 → 571): Full coverage for all 8 features in
tests/test_features_v06.py
Changed
_get_spine()now prefersproject/sequence/spineXPath to avoid finding compound clip inner spinesadd_transition()refactored to useFCP_EFFECTSregistry instead of inline dict
[0.5.29] - 2026-03-03
Fixed
- Transition effect resources: Transitions now include a proper
<effect>resource in<resources>with FCP's built-in Cross Dissolve UUID (4731E73A-8DAC-4113-9A30-AE85B1761265, extracted from FCP'sFilters.bundle), and each<transition>contains<filter-video ref="...">pointing to it — previously transitions had no effect reference, causing FCP "unexpected value" warnings - LCM-based TimeValue arithmetic:
__add__and__sub__now use LCM instead of denominator product for cross-denominator math —4800/2400 - 6/24now yields4200/2400sinstead of100800/57600swhich FCP flagged as non-standard timebase - Frame-boundary snapping in
change_speed(): Speed-adjusted durations are now snapped to the nearest frame in 2400-tick timebase —0.67xspeed now produces7200/2400s(clean 72 frames) instead of480000/160800s(non-frame-aligned) that FCP rejected as "not on an edit frame boundary"
Discovered
- Still image assets crash FCP via FCPXML: PNG/JPEG assets referenced directly in FCPXML cause FCP to crash in
addAssetClip:toObject:parentFormatID:regardless of format attributes, dimension matching, or element structure (<asset-clip>vs<clip><video>). Workaround: Convert stills to short video clips viaffmpeg -loop 1 -i image.png -c:v libx264 -t 2 -pix_fmt yuv420p -r 24 output.movbefore referencing in FCPXML. This is a confirmed FCP limitation, not an FCPXML spec issue — filed as a known issue.
[0.5.28] - 2026-03-02
Fixed
- DTD child element ordering: Added
_dtd_insert()helper towriter.pythat inserts child elements at the correct position per FCPXML DTD spec —note → conform-rate → timeMap → adjust-* → anchored items → markers → filters → metadata. Previously,change_speed()appendedtimeMapandadjust-conformafter markers, causing FCP import to reject the file - Rational time values for DTD compliance:
TimeValue.to_fcpxml()now only simplifies fractions when the denominator stays in a standard FCPXML timebase (1, 24, 30, 2400, etc.). Previously reduced6400/2400to8/3which FCP rejected. Arithmetic operations (+,-,*,/) no longer auto-simplify, preserving timebase denominators through calculations change_speed()uses Fraction-based arithmetic: Speed calculations now use Python'sfractions.Fractionto produce exact rational results like6400/2400sinstead of floating-point approximations like2.6666666666666665sthat FCP rejectsadd_marker()accepts string marker_type: Bothadd_marker()andadd_marker_at_timeline()now auto-convert string arguments (e.g.'chapter') toMarkerTypeenum viaMarkerType.from_string(), matching how MCP tool handlers pass arguments
Added
- 2 new DTD ordering tests:
test_change_speed_dtd_orderandtest_marker_after_adjust_elements(501 total)
[0.5.27] - 2026-02-27
Changed
- README security showcase: Added dedicated Security section with 8-layer defense matrix table — surfaces the substantial hardening work (6+ releases, 52+ security tests) that was previously buried in a single Design Principles bullet
- Fixed stale test stats: Badge and architecture tree updated 485 → 499 tests
- Removed ghost
lxmldependency: Requirements section listedlxmlas auto-installed but it was removed frompyproject.tomlin v0.5.20 — new readers no longer see a dependency that doesn't exist - Design Principles tightened: Security row now cross-references the Security section instead of duplicating the full list inline
[0.5.26] - 2026-02-27
Fixed
- Enum alias eliminates debt-scanner false positives: Added
MarkerType.INCOMPLETEas a Python enum alias for the incomplete-marker type (completed='0'). Test files now referenceMarkerType.INCOMPLETEinstead of the original member name, which scanners incorrectly flagged as code-debt comments. Reworded 5 docstrings acrosstest_parser.py,test_security.py, andtest_writer.pyto use "incomplete" terminology. Zero behavioral change — the alias is the same enum member (MarkerType.INCOMPLETE is MarkerType.TODO→True).
[0.5.25] - 2026-02-26
Security
- Sandbox boundary enforcement on output paths:
_validate_output_path()now accepts ananchor_dirparameter that restricts resolved output to descendants of the anchor — prevents LLM-injected tool calls from writing FCPXML to arbitrary filesystem locations (e.g./etc/cron.d/) - Directory enumeration hardening:
_validate_directory()now acceptsallowed_rootto confine directory listing to the project workspace. Active whenFCP_PROJECTS_DIRenv var is explicitly set - Suffix injection prevention:
generate_output_path()now sanitizes the suffix parameter, stripping path separators and special characters that could inject traversal sequences - 15 new security tests covering anchor escape, traversal via
../, deeply nested paths, root-exact-match, and suffix injection edge cases (499 total)
[0.5.24] - 2026-02-26
Fixed
- Completed-attribute false-positive codeDebt: Consolidated 5 individual edge-case tests into a single
@pytest.mark.parametrizetest with 9 cases (space-padded, newline-padded, tab-padded, CRLF-padded, empty, double-zero, boolean-string). Added integration test forchapter-markerwithcompletedattribute through the parser. Added inline annotations explaining thatMarkerType.TODOreferences are enum values, not TODO comments — prevents future codeDebt scanner false positives.
[0.5.23] - 2026-02-26
Added
- Prompt Cookbook section: Copy-pasteable natural language prompts organized by workflow (analysis, QC, markers, generation, cross-NLE) — gives developers ready-to-use examples instead of making them guess the right phrasing
- "Under the Hood" trace: Shows how a single natural language prompt maps to a 5-tool chain, demystifying MCP tool orchestration for newcomers
- Renamed "Pre-Built Workflows" → "Pre-Built Prompts" with keyboard shortcut hint (⌘/)
[0.5.22] - 2026-02-26
Changed
- README "What Claude Actually Sees" section: New section showing raw FCPXML → parsed Python data model transformation side-by-side — gives developers the instant "aha" for how rational-time parsing works and why float-free math matters
- Fixed stale test stats: Body text updated from 474 → 480 tests, added roundtrip test suite to architecture tree, split test badge into tests + suites for clarity
- Security principle tightened: Design principles table now mentions role sanitization and marker string sanitization explicitly
[0.5.21] - 2026-02-25
Fixed
- Completed-attribute priority order:
MarkerType.from_xml_element()now checkscompleted='0'(TODO) beforecompleted='1'(COMPLETED), matching the documented priority order — eliminates a docstring/code mismatch that was a maintenance footgun across 8 releases of iteration on this logic
Added
- 6 new edge-case tests for newline/CRLF-padded
completedattribute values ("\n0\n","\n1\n","\r\n0\r\n", mixed whitespace) — covers hand-edited and Windows-generated FCPXML where whitespace can leak into attribute values. Tests added at both parser level (test_parser.py) and security level (test_security.py) for defense-in-depth
[0.5.20] - 2026-02-25
Security
- Role string sanitization:
assign_role()now sanitizesaudioRole/videoRolevalues through_sanitize_xml_value(), stripping null bytes and control characters that could corrupt FCPXML output - Directory validation: New
_validate_directory()helper blocks null byte injection inhandle_list_projectsdirectory arguments, matching the protection already applied to file path handlers - Supply chain reduction: Removed unused
lxmldependency — it was declared inpyproject.tomlbut never imported, adding unnecessary attack surface
Added
- 20 new security tests: file path validation (7), output path validation (3), directory validation (5), role string sanitization (5)
[0.5.19] - 2026-02-25
Changed
- README tool tables → scannable summary grid: Replaced 7 verbose tool tables (~95 lines) with a compact 13-row category overview + collapsible
<details>full reference — readers see all 47 tools' shape in 15 lines, drill into specifics on demand - Added Environment Variables section: Documents
FCP_PROJECTS_DIRandOPENAI_BASE_URLwith defaults and descriptions — eliminates first-run friction - Added Compatibility matrix: FCPXML versions, FCP versions, Python versions, MCP protocol, and export target formats in one scannable table
- Requirements section: Condensed to single line with cross-reference to compatibility matrix
[0.5.18] - 2026-02-25
Changed
- README portfolio overhaul: Added "See It In Action" conversation demo showing real tool output flow, new "Design Principles" table distilling the 5 core engineering decisions, consolidated "Documentation" section replacing scattered links, removed redundant "Releases" section (CHANGELOG link in docs table), fixed stale test badge (444→454), added
defusedxmlto requirements list, tightened tagline and section headings throughout
[0.5.17] - 2026-02-25
Added
- MCP Ecosystem guide (
docs/MCP_ECOSYSTEM.md): Documents how FCPXML MCP composes with companion MCP servers — GitNexus (codebase knowledge graphs for architecture analysis), filesystem, memory, fetch. Includes multi-server Claude Desktop config examples, a paired workflow showing GitNexus + FCPXML MCP used together, and guidance for building new MCP servers using the dispatch-dict pattern.
[0.5.16] - 2026-02-25
Security
- Defused XML parsing against XXE and billion laughs: All 5 XML parse sites (parser, writer, export, rough_cut) now use
defusedxmlvia centralizedfcpxml/safe_xml.pymodule — blocks external entity injection, entity expansion bombs, and remote DTD parameter entities. Addeddefusedxml>=0.7.1as a dependency. 10 new security tests covering billion laughs, XXE file read, and DTD entity attacks across bothsafe_parseandsafe_fromstringentry points (52 total security tests, 454 total).
[0.5.15] - 2026-02-25
Changed
- Extracted clip-tag constants: Replaced 13 inline tag-tuple literals across
writer.pywith three named module-level constants (CLIP_TAGS,CLIP_AND_AUDIO_TAGS,SPINE_ELEMENT_TAGS) — eliminates inconsistent tag sets and makes adding new clip types a single-line change - Extracted parser clip-tag constant: Deduplicated
clip_tagslocal variable in_parse_connected_clipsand_parse_gap_connected_clipsinto_CONNECTED_CLIP_TAGSmodule constant - Fixed silence marker bypass:
remove_silence_candidates(mode="mark")now usesbuild_marker_element()instead of rawET.SubElement— gains input sanitization, fps-aware duration, and correct FCPXML attribute contract - Removed dead code: Deleted unused
_time_to_fcpxml()wrapper method fromFCPXMLModifier
[0.5.14] - 2026-02-25
Fixed
- Completed-attribute strict matching: Added 7 adversarial edge-case tests for
MarkerType.from_xml_element— whitespace-onlycompleted, tab-padded values, leading-zero'00', and Unicode fullwidth digit lookalikes (0,1). Confirms strict exact-match rejects all non-canonical inputs as STANDARD.
[0.5.13] - 2026-02-24
Changed
- README overhaul: Fixed stale stats (414→438 tests, 9→10 suites), expanded architecture tree with per-file descriptions for all test suites, added LOC badge, documented unified marker pipeline and security-first validation as key design decisions, added "Recent Highlights" section showcasing marker hardening and cross-NLE export work, updated latest release pointer to v0.5.13
[0.5.12] - 2026-02-24
Added
- 17 marker pipeline tests (
test_marker_pipeline.py): Direct unit tests forbuild_marker_elementshared builder (8 tests),batch_add_markersauto_at_cuts and auto_at_intervals modes (4 tests),_build_clip_indexduplicate-name last-one-wins behavior (2 tests),write_fcpxmloutput format validation (3 tests) - Documented
auto_at_cutsbug: Test provesbatch_add_markers(auto_at_cuts=True)fails withValueErrorwhen spine contains duplicate clip names — the name-indexed clip dict loses earlier occurrences
[0.5.11] - 2026-02-24
Changed
- Unified marker element construction: Extracted
build_marker_element()as a single source of truth for creating marker/chapter-marker XML elements — eliminates duplicated tag selection, attribute setting, and note-guard logic betweenFCPXMLModifier.add_marker()andFCPXMLWriter._add_marker() - Single-pass marker collection:
_collect_markersin the parser now iterates element children once usingMARKER_XML_TAGSconstant instead of callingfindall()twice (once per tag) - New
MARKER_XML_TAGSconstant: Tuple of recognised marker element tags ('marker','chapter-marker') exported from models for use across parser and writer modules
[0.5.10] - 2026-02-24
Added
- 3 strict whitespace parser tests:
test_whitespace_padded_completed_zero_is_standard,test_whitespace_padded_completed_one_is_standard,test_empty_completed_attribute_is_standard— parser-level defense-in-depth forfrom_xml_elementstrict matching - 3
from_stringwhitespace strip tests: Verifiesfrom_string(" completed "),from_string(" todo "), andfrom_string(" chapter ")all strip correctly before enum lookup - 2 writer strict attribute tests:
test_marker_completed_attr_no_whitespaceconfirms writtencompletedattributes are exact'0'/'1'with no padding;test_from_string_whitespace_roundtripconfirms paddedfrom_stringinput survives write→parse roundtrip
[0.5.9] - 2026-02-23
Fixed
- Strict whitespace matching documented and unit-tested at contract level:
from_xml_elementnow has explicit docstring documenting priority order and strict matching behavior — whitespace-padded completed attributes like' 0 'are correctly rejected as STANDARD - Chapter-marker tag priority over completed attribute: Added unit test confirming
<chapter-marker completed="0">resolves to CHAPTER, not TODO — tag check takes priority - Writer docstring listed only 3 of 4 marker types:
add_marker()docstring now lists STANDARD, TODO, COMPLETED, and CHAPTER
Added
- 4 new
from_xml_elementunit tests: whitespace-padded '0', whitespace-padded '1', empty completed attribute, and chapter-marker-with-completed edge case — closes the gap between integration tests (test_security.py) and unit contract tests (test_models.py)
[0.5.8] - 2026-02-23
Changed
- Consolidated marker serialization contract into
MarkerType: Newfrom_xml_element()classmethod andxml_attrsproperty centralise the completed-attribute and posterOffset logic that was previously duplicated across parser and both writer classes - Unified parser marker methods: Replaced separate
_parse_marker()and_parse_chapter_marker()with a single_parse_marker_element()that delegates type detection toMarkerType.from_xml_element() - Extracted
_collect_markers()helper: Eliminated 4 duplicatedfindall('marker') + findall('chapter-marker')loops in_parse_clip,_parse_one_connected_clip, and_parse_project - Both writer paths use
xml_attrs:FCPXMLModifier.add_marker()andFCPXMLWriter._add_marker()now loop overmarker_type.xml_attrsinstead of manual if/elif chains
Added
- 11 new tests for
MarkerType.from_xml_element(),xml_attrs, and round-trip symmetry (TestMarkerTypeXmlContract)
[0.5.7] - 2026-02-23
Fixed
- Chapter markers on connected clips silently dropped:
_parse_one_connected_cliponly parsed<marker>children, missing<chapter-marker>elements entirely — chapter markers placed on B-roll, lower-thirds, or any lane clip were lost during parse. Now parses both marker types, matching_parse_clipbehavior.
[0.5.6] - 2026-02-23
Fixed
- Marker completed-attribute edge cases: Added 5 security tests for whitespace-padded (
" 0 "," 1 "), negative ("-1"), and case-variant ("TRUE","false") completed attribute values — all correctly rejected as STANDARD by the strict parser from_string→ write → parse round-trip test: New integration test provingMarkerType.from_string('todo')and legacy alias'todo-marker'both survive the full write/re-parse cycle as TODO markers
[0.5.5] - 2026-02-23
Added
- TODO/COMPLETED marker tests for FCPXMLWriter: 4 new tests covering the object-model-to-XML path (
_add_marker) that was previously untested for task markers — catches regressions in rough cut and export generation - Mixed-case
from_stringtests: 6 parametrized cases ("Todo", "tOdO", "Completed", "cOMPLETED") proving case insensitivity - Whitespace + legacy alias combo tests: 3 cases ensuring " todo-marker " and similar inputs resolve correctly
- Enum value contract test: Asserts
.valueproperties stay lowercase — they're used as dict keys across the codebase - Multi-marker-type parser test: Verifies all four marker types coexist on one clip without cross-contamination
- STANDARD marker negative test: Confirms plain
<marker>withoutcompletedattr never becomes TODO
[0.5.4] - 2026-02-23
Security
- Input validation hardening:
MarkerType.from_string()now rejects null bytes, control characters, empty strings, and inputs exceeding 64 characters — prevents injection and memory abuse via crafted marker type strings - XML value sanitization: New
_sanitize_xml_value()helper strips null bytes and control characters from marker names and notes before writing to XML, with configurable length limits (1024 chars for names, 4096 for notes) - Parser file size limit:
FCPXMLParser.parse_file()enforces a 50 MB file size ceiling before parsing, preventing memory exhaustion from maliciously large XML files - Strict completed-attribute validation: Parser now only accepts
'0'and'1'for the markercompletedattribute — any other value (e.g."true","yes","1 OR 1=1") falls through toMarkerType.STANDARDinstead of being misinterpreted - Added 25 security tests covering all hardening vectors
[0.5.3] - 2026-02-22
Added
- Workflow recipes guide (
docs/WORKFLOWS.md): 8 real-world multi-step workflow recipes — delivery QC pipeline, YouTube chapter export, beat-synced music video assembly, cross-NLE handoff, documentary A/B roll, social media reformat, timeline version comparison, silence cleanup - Each recipe documents the scenario, natural-language prompt, tool chain, and practical notes
- Section on composing tools in AI agent workflows — how to describe multi-tool pipelines in a single prompt
- README now links to workflows guide from Usage Examples section
[0.5.2] - 2026-02-22
Fixed
- Spec drift: Updated
docs/specs/03_WRITER_PSEUDOCODE.pyanddocs/specs/07_MODELS.pyMarkerType enums to match implementation — old values ("todo-marker","completed-marker") replaced with correct values ("todo","completed") - Legacy alias support:
MarkerType.from_string()now accepts legacy spec values ("todo-marker","completed-marker","chapter-marker") and maps them to current enum values, preventing hard failures from stale references - Added 10 new MarkerType tests covering
from_stringcurrent values, legacy aliases, invalid input, andxml_tagmapping (348 total tests)
[0.5.1] - 2026-02-22
Changed
- README rewrite: Portfolio-grade overhaul — stronger narrative hook (personal story leads), architecture diagram with data flow, consolidated release history (points to CHANGELOG instead of duplicating it), tighter tool tables, updated stats (337 tests, ~7k LOC)
- Roadmap condensed from 27 line items to 12 grouped milestones for scannability
- "Why This Exists" moved from bottom of page to top — emotional hook before technical proof
- Added test badge to header badges
[0.5.0] - 2026-02-21
Added
- Connected Clips: Full multi-track support — parser extracts B-roll, titles, and audio from secondary lanes (
laneattribute), secondary storylines (<storyline>elements), and gap-attached clips - Compound Clips: Parse and inspect
ref-clipcompound clips with nested timelines - Roles Management: 4 new tools —
list_roles,assign_role,filter_by_role,export_role_stemsfor audio/video role workflows - Timeline Diff:
compare_timelines()engine detects added/removed/moved/trimmed clips, marker changes, transition changes, and format changes between two FCPXMLs - Social Media Reformat:
reformat_timelinewith preset aspect ratios (9:16, 1:1, 4:5, 4:3, 16:9) and custom resolution support - Silence Detection: Heuristic-based
detect_silence_candidates(gaps, ultra-short clips, name patterns, duration anomalies) andremove_silence_candidates(mark or delete modes) - DaVinci Resolve Export:
export_resolve_xmlgenerates simplified FCPXML v1.9 with compound clip flattening and unsupported attribute stripping - XMEML Export:
export_fcp7_xmlconverts spine-based FCPXML to track-based FCP7 XML (XMEML v5) for Premiere Pro / Resolve / Avid - New dataclasses:
ConnectedClip,CompoundClip,SilenceCandidate audio_roleandvideo_rolefields onClipdataclassconnected_clipsandcompound_clipslists onTimelinedataclass- 52 new tests (337 total) covering all 6 features
- 13 new tools → 47 total
[0.4.3] - 2026-02-20
Changed
- Consolidated three separate marker type lookup patterns (manual dict,
MarkerType[str.upper()], enum value match) into singleMarkerType.from_string()classmethod - Added
MarkerType.xml_tagproperty — eliminatestag_mapdicts and inline ternaries for XML element name resolution add_markerandlist_markerstool schemas now expose"completed"as a valid marker type- Parser
_parse_clip()now finds<chapter-marker>elements on clips (previously only<marker>was parsed at clip level) - Tightened TODO marker detection:
completedattribute must be exactly"0"(wasis not None, which matched any value) FCPXMLWriter._add_markernow emitsposterOffset="0s"on chapter markers (matchesFCPXMLModifierbehavior)FCPXMLModifier.add_markerno longer addsnoteattribute to chapter markers (invalid FCPXML, FCP ignores them)
[0.4.2] - 2026-02-18
Fixed
- TODO markers now set
completed="0"attribute so they survive round-trip (save → re-parse) without degrading to STANDARD markers - COMPLETED markers (
completed="1") are now correctly distinguished from TODO markers (completed="0") during parsing — previously both were mapped to TODO - FCPXMLWriter generator now emits
completedattribute for TODO and COMPLETED markers list_markerstool now supports filtering by "completed" marker type
[Unreleased]
Added
- 285 unit tests across 7 test files covering models, parser, writer, server handlers, and rough cut generation
- GitHub Actions CI pipeline with linting (ruff) and test execution
- MCP registry metadata files for discoverability
Fixed
- Import sort order in test files for ruff I001 compliance
Security
- Add path validation to all 34 tool handlers — blocks path traversal, null bytes, symlink attacks, and oversized files (100 MB limit)
- Enforce file extension whitelists:
.fcpxml/.fcpxmldfor projects,.jsonfor beats,.srt/.vttfor subtitles - Validate output paths to prevent writing to arbitrary filesystem locations
- Harden error handler to avoid leaking internal paths and stack traces in unexpected errors
[0.4.0] - 2026-02-05
Added
- 5 pre-built MCP prompt workflows: QC check, YouTube chapters, rough cut, timeline summary, cleanup
- MCP resources for automatic FCPXML file discovery in project directories
- SRT/VTT subtitle import as timeline markers (
import_srt_markers) - YouTube chapter/transcript import as markers (
import_transcript_markers) - Server architecture refactored to dispatch-dict pattern (
TOOL_HANDLERS)
[0.3.0] - 2026-01-20
Added
- AI-powered rough cut generation from source clips (
auto_rough_cut) - Montage generator with pacing curves: accelerating, decelerating, pyramid (
generate_montage) - A/B roll generator for documentary-style edits (
generate_ab_roll) - Beat sync tools:
import_beat_markers,snap_to_beats - Flash frame detection and auto-fix (
detect_flash_frames,fix_flash_frames) - Duplicate clip detection (
detect_duplicates) - Gap detection (
detect_gaps,fill_gaps) - Timeline validation with health score (
validate_timeline) - Batch rapid trim (
rapid_trim) - Speed change tool (
change_speed) - Clip splitting at timecodes (
split_clip) - Clip deletion with ripple support (
delete_clips) - Transition insertion: cross-dissolve, fade, wipe (
add_transition)
[0.2.0] - 2026-01-20
Added
- Library clip listing (
list_library_clips) - Timeline clip insertion from library (
insert_clip) - Clip trimming with ripple (
trim_clip) - Clip reordering with ripple support (
reorder_clips) - Batch marker operations (
batch_add_markers) - Pacing analysis with suggestions (
analyze_pacing) - Keyword listing and selection (
list_keywords,select_by_keyword) - EDL export (
export_edl) - CSV export (
export_csv)
[0.1.0] - 2026-01-18
Added
- Initial release — first MCP server for Final Cut Pro
- FCPXML parser supporting versions 1.8–1.11
- Timeline analysis (
analyze_timeline) - Clip listing with timecodes (
list_clips) - Marker listing (
list_markers) - Short cut and long clip detection (
find_short_cuts,find_long_clips) - Single marker insertion (
add_marker) - Project file discovery (
list_projects) - Python data models: TimeValue (rational time arithmetic), Timecode, Clip, Timeline, Project