9.7 KiB
Executable File
FCPXML Structure Reference
How FCPXML represents different elements and what nodes need modification for each editing operation.
Document Structure
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fcpxml>
<fcpxml version="1.11">
<resources>
<!-- Media assets, formats, effects -->
</resources>
<library>
<event name="My Event">
<project name="My Project">
<sequence>
<spine>
<!-- Primary storyline clips -->
</spine>
</sequence>
</project>
</event>
</library>
</fcpxml>
Key Elements
<asset> - Media Reference
<asset id="r1" name="Interview_01" src="file:///path/to/Interview_01.mov"
start="0s" duration="300s" hasVideo="1" hasAudio="1">
<format id="r2" name="FFVideoFormat1080p30"/>
</asset>
<clip> - Timeline Clip
<clip name="Interview_01" offset="0s" duration="120s" start="30s"
tcFormat="NDF" ref="r1">
<!-- offset: position in timeline -->
<!-- duration: length shown in timeline -->
<!-- start: in-point in source media -->
<!-- ref: links to asset id -->
</clip>
<video> / <audio> - A/V Components
<video name="B-Roll" offset="120s" duration="60s" start="0s" ref="r3">
<audio lane="-1" offset="0s" duration="60s" start="0s" ref="r3"/>
</video>
<gap> - Empty Space
<gap name="Gap" offset="180s" duration="30s"/>
<marker> - Marker
<marker start="45s" duration="1/30s" value="Chapter 1"/>
<!-- Chapter marker -->
<chapter-marker start="90s" duration="1/30s" value="Intro"
posterOffset="0s"/>
<!-- To-do marker -->
<marker start="120s" duration="1/30s" value="Fix audio">
<note>Audio levels too low</note>
</marker>
<keyword> - Keyword Range
<clip ref="r1" ...>
<keyword start="0s" duration="30s" value="Interview"/>
<keyword start="30s" duration="15s" value="B-Roll"/>
</clip>
<transition> - Transition Effect
<transition name="Cross Dissolve" offset="119/2s" duration="1s">
<filter-video ref="r10" name="Cross Dissolve"/>
</transition>
Edit Operations → XML Changes
ADD_MARKER
Target: Inside <clip>, <video>, <audio>, or <spine>
<!-- Before -->
<clip name="Interview" offset="0s" duration="120s" start="0s" ref="r1">
</clip>
<!-- After: Standard marker at 45s -->
<clip name="Interview" offset="0s" duration="120s" start="0s" ref="r1">
<marker start="45s" duration="1/30s" value="Review this"/>
</clip>
<!-- After: Chapter marker -->
<clip name="Interview" offset="0s" duration="120s" start="0s" ref="r1">
<chapter-marker start="45s" duration="1/30s" value="Introduction" posterOffset="0s"/>
</clip>
<!-- After: Colored marker -->
<clip name="Interview" offset="0s" duration="120s" start="0s" ref="r1">
<marker start="45s" duration="1/30s" value="Needs work">
<marker-color color="3"/> <!-- 0=blue, 1=cyan, 2=green, 3=yellow... -->
</marker>
</clip>
TRIM_CLIP
Target: start and duration attributes of <clip>
<!-- Before: 2 minute clip starting at source timecode 30s -->
<clip name="Interview" offset="0s" duration="120s" start="30s" ref="r1"/>
<!-- After: Trim 10s from start (new in-point) -->
<clip name="Interview" offset="0s" duration="110s" start="40s" ref="r1"/>
<!-- After: Trim 10s from end (shorter duration) -->
<clip name="Interview" offset="0s" duration="110s" start="30s" ref="r1"/>
Ripple trim also requires updating offset of all subsequent clips:
<!-- Before -->
<spine>
<clip name="A" offset="0s" duration="60s" .../>
<clip name="B" offset="60s" duration="60s" .../>
<clip name="C" offset="120s" duration="60s" .../>
</spine>
<!-- After: Trim 10s from end of clip A with ripple -->
<spine>
<clip name="A" offset="0s" duration="50s" .../>
<clip name="B" offset="50s" duration="60s" .../> <!-- offset shifted -->
<clip name="C" offset="110s" duration="60s" .../> <!-- offset shifted -->
</spine>
REORDER_CLIPS
Target: offset attributes of all affected clips
<!-- Before: A, B, C order -->
<spine>
<clip name="A" offset="0s" duration="30s" .../>
<clip name="B" offset="30s" duration="30s" .../>
<clip name="C" offset="60s" duration="30s" .../>
</spine>
<!-- After: Move C to start → C, A, B -->
<spine>
<clip name="C" offset="0s" duration="30s" .../>
<clip name="A" offset="30s" duration="30s" .../>
<clip name="B" offset="60s" duration="30s" .../>
</spine>
Important: Element order in XML doesn't matter, only offset values determine timeline position.
ADD_TRANSITION
Target: Insert <transition> element between clips
<!-- Before -->
<spine>
<clip name="A" offset="0s" duration="60s" .../>
<clip name="B" offset="60s" duration="60s" .../>
</spine>
<!-- After: Add 1s cross-dissolve between A and B -->
<spine>
<clip name="A" offset="0s" duration="60s" .../>
<transition name="Cross Dissolve" offset="59s" duration="1s">
<filter-video ref="r_dissolve" name="Cross Dissolve"/>
</transition>
<clip name="B" offset="60s" duration="60s" .../>
</spine>
Note: Transition offset is typically clip_end - (transition_duration / 2)
CHANGE_SPEED
Target: Add <timeMap> or <conform-rate> inside clip
<!-- Constant speed change (50% slow-mo) -->
<clip name="Action" offset="0s" duration="120s" start="0s" ref="r1">
<conform-rate scaleEnabled="1" srcFrameRate="30"/>
<timeMap>
<timept time="0s" value="0s" interp="linear"/>
<timept time="120s" value="60s" interp="linear"/>
</timeMap>
</clip>
<!-- Speed ramp (100% → 50% → 100%) -->
<clip name="Action" offset="0s" duration="90s" start="0s" ref="r1">
<timeMap>
<timept time="0s" value="0s" interp="linear"/>
<timept time="30s" value="30s" interp="smooth2"/>
<timept time="60s" value="45s" interp="smooth2"/>
<timept time="90s" value="75s" interp="linear"/>
</timeMap>
</clip>
Interpolation values:
linear- constant speedsmooth2- ease in/outsmooth- smoother ease
SPLIT_CLIP
Target: Create two clips from one, adjusting start, duration, offset
<!-- Before: Single 60s clip -->
<spine>
<clip name="Interview" offset="0s" duration="60s" start="0s" ref="r1"/>
</spine>
<!-- After: Split at 30s mark -->
<spine>
<clip name="Interview" offset="0s" duration="30s" start="0s" ref="r1"/>
<clip name="Interview" offset="30s" duration="30s" start="30s" ref="r1"/>
</spine>
DELETE_CLIP
Ripple delete: Remove clip, shift subsequent clips
<!-- Before -->
<spine>
<clip name="A" offset="0s" duration="30s" .../>
<clip name="B" offset="30s" duration="30s" .../>
<clip name="C" offset="60s" duration="30s" .../>
</spine>
<!-- After: Delete B with ripple -->
<spine>
<clip name="A" offset="0s" duration="30s" .../>
<clip name="C" offset="30s" duration="30s" .../> <!-- offset updated -->
</spine>
Non-ripple delete: Replace with gap
<!-- After: Delete B without ripple -->
<spine>
<clip name="A" offset="0s" duration="30s" .../>
<gap name="Gap" offset="30s" duration="30s"/>
<clip name="C" offset="60s" duration="30s" .../>
</spine>
Time Format Conversions
FCPXML uses rational time (fractions of seconds):
| Timecode | FCPXML Time |
|---|---|
| 00:00:01:00 @ 30fps | 1s or 30/30s |
| 00:00:00:15 @ 30fps | 15/30s or 1/2s |
| 00:01:00:00 | 60s |
| 00:00:02:10 @ 24fps | 58/24s |
Conversion formula:
fcpxml_time = (hours * 3600) + (minutes * 60) + seconds + (frames / fps)
Frame-accurate time:
frames_total = (hours * 3600 * fps) + (minutes * 60 * fps) + (seconds * fps) + frames
fcpxml_time = f"{frames_total}/{fps}s"
Resource References
Every clip references resources by id:
<resources>
<format id="r1" name="FFVideoFormat1080p30" width="1920" height="1080"
frameDuration="1/30s"/>
<asset id="r2" name="Interview" src="file:///path/to/file.mov"
start="0s" duration="3600s" format="r1"/>
<effect id="r3" name="Cross Dissolve" uid=".../Cross Dissolve"/>
</resources>
<!-- In timeline -->
<clip ref="r2" .../>
<transition>
<filter-video ref="r3"/>
</transition>
Compound Clips & Nested Timelines
<ref-clip name="Nested Sequence" ref="r5" offset="0s" duration="120s">
<!-- r5 points to another sequence -->
</ref-clip>
<!-- Inline compound (multicam, synchronized) -->
<mc-clip name="Multicam" offset="0s" duration="60s">
<mc-source angleID="angle1">
<clip ref="r10" .../>
</mc-source>
<mc-source angleID="angle2">
<clip ref="r11" .../>
</mc-source>
</mc-clip>
Audio Specifics
<!-- Detached audio -->
<clip ref="r1" ...>
<audio lane="-1" ref="r1" offset="0s" duration="60s"/>
</clip>
<!-- Audio only clip -->
<audio-clip name="Music" lane="-2" offset="0s" duration="180s" ref="r20"/>
<!-- Audio adjustments -->
<clip ref="r1" ...>
<adjust-volume amount="-6dB"/>
<audio lane="-1" ref="r1">
<adjust-volume amount="3dB"/>
</audio>
</clip>
Critical Implementation Notes
- Always validate XML after modification - FCP will reject malformed FCPXML
- Maintain resource integrity - never orphan asset references
- Recalculate all offsets after any operation that changes clip positions
- Preserve existing attributes - don't strip attributes you don't understand
- Handle connected clips - clips on other lanes may connect to spine clips
- Time precision matters - use rational fractions, not floats