SpliceKit is an ObjC dylib injected into FCP's process. It exposes all 78,000+ ObjC classes via a JSON-RPC server on TCP 127.0.0.1:9876. Everything is fully programmatic -- no AppleScript, no UI automation, no menu clicks.
All FCP actions MUST go through direct ObjC calls. Never use any of these:
SpliceKit_simulateKeyPress()or synthetic NSEvent key eventsCGEventkeyboard/mouse postingosascript/NSAppleScript/ AppleScripttell application- Accessibility APIs (
AXUIElement) for clicking or typing - Frame-stepping loops (
nextFramex N) whenseekToTimeexists
Instead, always use one of these patterns (in priority order):
- Direct method call on the timeline/sequence/clip object via
objc_msgSend - Responder chain via
[NSApp sendAction:NSSelectorFromString(@"selector:") to:nil from:nil] - Batch bridge endpoint (e.g.
timeline.addMarkers) for operations on multiple items - Menu execute via
menu.executefor actions only accessible through menus
If an action doesn't have a direct ObjC selector yet, find one using explore_class /
search_methods on the relevant class (FFAnchoredTimelineModule, FFAnchoredSequence, etc.)
rather than simulating the keyboard shortcut.
1. bridge_status() -- verify connection
2. get_timeline_clips() -- see timeline contents
3. timeline_action("blade") -- edit
4. verify_action("after blade") -- confirm
If get_timeline_clips() returns an error about "no sequence", load a project:
# Navigate: library -> sequences -> find one with content -> load it
libs = call_method_with_args("FFLibraryDocument", "copyActiveLibraries", "[]", true, true)
libs_handle = json.loads(libs)["handle"]
lib = call_method_with_args(libs_handle, "objectAtIndex:", '[{"type":"int","value":0}]', false, true)
lib_handle = json.loads(lib)["handle"]
seqs = call_method_with_args(lib_handle, "_deepLoadedSequences", "[]", false, true)
seqs_handle = json.loads(seqs)["handle"]
allSeqs = call_method_with_args(seqs_handle, "allObjects", "[]", false, true)
# For each sequence result, extract its "handle" before calling hasContainedItems
# Example: seq_handle = json.loads(seq)["handle"]
# Check each: call_method_with_args(seq_handle, "hasContainedItems", "[]", false)
# Load: get NSApp -> delegate -> activeEditorContainer -> loadEditorForSequence:Color correction, retiming, titles, and effects require a selected clip:
playback_action("goToStart") # position
playback_action("nextFrame") x N # navigate
timeline_action("selectClipAtPlayhead") # select
timeline_action("addColorBoard") # now apply
- 1 frame = ~0.042s at 24fps, ~0.033s at 30fps
- Use
nextFramewith repeat count for precise positioning batch_timeline_actionsis fastest for multi-step sequences- Always go to a known position (goToStart) before stepping
timeline_action("undo") # undoes last edit, returns action name
timeline_action("redo") # redoes it
Undo routes through FCP's FFUndoManager (not the responder chain).
FCP stores items in: sequence -> primaryObject (FFAnchoredCollection) -> containedItems
FFAnchoredMediaComponent= video/audio clipsFFAnchoredTransition= transitions (Cross Dissolve, etc.)get_timeline_clips()handles this automatically
| Category | Actions |
|---|---|
| Blade | blade, bladeAll |
| Markers | addMarker, addTodoMarker, addChapterMarker, deleteMarker, nextMarker, previousMarker, deleteMarkersInSelection |
| Transitions | addTransition |
| Navigation | nextEdit, previousEdit, selectClipAtPlayhead, selectToPlayhead |
| Selection | selectAll, deselectAll |
| Edit | delete, cut, copy, paste, undo, redo, pasteAsConnected, replaceWithGap, copyTimecode |
| Edit Modes | connectToPrimaryStoryline, insertEdit, appendEdit, overwriteEdit |
| Effects | pasteEffects, pasteAttributes, removeAttributes, copyAttributes, removeEffects |
| Insert | insertGap, insertPlaceholder, addAdjustmentClip |
| Trim | trimToPlayhead, extendEditToPlayhead, trimStart, trimEnd, joinClips, nudgeLeft, nudgeRight, nudgeUp, nudgeDown |
| Color | addColorBoard, addColorWheels, addColorCurves, addColorAdjustment, addHueSaturation, addEnhanceLightAndColor, balanceColor, matchColor, addMagneticMask, smartConform |
| Volume | adjustVolumeUp, adjustVolumeDown |
| Audio | expandAudio, expandAudioComponents, addChannelEQ, enhanceAudio, matchAudio, detachAudio |
| Titles | addBasicTitle, addBasicLowerThird |
| Speed | retimeNormal, retimeFast2x/4x/8x/20x, retimeSlow50/25/10, retimeReverse, retimeHold, freezeFrame, retimeBladeSpeed, retimeSpeedRampToZero, retimeSpeedRampFromZero |
| Keyframes | addKeyframe, deleteKeyframes, nextKeyframe, previousKeyframe |
| Rating | favorite, reject, unrate |
| Range | setRangeStart, setRangeEnd, clearRange, setClipRange |
| Clip Ops | solo, disable, createCompoundClip, autoReframe, breakApartClipItems, synchronizeClips, openClip, renameClip, addToSoloedClips, referenceNewParentClip, changeDuration |
| Storyline | createStoryline, liftFromPrimaryStoryline, overwriteToPrimaryStoryline, collapseToConnectedStoryline |
| Audition | createAudition, finalizeAudition, nextAuditionPick, previousAuditionPick |
| Captions | addCaption, splitCaption, resolveOverlaps |
| Multicam | createMulticamClip |
| Show/Hide | showVideoAnimation, showAudioAnimation, soloAnimation, showTrackingEditor, showCinematicEditor, showMagneticMaskEditor, enableBeatDetection, showPrecisionEditor, showAudioLanes, expandSubroles, showDuplicateRanges, showKeywordEditor |
| View | zoomToFit, zoomIn, zoomOut, verticalZoomToFit, zoomToSamples, toggleSnapping, toggleSkimming, toggleClipSkimming, toggleAudioSkimming, toggleInspector, toggleTimeline, toggleTimelineIndex, toggleInspectorHeight, beatDetectionGrid, timelineScrolling, enterFullScreen, timelineHistoryBack, timelineHistoryForward |
| Project | duplicateProject, snapshotProject, projectProperties |
| Library | closeLibrary, libraryProperties, consolidateEventMedia, mergeEvents, deleteGeneratedFiles |
| Render | renderSelection, renderAll |
| Export | exportXML, shareSelection |
| Find | find, findAndReplaceTitle |
| Reveal | revealInBrowser, revealProjectInBrowser, revealInFinder, moveToTrash |
| Keywords | showKeywordEditor, removeAllKeywords, removeAnalysisKeywords |
| Other | analyzeAndFix, backgroundTasks, recordVoiceover, editRoles, hideClip, addVideoGenerator |
playPause, goToStart, goToEnd, nextFrame, prevFrame, nextFrame10, prevFrame10, playAroundCurrent
execute_menu_command(["File", "New", "Project"]) # any menu item
execute_menu_command(["Modify", "Balance Color"]) # color correction
execute_menu_command(["View", "Playback", "Loop"]) # toggle loop
list_menus(menu="File") # discover menu items
list_menus(menu="Modify", depth=3) # see nested submenus
get_inspector_properties() # read all properties of selected clip
get_inspector_properties("transform") # just transform (position, rotation, scale)
get_inspector_properties("compositing") # opacity, blend mode
set_inspector_property("opacity", 0.5) # set opacity to 50%
set_inspector_property("volume", -6.0) # set audio volume
set_inspector_property("positionX", 100.0) # move clip position
toggle_panel("videoScopes") # show/hide video scopes
toggle_panel("inspector") # toggle inspector
toggle_panel("effectsBrowser") # effects browser
set_workspace("colorEffects") # switch workspace layout
select_tool("blade") # switch to blade tool
select_tool("trim") # switch to trim tool
select_tool("range") # switch to range selection
select_tool("transform") # switch to transform tool
assign_role("audio", "Dialogue") # assign audio role
assign_role("video", "Titles") # assign video role
share_project("Export File") # export with specific destination
share_project() # export with default destination
create_project() # create new project
create_event() # create new event
create_library() # create new library
playback_action("goToStart")
batch_timeline_actions('[{"type":"playback","action":"nextFrame","repeat":72}]') # 3s at 24fps
timeline_action("blade")
batch_timeline_actions('[
{"type":"playback","action":"goToStart"},
{"type":"playback","action":"nextFrame","repeat":48},
{"type":"timeline","action":"blade"},
{"type":"playback","action":"nextFrame","repeat":48},
{"type":"timeline","action":"blade"},
{"type":"playback","action":"nextFrame","repeat":48},
{"type":"timeline","action":"blade"}
]')
playback_action("goToStart")
timeline_action("selectClipAtPlayhead")
timeline_action("addColorBoard")
timeline_action("selectClipAtPlayhead")
timeline_action("retimeSlow50") # 50% speed
# Undo: timeline_action("undo")
playback_action("goToStart")
batch_timeline_actions('[
{"type":"playback","action":"nextFrame","repeat":120},
{"type":"timeline","action":"addMarker"},
{"type":"playback","action":"nextFrame","repeat":120},
{"type":"timeline","action":"addChapterMarker"}
]')
xml = generate_fcpxml(
project_name="My Project",
frame_rate="24",
items='[
{"type":"gap","duration":10},
{"type":"title","text":"Introduction","duration":5},
{"type":"transition","duration":1},
{"type":"gap","duration":15},
{"type":"marker","time":5,"name":"Chapter 1","kind":"chapter"}
]'
)
import_fcpxml(xml, internal=True)
timeline_action("selectClipAtPlayhead")
get_clip_effects() # shows effect names, IDs, handles
analyze_timeline() # pacing, flash frames, clip stats
batch_export() # export all clips using default share destination
batch_export(scope="selected") # export only selected clips
Each clip is exported individually with all effects/color grading baked in. For each clip, SpliceKit:
- Computes the clip's exact position in the timeline
- Sets the in/out range (mark in/out) to the clip boundaries
- Triggers FCP's share dialog — click "Share" to confirm
Set your default share destination in FCP first (File > Share > Add Destination).
set_timeline_range(start_seconds=5.0, end_seconds=10.0) # mark in at 5s, out at 10s
timeline_action("setRangeStart") # mark in at current playhead
timeline_action("setRangeEnd") # mark out at current playhead
timeline_action("clearRange") # remove range selection
open_transcript() # transcribe all clips on timeline
open_transcript(file_url="/path/to/video.mp4") # transcribe a specific file
get_transcript() # get words with timestamps + speakers + silences
delete_transcript_words(start_index=5, count=3) # delete words 5-7 (removes video segment)
move_transcript_words(start_index=10, count=2, dest_index=3) # reorder clips
search_transcript("hello") # search for text in transcript
search_transcript("pauses") # find all silences/pauses
delete_transcript_silences() # batch-remove all silences from timeline
delete_transcript_silences(min_duration=1.0) # remove only silences > 1 second
set_transcript_speaker(start_index=0, count=50, speaker="Host") # label speakers
set_silence_threshold(threshold=0.5) # set minimum pause detection (seconds)
close_transcript() # close the panel
The transcript panel opens inside FCP as a floating window with an engine selector dropdown:
- Parakeet v3 (default) — NVIDIA Parakeet TDT 0.6B multilingual (25 languages), on-device via FluidAudio
- Parakeet v2 — English-optimized variant, same speed
- Apple Speech — SFSpeechRecognizer (slower, network-capable)
- FCP Native — Built-in AASpeechAnalyzer
All clips are transcribed in a single batch process (model loaded once, reused across clips). Speaker diarization is available with Parakeet engines (checkbox in UI).
Panel features:
- Shows transcribed text grouped by speaker segments with timecode ranges (HH:MM:SS:FF)
- Silence markers
[...]shown inline between words where pauses are detected - Click a word to jump the playhead to that time
- Click a silence marker to jump to that pause
- Select words and press Delete to remove those video segments (ripple delete)
- Select silence markers and press Delete to remove pauses
- Drag words to reorder clips on the timeline
- Current word is highlighted as playback progresses
- Search bar with text search and filter by Pauses or Low Confidence
- Batch operations: Delete all search results or delete all silences
- Result count with prev/next navigation (Cmd+F to focus search)
Deleting words performs: blade at start -> blade at end -> select segment -> delete Moving words performs: blade + cut at source -> move playhead -> paste at destination
list_transitions() # list all 376+ available transitions
list_transitions(filter="dissolve") # filter by name or category
apply_transition(name="Flow") # apply by display name
apply_transition(name="Cross Dissolve") # apply specific transition
apply_transition(effectID="HEFlowTransition") # apply by effect ID
Transitions are applied at the current edit point. Navigate to an edit point first:
timeline_action("nextEdit") # go to next edit point
apply_transition(name="Flow") # apply Flow transition there
When clips don't have enough extra media beyond their edges for a transition, FCP normally shows a dialog offering to ripple trim. SpliceKit adds a third option: "Use Freeze Frames".
- UI button: Whenever the "not enough extra media" dialog appears (including manual use), a "Use Freeze Frames" button is added. It extends clip edges with freeze frames and re-applies the transition without shortening the project.
- API parameter: Use
freeze_extend=Trueto automatically extend with freeze frames:
apply_transition(name="Cross Dissolve", freeze_extend=True) # auto freeze-extend if needed
This creates freeze frames at the outgoing clip's last frame and the incoming clip's first frame, providing the media handles needed for the transition overlap.
show_command_palette() # open the palette (or Cmd+Shift+P)
search_commands("blade") # find commands by name/keyword
execute_command("blade", type="timeline") # run a command directly
ai_command("slow this clip to half speed") # natural language via Apple Intelligence
hide_command_palette() # close it
The command palette opens as a floating window inside FCP:
- Fuzzy search across all available actions (editing, playback, color, speed, markers, etc.)
- Arrow keys to navigate, Return to execute, Escape to close
- Type natural language sentences and press Tab to ask Apple Intelligence
- Falls back to keyword matching when Apple Intelligence is unavailable
- Also accessible via toolbar button or Enhancements menu
get_playhead_position() # current time, duration, frame rate, playing state
get_selected_clips() # get only selected clips in timeline
seek_to_time(3.5) # jump to 3.5 seconds instantly (faster than stepping)
get_viewer_zoom() # current zoom level (0.0=Fit, 1.0=100%, 2.0=200%)
set_viewer_zoom(0.0) # fit to window
set_viewer_zoom(1.0) # 100%
set_viewer_zoom(2.0) # 200% — any float value accepted
detect_dialog() # scan for open dialogs, see buttons/fields/checkboxes
click_dialog_button(button="OK") # click by title (case-insensitive, partial match)
click_dialog_button(index=0) # click by index
fill_dialog_field(value="My Project") # fill text field
toggle_dialog_checkbox("Use custom settings", checked=True)
select_dialog_popup(select="4K") # choose from dropdown
dismiss_dialog(action="default") # click default button to close
dismiss_dialog(action="cancel") # cancel/escape
detect_scene_changes() # list scene change timestamps
detect_scene_changes(threshold=0.2) # more sensitive
detect_scene_changes(action="markers") # add markers at cuts
detect_scene_changes(action="blade") # blade at every scene change
detect_scene_changes(threshold=0.5, sample_interval=0.25) # less sensitive, faster
detect_beats(file_path="/path/to/song.mp3") # detect beats, bars, sections, BPM
detect_beats(file_path="song.mp3", sensitivity=0.8) # more sensitive
detect_beats(file_path="song.mp3", min_bpm=120, max_bpm=180) # for fast music
Build first: swiftc -O -o build/beat-detector tools/beat-detector.swift
import_srt_as_markers(srt_content="1\n00:00:05,000 --> 00:00:10,000\nSubtitle text")
get_bridge_options() # see all configurable options
set_bridge_option("effectDragAsAdjustmentClip", True) # drag effects to create adjustment clips
set_bridge_option("viewerPinchZoom", True) # trackpad pinch-to-zoom on viewer
set_bridge_option("videoOnlyKeepsAudioDisabled", True) # keep audio disabled in video-only mode
set_bridge_option("suppressAutoImport", True) # stop auto-opening Import window on card/camera mount
set_bridge_option_value("defaultSpatialConformType", "fill") # default new clips to Fill (crop to fill frame)
set_bridge_option_value("defaultSpatialConformType", "none") # default new clips to None (native resolution)
set_bridge_option_value("defaultSpatialConformType", "fit") # restore FCP default (Fit)
# Get a handle to an object
r = call_method_with_args("FFLibraryDocument", "copyActiveLibraries", "[]", true, true)
# r = {"handle": "obj_1", "class": "__NSArrayM", ...}
# Use handle in subsequent calls
call_method_with_args("obj_1", "objectAtIndex:", '[{"type":"int","value":0}]', false, true)
# Read properties via KVC
get_object_property("obj_2", "displayName")
# Always clean up
manage_handles(action="release_all")
Argument types: string, int, double, float, bool, nil, sender, handle, cmtime, selector
- "No active timeline module" -> No project open. Load one (see above).
- "No sequence in timeline" -> Same. Need loadEditorForSequence:.
- "Cannot connect" -> FCP not running. Launch it.
- "Handle not found" -> Released or GC'd. Get a fresh reference.
- "No responder handled X" -> Action not available (wrong state or no selection).
- Broken pipe -> Stale connection. Next call auto-reconnects.
| Class | Use |
|---|---|
| FFAnchoredTimelineModule | Timeline editing (1435 methods) |
| FFAnchoredSequence | Timeline data model |
| FFAnchoredMediaComponent | Clips in timeline |
| FFAnchoredTransition | Transitions |
| FFLibrary / FFLibraryDocument | Library management |
| FFEditActionMgr | Edit commands |
| FFEffectStack | Effects on clips |
| PEAppController | App controller |
| PEEditorContainerModule | Editor/timeline modules |
get_classes(filter="FFColor") # find classes by name
explore_class("FFAnchoredTimelineModule") # full overview (methods, ivars, protocols)
search_methods("FFAnchoredTimelineModule", "blade") # find methods by name
get_methods("FFEffectStack") # all instance + class methods with type encodings
get_ivars("FFPlayer") # ivar names, types, byte offsets
get_properties("FFAnchoredSequence") # properties with parsed attributes
get_protocols("FFAnchoredMediaComponent") # protocol conformances
get_superchain("FFAnchoredTimelineModule") # inheritance chain to NSObject
Each method entry includes: selector name, ObjC type encoding, IMP hex address, owning image (via dladdr), and linker symbol name. Ivar entries include byte offsets for struct reconstruction.
SpliceKit exposes FCP's internal developer logging, debug overlays, and performance monitoring tools that are normally hidden. These are built into FCP's own frameworks (ProAppSupport, TimelineKit, Helium, ProCore) and controlled via NSUserDefaults and CFPreferences keys.
debug_get_config() # see all current debug settings
debug_enable_preset("timeline_visual") # turn on visual debug overlays
debug_enable_preset("all_off") # reset everything to normal
debug_get_config()
Returns the current value of every debug flag organized into four groups:
- timeline_debug: 35 TLKUserDefaults keys (visual overlays, logging, rendering)
- cfpreferences_debug: 6 CFPreferences keys (video decoder, frame drops, GPU)
- proapp_log: ProAppSupport structured log system (level, categories, UI, threads)
- fcp_flags: FCP behavioral overrides (gap coalescing, snapping, skimming)
debug_set_config("TLKShowHiddenGapItems", "true") # show hidden gaps in timeline
debug_set_config("TLKPerformanceMonitorEnabled", "true") # enable perf monitor
debug_set_config("LogLevel", "trace") # most verbose logging
debug_set_config("VideoDecoderLogLevelInNLE", "3") # video decoder verbosity
debug_set_config("GPU_LOGGING", "true") # GPU/FxPlug logging
debug_set_config("TLKShowHiddenGapItems", "false") # turn it back off
TLK flags take effect immediately (TLKUserDefaults is reloaded live). CFPreferences flags may require FCP restart for some subsystems.
debug_enable_preset("timeline_visual") # visual debug overlays
debug_enable_preset("timeline_logging") # timeline subsystem logging
debug_enable_preset("performance") # perf monitor + decoder/frame drop logging
debug_enable_preset("render_debug") # disable rendering layers + GPU logging
debug_enable_preset("verbose_logging") # trace-level logging + log UI + thread info
debug_enable_preset("all_off") # reset all debug flags to defaults
Preset details:
| Preset | What it enables |
|---|---|
timeline_visual |
Lane indices, misaligned edges, render bar, hidden gaps, invalid layouts, color-highlight changed objects |
timeline_logging |
Log layer changes, parts, reload requests, recycling, visible rect changes, segmentation stats |
performance |
TLK performance monitor, VideoDecoderLogLevelInNLE=2, FrameDropLogLevel=2 |
render_debug |
Disable filmstrip/background/waveform rendering, enable GPU logging (isolate render issues) |
verbose_logging |
LogLevel=trace, LogUI=true, LogThread=true, EnableScheduledReadAudioLogging=true |
all_off |
Remove all debug flags, reset CFPreferences, clear log settings |
debug_start_framerate_monitor(2.0) # log fps every 2 seconds
debug_stop_framerate_monitor() # stop monitoring
Uses FCP's built-in HMDFramerate (ProCore). Reports to system log:
- Overall fps
- Average getFrame() call time in ms
- Min/max frame times
View output: log stream --process "Final Cut Pro" or Console.app
debug_reset_config("all") # reset everything
debug_reset_config("tlk") # reset timeline flags only
debug_reset_config("cfprefs") # reset CFPreferences only
debug_reset_config("log") # reset ProAppSupport log settings only
Timeline visual overlays (TLK*):
| Key | Effect |
|---|---|
| TLKShowItemLaneIndex | Show lane index number on each timeline item |
| TLKShowMisalignedEdges | Highlight misaligned edges between items |
| TLKShowRenderBar | Show render status bar overlay |
| TLKShowHiddenGapItems | Reveal hidden gap items in timeline |
| TLKShowHiddenItemHeaders | Reveal hidden item headers |
| TLKShowInvalidLayoutRects | Highlight invalid layout rectangles |
| TLKShowContainerBounds | Show container bounds |
| TLKShowContentLayers | Show content layer boundaries |
| TLKShowRulerBounds | Show ruler bounds overlay |
| TLKShowUsedRegion | Show used region overlay |
| TLKShowZeroHeightSpineItems | Show zero-height spine items |
Timeline logging (TLK*):
| Key | What it logs |
|---|---|
| TLKLogVisibleLayerChanges | Changes to visible layers |
| TLKLogParts | Timeline parts lifecycle |
| TLKLogReloadRequests | Reload/refresh requests |
| TLKLogRecyclingLayerChanges | Layer recycling events |
| TLKLogVisibleRectChanges | Visible rect geometry changes |
| TLKLogSegmentationStatistics | Segmentation statistics |
Timeline performance/rendering (TLK* and Debug*):
| Key | Effect |
|---|---|
| TLKPerformanceMonitorEnabled | Enable timeline performance monitoring |
| TLKDebugColorChangedObjects | Color-highlight changed objects after updates |
| TLKDebugLayoutConstraints | Debug layout constraint resolution |
| TLKDebugErrorsAndWarnings | Show errors and warnings visually |
| TLKDisableItemContents | Disable all item content rendering |
| DebugKeyItemVideoFilmstripsDisabled | Disable video filmstrip thumbnails |
| DebugKeyItemBackgroundDisabled | Disable item background rendering |
| DebugKeyItemAudioWaveformsDisabled | Disable audio waveform rendering |
Video/audio/GPU logging (CFPreferences):
| Key | Type | Effect |
|---|---|---|
| VideoDecoderLogLevelInNLE | int | Video decoder verbosity (0=off, higher=more) |
| FrameDropLogLevel | int | Frame drop reporting (0=off, higher=more) |
| GPU_LOGGING | bool | GPU/FxPlug pipeline logging |
| EnableScheduledReadAudioLogging | bool | Audio scheduled read logging |
| EnableLibraryUpdateHistoryValidation | bool | Library update history validation |
| FFVAMLSaveTranscription | bool | Save transcription data to disk |
ProAppSupport log system:
| Key | Values | Effect |
|---|---|---|
| LogLevel | trace, debug, info, warning, error, failure | Set minimum log level |
| LogUI | bool | Toggle in-app log viewer panel |
| LogThread | bool | Include thread info in log output |
| LogCategory | bitmask | Filter by subsystem category |
Log categories: dev, player, sequenceEditor, camera, inspector, director, voiceover, selection, network, theme, share, analysisKit, backgroundTasks, angleEditor, lessons, onboarding, userNotifications, ui, all
FCP behavior overrides:
| Key | Effect |
|---|---|
| FFDontCoalesceGaps | Prevent automatic gap coalescing in timeline |
| FFDisableSnapping | Disable magnetic snapping |
| FFDisableSkimming | Disable clip skimming |
Diagnosing timeline layout issues: Enable timeline_visual preset to see lane
indices, hidden gaps, misaligned edges, and invalid layout rects. This reveals
structural problems invisible in the normal UI.
Performance troubleshooting: Enable performance preset + debug_start_framerate_monitor()
to measure actual rendering fps and identify bottlenecks. Video decoder and frame drop
logging pinpoint decode pipeline issues.
Render pipeline isolation: The render_debug preset disables filmstrips, backgrounds,
and waveforms independently, letting you isolate which rendering subsystem is causing
problems. GPU logging captures the FxPlug/shader pipeline.
Verbose logging for development: The verbose_logging preset sets ProAppSupport to
trace level with the log UI enabled, giving maximum visibility into FCP's internal
operations. Useful when developing new SpliceKit features or investigating FCP behavior.
Understanding timeline internals: TLKShowHiddenGapItems and TLKShowZeroHeightSpineItems
reveal items FCP hides from the user, helping understand the true timeline data model.
Extract rich ObjC runtime metadata from the live FCP process — data that static binary analysis cannot provide. Used to enrich IDA Pro decompilation and build the 303K-function decompiled codebase.
dump_runtime_metadata(binary="Flexo") # full metadata for all Flexo classes
dump_runtime_metadata(binary="Flexo", classes_only=True) # just class names (fast)
dump_runtime_metadata() # all images (large!)
Returns per class:
- Instance & class methods with selector, type encoding, IMP address, dladdr info (which binary owns the IMP — reveals category methods from other frameworks)
- Ivars with name, type encoding, and byte offset (for struct reconstruction)
- Ivar layout bitmaps — which ivars are strong vs weak references
- Protocol conformances with full method declarations (required/optional, instance/class, type encodings) and protocol inheritance chains
- Parsed property attributes — type, getter/setter selectors, backing ivar, readonly/copy/strong/weak/nonatomic/dynamic (structured, not raw attribute strings)
- Superclass chain and instance size
list_loaded_images() # all 1255 loaded Mach-O images
list_loaded_images(filter="Flexo") # filter by name
Returns path, base address, ASLR slide, and ObjC class count per image. The ASLR slide is needed to map runtime IMP addresses → IDA static addresses.
get_image_sections(binary="Flexo") # selector refs, class refs
Returns:
- Selector references — every ObjC selector the binary calls (44K+ for Flexo)
- Class references — which classes the binary references
- Superclass references — parent class dependencies
For shared-cache system frameworks, uses ObjC runtime APIs instead of raw section reads.
get_image_symbols(binary="Flexo", filter="Timeline") # filtered
get_image_symbols(binary="TimelineKit") # all symbols
Discovers exported symbols via dladdr on all methods. Swift symbols are automatically
demangled using swift_demangle() from libswiftCore.
get_notification_names(binary="Flexo") # notification-related classes & symbols
Finds classes with "Notification" in their name and enumerates their methods.
Also resolves well-known notification name constants (e.g., FFEffectsChangedNotification).
The tools/ directory contains scripts for applying runtime metadata to IDA Pro:
# Step 1: Export runtime metadata from live FCP
python3 tools/fcp_runtime_export.py --binary Flexo -o ida_export
# Step 2: Run IDA headless with metadata enrichment + decompile
RUNTIME_JSON=ida_export/Flexo.json DECOMPILE_OUTPUT_DIR=output \
idat -A -S"tools/ida_apply_and_decompile.py" /path/to/FlexoWhat the IDA script does:
- Declares struct types from ivars with correct offsets and typed members
- Registers types in IDA's local type library (persists across sessions)
- Renames functions to ObjC names (
sub_XXXX→-[FFPlayer play]) - Sets function prototypes with typed parameters
- Adds class hierarchy and protocol conformance comments
- Creates enums for known constant sets
- Triggers type propagation across the entire binary
Tools:
tools/fcp_runtime_export.py— Collection script (connects to SpliceKit, dumps JSON per binary)tools/ida_apply_and_decompile.py— IDAPython headless script (applies metadata + decompiles)tools/ida_objc_types.py— ObjC type encoding parser (converts@"NSArray"→NSArray *)tools/ida_import_runtime.py— Interactive IDAPython script (for use inside IDA GUI)tools/batch_enhanced_decompile.sh— Batch process all 53 FCP binaries
SpliceKit includes a full debugging toolkit that provides Xcode/lldb-level capabilities from within FCP's process, accessible via MCP. No debugger attachment required.
debug.breakpoint(action="add", className="FFAnchoredTimelineModule", selector="blade:")
# ... press B in FCP — execution pauses, breakpoint.hit event fires ...
debug.breakpoint(action="inspect") # see paused state
debug.breakpoint(action="inspectSelf", keyPath="sequence.displayName") # inspect properties
debug.breakpoint(action="continue") # resume execution
debug.breakpoint(action="step") # resume + break on next call
Supports conditional breakpoints (condition="keyPath"), hit counts (hitCount=5),
and one-shot breakpoints (oneShot=True). FCP freezes while paused (same as Xcode).
The JSON-RPC server stays alive on a separate thread so you can inspect state.
debug.traceMethod(action="add", className="FFAnchoredTimelineModule",
selector="blade:", logStack=True)
# ... perform action in FCP ...
debug.traceMethod(action="getLog", limit=10) # see calls + call stacks
debug.traceMethod(action="removeAll") # clean up
Traces are broadcast to MCP clients in real-time as JSON-RPC notifications. Use tracing when you want to observe without pausing, breakpoints when you need to inspect state at a specific moment.
debug.watch(action="add", className="NSApplication", keyPath="mainWindow")
# Events broadcast when property changes with old/new values
debug.watch(action="removeAll")
debug.crashHandler(action="install") # catch exceptions + signals
debug.crashHandler(action="getLog") # see crash stack traces
Catches NSExceptions and signals (SIGABRT, SIGSEGV, etc.) with full stack traces.
debug.threads() # thread count, operation queues
debug.threads(detailed=True) # per-thread CPU usage, run state, stacks
Uses Mach kernel APIs. Shows all ~45 threads with CPU usage percentages.
debug.eval(expression="NSApp.delegate._targetLibrary.displayName")
debug.eval(chain=["delegate", "_targetLibrary"], storeResult=True)
Walks ObjC property chains. Stores results as handles for further inspection.
debug.loadPlugin(action="load", path="/tmp/patch.dylib") # inject code
debug.loadPlugin(action="unload", path="/tmp/patch.dylib") # remove it
Compile a .dylib with fixes/features, load into running FCP without restart.
debug.observeNotification(action="add", name="FFEffectsChangedNotification")
debug.observeNotification(action="add", name="*") # all notifications (high volume)
debug.observeNotification(action="removeAll")
Subscribe to FCP's internal NSNotification events. Broadcast to MCP clients.
Calls Flexo's parameterized action* methods directly on FFAnchoredTimelineModule
with real arguments. More powerful than the simple responder-chain timeline.action.
timeline.directAction(action="retimeSetRate", rate=0.5, ripple=True)
timeline.directAction(action="retimeSpeedRamp", toZero=True)
timeline.directAction(action="retimeInstantReplay", rate=0.5, addTitle=True)
timeline.directAction(action="retimeJumpCut", framesToJump=5)
timeline.directAction(action="retimeRewind", speed=2.0)
timeline.directAction(action="insertFreezeFrame")
timeline.directAction(action="changeMarkerType", type="chapter")
timeline.directAction(action="changeMarkerName", name="Intro", marker="obj_5")
timeline.directAction(action="markMarkerCompleted", completed=True)
timeline.directAction(action="changeAudioVolume", amount=-6.0, relative=True)
timeline.directAction(action="applyAudioFadesDirect", fadeIn=True, duration=0.5)
timeline.directAction(action="setBackgroundMusic", enabled=True)
timeline.directAction(action="addKeywords", keywords=["Interview", "B-Roll"])
timeline.directAction(action="removeEffectByID", effectID="HEFlowTransition")
timeline.directAction(action="renameAngle", name="Camera 2")
timeline.directAction(action="newProject", name="My Project")
timeline.directAction(action="alignToMusicMarkers")
timeline.directAction(action="duplicateCaptions", language="es", format="SRT")
timeline.directAction(selector="actionValidateAndRepair:validateMode:error:")
See docs/DEBUG_TOOLS_GUIDE.md for comprehensive documentation of all debug
endpoints, parameters, workflows, and recipes.
See docs/FCP_API_REFERENCE.md for comprehensive documentation of all key classes,
methods, properties, notifications, and patterns. This reference is sufficient to use
SpliceKit without access to the decompiled FCP source code.
open_captions() # open panel + transcribe timeline
open_captions(style="bold_pop") # open with preset style
get_caption_state() # check transcription progress + segments
get_caption_styles() # list all 13 style presets
set_caption_style(preset_id="neon_glow") # apply a preset
set_caption_style(preset_id="bold_pop", font_size=80, position="center") # customize
set_caption_grouping(mode="words", max_words=4) # control word grouping
generate_captions(style="bold_pop") # generate + paste to user's timeline
verify_captions() # inspect titles to verify text/font
get_title_text() # read text/font from selected title
export_captions_srt(path="/tmp/captions.srt") # export SRT subtitles
export_captions_txt(path="/tmp/captions.txt") # export plain text
Generates word-by-word highlighted, animated caption titles as FCPXML. The pipeline:
- Imports FCPXML into a temp project (resolves Motion template)
- Copies titles from temp project to clipboard (native format)
- Pastes as connected storyline onto the user's actual timeline
- Applies position offset via ObjC transform (not FCPXML adjust-transform)
- Self-verifies: inspects first title's CHChannelText for text/font/size
- Cleans up the temp project
No drag-and-drop, no dialogs, captions land directly on the user's timeline.
Style presets (13 built-in): bold_pop, neon_glow, clean_minimal, handwritten,
gradient_fire, outline_bold, shadow_deep, karaoke, typewriter, bounce_fun,
subtitle_pro, social_bold, social_reels
Positions: bottom (default lower third), center, top, custom
Animations: none, fade, pop, slide_up, typewriter, bounce
Word grouping modes: social (2-3 words, 0.5s silence break — best for TikTok/Reels), words (max N per group), sentence (by punctuation), time (max seconds), chars (max characters)
Each caption is a <title> element with <text-style> attributes. Word-by-word
highlight uses multiple <text-style> refs per title — the active word gets the
highlight color, others get the base text color.
Title text inspection: get_title_text() reads text content, font family, font name,
and point size from the selected Motion title's CHChannelText channel. verify_captions()
walks connected titles on the timeline and checks text/fontSize against the expected style.
docs/TRANSCRIPT_EDITING_GUIDE.md— Transcript-based editing (engines, silence removal, speakers)docs/COMMAND_PALETTE_GUIDE.md— Command palette & Apple Intelligencedocs/RUNTIME_INTROSPECTION_GUIDE.md— ObjC runtime exploration & reverse engineeringdocs/DIALOG_AUTOMATION_GUIDE.md— Dialog detection & interactiondocs/SCENE_BEAT_DETECTION_GUIDE.md— Scene change & beat detectiondocs/FXPLUG_PLUGIN_GUIDE.md— FxPlug 4 plugin developmentdocs/FCPXML_FORMAT_REFERENCE.md— FCPXML interchange formatdocs/WORKFLOW_EXTENSIONS_GUIDE.md— Workflow Extensions (ProExtensionHost)docs/CONTENT_EXCHANGE_GUIDE.md— Content exchange mechanismsdocs/FLEXMUSIC_AND_MONTAGE_GUIDE.md— FlexMusic & Montage Makerdocs/DEBUG_TOOLS_GUIDE.md— In-process debugging (tracing, watching, crash handling, eval, hot-loading)tools/fcp_runtime_export.py— Export runtime metadata for IDA Pro (usage:--help)tools/ida_apply_and_decompile.py— IDAPython headless: apply metadata + decompile all functionstools/batch_enhanced_decompile.sh— Batch decompile all 53 FCP binaries with runtime enrichment