{"record":{"id":"2df3fc0959bc7b5d","repo":"affaan-m/ECC","slug":"clip-i-path-has-non-positive-duration-dur-r-export","errorCode":null,"errorMessage":"clip {i} ({path}) has non-positive duration {dur!r}","messagePattern":"clip (.+?) \\((.+?)\\) has non-positive duration (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/export.py","lineNumber":57,"sourceCode":"\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\n\n\ndef _file_uri(path: str) -> str:","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/export.py#L39-L75","documentation":"normalise_clips requires every clip to have a strictly positive duration; duration defaults to 0.0 when missing. If clip i's duration is missing, zero, negative, or non-numeric-coercible to <= 0, it raises ValueError naming the clip index, path, and offending duration. Zero/negative durations would produce empty or invalid timeline events.","triggerScenarios":"Calling build_fcpxml/build_edl with a clip lacking 'duration' (coerced to 0.0), duration=0, a negative duration, or a string like \"abc\" that makes float() fail-adjacent logic produce invalid values — e.g. frames computed from a bad duration.","commonSituations":"CSV/JSON imports with blank duration cells, durations expressed as \"00:00:05\" timecodes instead of seconds, unit mix-ups (milliseconds fed as seconds can pass but produce wrong frames; negatives indicate bad subtraction upstream).","solutions":["Ensure each clip has a positive numeric duration in seconds before export","Convert timecode strings (HH:MM:SS) to seconds before passing clips","Pre-validate and filter clips: `float(c.get(\"duration\") or 0) > 0`","Fix upstream arithmetic producing negative durations (e.g. end < start)"],"exampleFix":"// before\nclips = [{\"path\": \"/media/a.mov\", \"duration\": 0}]\n// after\nclips = [{\"path\": \"/media/a.mov\", \"duration\": 5.0}]","handlingStrategy":"validation","validationCode":"def clips_have_positive_durations(clips: list[dict]) -> list[int]:\n    bad = []\n    for i, c in enumerate(clips):\n        try:\n            if float(c.get(\"duration\") or 0.0) <= 0:\n                bad.append(i)\n        except (TypeError, ValueError):\n            bad.append(i)\n    return bad","typeGuard":"def has_positive_duration(clip) -> bool:\n    try:\n        return float(clip.get(\"duration\") or 0.0) > 0\n    except (TypeError, ValueError):\n        return False","tryCatchPattern":"try:\n    edl = build_edl(clips, fps=24)\nexcept ValueError as e:\n    if \"non-positive duration\" in str(e):\n        print(e)  # names clip index, path, and bad duration\n    raise","preventionTips":["Store durations as seconds (floats), never timecode strings","Sanity-check that computed durations (end - start) are positive before export","Pre-filter clip lists with a duration validator and log rejected entries"],"tags":["export","validation","duration","clips"],"backgroundTag":"invalid-duration-format","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"}