help quicky produce instagram reels and youtube shorts
80
93%
Does it follow best practices?
Impact
47%
1.95xAverage score across 1 eval scenario
Passed
No known issues
#!/usr/bin/env python3
"""Import an FCPXML timeline back into cut_plan.json (round trip from an NLE).
Reads fcpxml exported by Final Cut Pro or DaVinci Resolve after manual edits
and rebuilds the plan: spine clip order/in-points/durations become segments,
a connected audio clip (lane != 0) becomes the music track. Accepts flat
.fcpxml files and .fcpxmld bundles (FCP 11's default export).
Only cuts survive the round trip: clip order, source in-points, durations,
music offset. NLE-side effects, transitions, retimes, and titles are ignored
with a warning — apply looks via --grade at render time instead.
Usage:
import_timeline.py plan/reel_edited.fcpxml --out plan/cut_plan.json \
[--base-plan plan/cut_plan.json] # inherit music/bpm if timeline lacks them
"""
import argparse, json, sys
import xml.etree.ElementTree as ET
from pathlib import Path
from urllib.parse import unquote, urlparse
IGNORED = {"transition", "title", "video", "audio", "spine"} # non asset-clip spine items we warn on
def secs(v):
"""Parse fcpxml rational time: '2800/3000s' | '8s' | '1.5s' -> float seconds."""
if not v:
return 0.0
v = v.rstrip("s")
if "/" in v:
n, d = v.split("/")
return float(n) / float(d)
return float(v)
def uri_to_path(src):
if src.startswith("file:"):
p = unquote(urlparse(src).path)
return p
return src
def relativize(path):
try:
return str(Path(path).resolve().relative_to(Path.cwd()))
except ValueError:
return path
def main():
ap = argparse.ArgumentParser()
ap.add_argument("fcpxml", help=".fcpxml file or .fcpxmld bundle")
ap.add_argument("--out", default="plan/cut_plan.json")
ap.add_argument("--base-plan",
help="Existing plan to inherit music/music_start/bpm from "
"when the timeline doesn't carry them")
args = ap.parse_args()
src = Path(args.fcpxml)
if src.is_dir(): # .fcpxmld bundle
inner = next(iter(src.glob("*.fcpxml")), None)
if not inner:
sys.exit(f"No .fcpxml inside bundle {src}")
src = inner
root = ET.parse(src).getroot()
# asset id -> media path (media-rep child, or src attr in older exports)
asset_path = {}
for a in root.iter("asset"):
rep = a.find("media-rep")
loc = (rep.get("src") if rep is not None else None) or a.get("src")
if loc:
asset_path[a.get("id")] = uri_to_path(loc)
spine = next(root.iter("spine"), None)
if spine is None:
sys.exit("No <spine> found — is this a timeline export?")
segments, music, music_start, warnings = [], None, 0.0, []
for el in spine:
tag = el.tag
if tag == "gap":
warnings.append(f"gap at offset {el.get('offset')} dropped — the "
"renderer butts segments together; re-time in the plan if intended")
continue
if tag not in ("asset-clip", "clip"):
warnings.append(f"<{tag}> in spine ignored (only cuts survive the round trip)")
continue
ref = el.get("ref")
if ref is None and tag == "clip": # FCP sometimes nests the ref
inner = next((c for c in el.iter() if c.get("ref")), None)
ref = inner.get("ref") if inner is not None else None
path = asset_path.get(ref)
if not path:
warnings.append(f"clip '{el.get('name')}' has no resolvable media — skipped")
continue
segments.append({
"clip": relativize(path),
"in": round(secs(el.get("start")), 3),
"duration": round(secs(el.get("duration")), 3),
"note": el.get("name", ""),
})
# connected clips: first audio-only lane is the music
for child in el:
if child.tag == "asset-clip" and child.get("lane") and music is None:
mp = asset_path.get(child.get("ref"))
if mp:
music = relativize(mp)
music_start = round(secs(child.get("start")), 3)
if not segments:
sys.exit("No clips found in the spine.")
base = {}
if args.base_plan and Path(args.base_plan).exists():
base = json.loads(Path(args.base_plan).read_text())
plan = {
"music": music or base.get("music", ""),
"music_start": music_start if music else base.get("music_start", 0.0),
**({"bpm": base["bpm"]} if "bpm" in base else {}),
"total_duration": round(sum(s["duration"] for s in segments), 2),
"segments": segments,
}
out = Path(args.out)
out.parent.mkdir(parents=True, exist_ok=True)
if out.exists():
backup = out.with_suffix(".json.bak")
backup.write_text(out.read_text())
print(f"Existing plan backed up to {backup}")
out.write_text(json.dumps(plan, indent=2))
print(f"Imported {len(segments)} segments, {plan['total_duration']:.1f}s -> {out}")
if not plan["music"]:
print(" NOTE: no music track found in the timeline — set \"music\" in the "
"plan or pass --base-plan.")
for w in warnings:
print(f" WARNING: {w}")
print("Render a preview + check_cuts.py before showing the human.")
if __name__ == "__main__":
main()