{"record":{"id":"52b9922c3d219461","repo":"affaan-m/ECC","slug":"output-must-have-a-video-suffix-distinct-from-timeline","errorCode":null,"errorMessage":"output must have a video suffix distinct from timeline/manifest files","messagePattern":"output must have a video suffix distinct from timeline/manifest files","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/forge.py","lineNumber":43,"sourceCode":"from datetime import datetime, timezone\nfrom pathlib import Path\n\nimport numpy as np\n\nfrom taste import assemble as asm\nfrom taste import cadence as cad_mod\nfrom taste import frames as frame_mod\nfrom taste import grade as grade_mod\nfrom taste import pack as pack_mod\nfrom taste import plates as plate_mod\nfrom taste import timeline as tl_mod\n\n\ndef validate_output(out_path: Path) -> None:\n    \"\"\"Refuse to replace either a viewing copy or any part of its handoff.\"\"\"\n    outputs = [out_path, *(out_path.with_suffix(s) for s in (\".fcpxml\", \".edl\", \".json\"))]\n    if len(set(outputs)) != len(outputs):\n        raise ValueError(\"output must have a video suffix distinct from timeline/manifest files\")\n    for path in outputs:\n        if path.exists() or path.is_symlink():\n            raise FileExistsError(f\"output already exists; choose a new --out: {path}\")\n\n\ndef forge(\n    genre: str,\n    takes: list[str],\n    out: str,\n    root: str = \"stylepacks\",\n    base_video: str | None = None,\n    base_ratio: float = 0.35,\n    overlays: list[str] | None = None,\n    overlay_every: int = 4,\n    overlay_opacity: float = 0.3,\n    duration: float | None = None,\n    strength: float = 1.0,\n    grade_base: bool = True,","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/forge.py#L25-L61","documentation":"forge.validate_output() computes the output path plus the sibling .fcpxml, .edl and .json handoff files (generated via with_suffix). If the output path has no suffix, or its suffix is one of those, the derived set collapses to duplicates and the viewer copy would overwrite its own timeline/manifest, so it raises ValueError.","triggerScenarios":"Calling forge(...) with --out given as a suffixless path (e.g. 'edit'), or an output whose suffix is .fcpxml, .edl, or .json (e.g. --out edit.json), so out_path.with_suffix('.json') equals out_path itself.","commonSituations":"Copy-pasting a timeline path as the --out argument; forgetting the video extension on the CLI; scripting that builds the out path from a stem without adding .mp4/.mov.","solutions":["Pass an output path with a real video suffix (e.g. --out edit.mp4 or edit.mov) that is not .fcpxml/.edl/.json.","Append the extension in your wrapper script: out = stem + '.mp4'.","Check with Path(out).suffix before calling forge to catch the mistake early."],"exampleFix":"# before\nforge('noir', takes, out='edit')            # no suffix -> collapse\n\n# after\nforge('noir', takes, out='edit.mp4')        # distinct from .fcpxml/.edl/.json","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef out_is_safe(out):\n    p = Path(out)\n    if p.suffix.lower() in {'.fcpxml', '.edl', '.json'} or not p.suffix:\n        return False\n    return len({p, *(p.with_suffix(s) for s in ('.fcpxml', '.edl', '.json'))}) == 4","typeGuard":null,"tryCatchPattern":"try:\n    forge(genre, takes, out=out)\nexcept ValueError as e:\n    if 'distinct from' in str(e):\n        out = str(Path(out).with_suffix('.mp4')) if Path(out).suffix else out + '.mp4'\n        forge(genre, takes, out=out)\n    else:\n        raise","preventionTips":["Always give --out a video suffix (.mp4, .mov) that is not .fcpxml/.edl/.json.","Never pass a timeline/manifest path as the video output.","Validate the suffix in wrapper scripts before invoking forge."],"tags":["cli","validation","filesystem","python"],"backgroundTag":"invalid-argument-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"}