{"record":{"id":"1c41f2a5d62711bb","repo":"affaan-m/ECC","slug":"no-clips-to-write-a-timeline-needs-at-least-one-event-export","errorCode":null,"errorMessage":"no clips to write - a timeline needs at least one event","messagePattern":"no clips to write - a timeline needs at least one event","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/export.py","lineNumber":71,"sourceCode":"        if not path:\n            raise ValueError(f\"clip {i} has no 'path'\")\n        dur = float(c.get(\"duration\") or 0.0)\n        if dur <= 0:\n            raise ValueError(f\"clip {i} ({path}) has non-positive duration {dur!r}\")\n        frames = max(1, seconds_to_frames(dur, fps))  # never a zero-length event\n        name = str(c.get(\"name\") or Path(path).stem)\n        out.append(\n            {\n                \"path\": path,\n                \"name\": name,\n                \"frames\": frames,\n                \"offset_frames\": offset,\n                \"seconds\": dur,\n            }\n        )\n        offset += frames\n    if not out:\n        raise ValueError(\"no clips to write - a timeline needs at least one event\")\n    return out\n\n\ndef _file_uri(path: str) -> str:\n    p = Path(path)\n    if not p.is_absolute():\n        p = Path.cwd() / p\n    return Path(str(p)).absolute().as_uri()\n\n\ndef _format_name(width: int, height: int, fps: float | Fraction) -> str:\n    f = fps_fraction(fps)\n    rate = float(f)\n    label = f\"{rate:.2f}\".rstrip(\"0\").rstrip(\".\").replace(\".\", \"\")\n    return f\"FFVideoFormat{height}p{label}\"\n\n\ndef _is_drop_frame(fps: float | Fraction) -> bool:","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/export.py#L53-L89","documentation":"normalise_clips() builds the normalized clip list for timeline export; if the input timeline produced zero events it raises ValueError because FCPXML/EDL writers cannot represent an empty timeline. The library refuses to emit a structurally empty sequence rather than writing a broken file.","triggerScenarios":"Calling build_fcpxml or build_edl with a timeline whose event list is empty, or where every event was filtered out during normalization (e.g. zero-length clips dropped before the `if not out` check).","commonSituations":"Scripts importing an empty EDL/JSON source, timelines where all clips were trimmed to zero duration, or a typo passing the wrong variable (an empty list) as the events argument.","solutions":["Verify the timeline object passed to build_fcpxml/build_edl contains at least one event before calling","Check that input parsing (e.g. parse_edl of the source) actually yielded clips; log its length","If empty timelines are legitimate in your workflow, check clip count first and skip export or emit a placeholder"],"exampleFix":"// before\nxml = build_fcpxml(timeline)\n// after\nif not timeline.events:\n    print(\"timeline is empty; nothing to export\")\nelse:\n    xml = build_fcpxml(timeline)","handlingStrategy":"validation","validationCode":"if not timeline_events:\n    raise SystemExit(\"timeline has no events; nothing to export\")","typeGuard":"def has_clips(clips) -> bool:\n    return isinstance(clips, (list, tuple)) and len(clips) > 0","tryCatchPattern":"try:\n    xml = build_fcpxml(timeline)\nexcept ValueError as e:\n    if \"no clips to write\" in str(e):\n        print(\"skipping export: empty timeline\")\n    else:\n        raise","preventionTips":["Check len(timeline.events) before every export call","Log the source parse result length to confirm clips were ingested","Filter zero-length clips upstream and report when filtering empties the timeline"],"tags":["empty-input","timeline","validation"],"backgroundTag":"empty-required-field","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}