{"record":{"id":"09f4019ee4c1c10a","repo":"affaan-m/ECC","slug":"merge-videos-needs-at-least-one-video-url","errorCode":null,"errorMessage":"merge_videos() needs at least one video URL","messagePattern":"merge_videos\\(\\) needs at least one video URL","errorType":"validation","errorClass":"FalError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/falapi.py","lineNumber":647,"sourceCode":"    result = submit(ENDPOINTS[\"extract_frame\"], payload, timeout)\n    return first_url(result, ENDPOINTS[\"extract_frame\"])\n\n\ndef compose(tracks: list[dict], *, timeout: int = 900) -> str:\n    \"\"\"Composite timeline tracks into one video. Returns the output URL.\n\n    ``tracks`` is passed straight through so the caller owns the timeline\n    shape; the ffmpeg-api track schema is another default worth verifying\n    before a live run.\n    \"\"\"\n    result = submit(ENDPOINTS[\"compose\"], {\"tracks\": tracks}, timeout)\n    return first_url(result, ENDPOINTS[\"compose\"])\n\n\ndef merge_videos(video_urls: list[str], *, timeout: int = 900) -> str:\n    \"\"\"Concatenate videos end to end. Returns the merged URL.\"\"\"\n    if not video_urls:\n        raise FalError(\"merge_videos() needs at least one video URL\")\n    payload = {\"video_urls\": list(video_urls)}\n    result = submit(ENDPOINTS[\"merge_videos\"], payload, timeout)\n    return first_url(result, ENDPOINTS[\"merge_videos\"])\n\n\n# ---------------------------------------------------------------------------\n# download\n# ---------------------------------------------------------------------------\n\n\nMAX_DOWNLOAD_BYTES = 2 * 1024 * 1024 * 1024  # bounded large video/GLB downloads\n\n\ndef _validate_download_url(url: str) -> None:\n    try:\n        parsed = urllib.parse.urlsplit(url)\n        host = parsed.hostname or \"\"\n        valid = (parsed.scheme == \"https\" and not parsed.username","sourceCodeStart":629,"sourceCodeEnd":665,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/falapi.py#L629-L665","documentation":"FalError raised by merge_videos() when the video_urls argument is empty (None, [], or falsy). Concatenating videos requires at least one clip URL, and the library rejects the empty case before making a provider call.","triggerScenarios":"Calling merge_videos([]) or merge_videos(video_urls=None); passing a list that was emptied by upstream filtering; passing a generator/iterable that was already consumed so it converts to an empty list.","commonSituations":"Downstream of a render loop that produced zero clips; passing a one-shot iterator (e.g. a map/generator) that list() drains to [] on the second read; variable initialized to [] and never filled due to an earlier error path.","solutions":["Pass a non-empty list of video URLs to merge_videos","Materialize any generator into a list first and check it is non-empty","Trace upstream clip generation — if no clips were produced, fix that step","Guard with an explicit emptiness check before calling"],"exampleFix":"// before\nclips = render_clips()  # generator\nurl = merge_videos(list(clips))\n// after\nclips = list(render_clips())\nif not clips:\n    raise SystemExit(\"no clips rendered; nothing to merge\")\nurl = merge_videos(clips)","handlingStrategy":"validation","validationCode":"clips = list(video_urls) if video_urls else []\nif not clips or not all(isinstance(u, str) and u.startswith(\"https://\") for u in clips):\n    raise SystemExit(\"merge_videos needs >=1 valid https video URL\")","typeGuard":null,"tryCatchPattern":"try:\n    merged = merge_videos(clips)\nexcept FalError:\n    logging.error(\"merge_videos called with empty list; clips=%r\", clips)\n    raise","preventionTips":["Materialize generators into lists before merging","Assert the render step produced at least one clip","Guard shared clip variables against being emptied by earlier error paths","Validate every URL is a non-empty https string before merging"],"tags":["validation","arguments","empty-input"],"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"}