{"record":{"id":"245f4da20fd4f116","repo":"affaan-m/ECC","slug":"nothing-to-concatenate-assemble","errorCode":null,"errorMessage":"nothing to concatenate","messagePattern":"nothing to concatenate","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-distillation/scripts/taste/assemble.py","lineNumber":181,"sourceCode":"        f\"[0:v]format=gbrp[base];\"\n        f\"[base][ov]blend=all_mode={blend or 'screen'}:shortest=1,format=yuv420p\"\n    )\n    _run([\n        \"ffmpeg\", \"-nostdin\", \"-loglevel\", \"error\", \"-y\",\n        # Keep the still alive until the video ends; shortest=1 otherwise\n        # terminates every shot after the image's single decoded frame.\n        \"-i\", str(clip), \"-loop\", \"1\", \"-i\", str(image), \"-filter_complex\", fc,\n        \"-c:v\", \"libx264\", \"-crf\", \"14\", \"-preset\", \"veryfast\",\n        \"-pix_fmt\", \"yuv420p\", \"-an\", str(dst),\n    ])\n    return Path(dst)\n\n\ndef concat(clips: list[str | Path], dst: str | Path, fps: float = 24.0) -> Path:\n    \"\"\"Join clips into one file. Assumes they already share codec and size.\"\"\"\n    clips = [Path(c) for c in clips]\n    if not clips:\n        raise ValueError(\"nothing to concatenate\")\n    dst = Path(dst)\n    dst.parent.mkdir(parents=True, exist_ok=True)\n    listing = dst.parent / f\"{dst.stem}_concat.txt\"\n    listing.write_text(\"\".join(f\"file '{c.resolve().as_posix()}'\\n\" for c in clips))\n    _run([\n        \"ffmpeg\", \"-nostdin\", \"-loglevel\", \"error\", \"-y\",\n        \"-f\", \"concat\", \"-safe\", \"0\", \"-i\", str(listing),\n        \"-vf\", f\"fps={fps:.6f}\",\n        \"-c:v\", \"libx264\", \"-crf\", \"16\", \"-pix_fmt\", \"yuv420p\", str(dst),\n    ])\n    listing.unlink(missing_ok=True)\n    return dst\n\n\ndef normalize(\n    src: str | Path,\n    dst: str | Path,\n    width: int,","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-distillation/scripts/taste/assemble.py#L163-L199","documentation":"concat() joins a list of clip files into one video via an ffmpeg concat demuxer invocation. It raises ValueError('nothing to concatenate') when the clips list is empty, because writing an empty concat listing and invoking ffmpeg would produce a meaningless/failed run.","triggerScenarios":"concat([]) called directly, or an upstream step (e.g. cut_take producing zero takes, filtered shot lists) yields an empty clips list that is passed through to concat.","commonSituations":"Shot detection found no shots so cut_take exported no clips; caller filtered all clips out by a predicate; path glob matched nothing; wiring bug passing the wrong (empty) list variable.","solutions":["Guard at the call site: only call concat when the clips list is non-empty, and decide what an empty result should mean for your pipeline.","Trace why the upstream clip list is empty — check cut_take output directory and shot detection results.","Log the number of clips before concat so empty lists surface earlier with context.","If clips are generated by globbing, verify the glob pattern and directory actually match produced files.","Fix any filter predicate that removed all clips unintentionally."],"exampleFix":"// before\nassemble.concat(clips, dst)  # ValueError if clips == []\n\n// after\nif not clips:\n    raise SystemExit(\"no takes were cut; nothing to concatenate\")\nassemble.concat(clips, dst)","handlingStrategy":"validation","validationCode":"if not clips:\n    raise SystemExit(\"no clips produced upstream; skipping concat\")","typeGuard":"def has_clips(clips: list) -> bool:\n    return len(clips) > 0 and all(Path(c).exists() for c in clips)","tryCatchPattern":"try:\n    assemble.concat(clips, dst)\nexcept ValueError as exc:\n    if str(exc) == \"nothing to concatenate\":\n        log.warning(\"concat skipped: no clips\")\n        return None","preventionTips":["Check the clip count after cut_take before concatenating","Log list lengths at each pipeline stage to spot empty lists early","Verify glob patterns match actually-produced clip files","Decide explicitly how the pipeline should behave when zero takes are cut","Filter predicates should warn when they remove every element"],"tags":["video","ffmpeg","empty-input","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"}