{"record":{"id":"ede30081c7127ca3","repo":"affaan-m/ECC","slug":"clip-i-has-no-path-export","errorCode":null,"errorMessage":"clip {i} has no 'path'","messagePattern":"clip (.+?) has no 'path'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/export.py","lineNumber":54,"sourceCode":"    \"write_timeline\",\n]\n\n\n# ---------------------------------------------------------------------------\n# clip normalisation\n# ---------------------------------------------------------------------------\n\ndef normalise_clips(clips: Iterable[dict], fps: float | Fraction) -> list[dict]:\n    \"\"\"Validate clips and pre-compute integer frame counts and offsets.\n\n    Each clip is ``{\"path\": str, \"duration\": float, \"name\": str?}``.\n    \"\"\"\n    out: list[dict] = []\n    offset = 0\n    for i, c in enumerate(clips):\n        path = str(c.get(\"path\") or \"\")\n        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","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/export.py#L36-L72","documentation":"normalise_clips converts a clip list into export-ready normalized entries for FCPXML/EDL building. Each input clip dict must carry a non-empty 'path'. When clip i's path is missing or empty (after str() coercion of c.get('path')), it raises ValueError naming the offending index. Without a path, no asset can be referenced in the timeline export.","triggerScenarios":"Calling build_fcpxml or build_edl with a clips list where some entry has no 'path' key, path=None, or path=\"\" — e.g. a script generating clips from a spreadsheet with blank rows.","commonSituations":"Hand-assembled clip lists, CSV/JSON imports with missing cells, clips constructed programmatically with only name/duration, or keys renamed upstream (e.g. 'file' instead of 'path').","solutions":["Ensure every clip dict has a non-empty \"path\" string before calling build_fcpxml/build_edl","Pre-filter the clips list: drop or fix entries lacking a path, and log them","Check for upstream key renames ('file', 'src', 'location') and map them to 'path'","Fix the data source (spreadsheet/CSV) so path cells are filled"],"exampleFix":"// before\nclips = [{\"duration\": 5.0}]\nbuild_fcpxml(clips, fps=24)\n// after\nclips = [{\"path\": \"/media/a.mov\", \"duration\": 5.0}]\nbuild_fcpxml(clips, fps=24)","handlingStrategy":"validation","validationCode":"def clips_have_paths(clips: list[dict]) -> list[int]:\n    return [i for i, c in enumerate(clips) if not str(c.get(\"path\") or \"\").strip()]","typeGuard":"def has_path(clip) -> bool:\n    return bool(str(clip.get(\"path\") or \"\").strip())","tryCatchPattern":"try:\n    xml = build_fcpxml(clips, fps=24)\nexcept ValueError as e:\n    if \"has no 'path'\" in str(e):\n        idx = int(e.args[0].split()[1])\n        print(f\"clip {idx} missing path; fix input data\")\n    raise","preventionTips":["Validate clip dicts (path present, duration > 0) before any export call","Normalize upstream key names ('file', 'src') to 'path' during import","Skip-and-log blank rows when importing clip lists from CSV/JSON"],"tags":["export","validation","missing-field","clips"],"backgroundTag":"missing-required-argument","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"}