affaan-m/ECC · error · FalError

merge_videos() needs at least one video URL

Error message

merge_videos() needs at least one video URL

What it means

merge_videos concatenates a list of hosted video URLs via the fal.ai compose/merge endpoint. Because concatenating nothing is meaningless, the library raises FalError up front when the list is empty, before any network request is made.

Solutions

  1. Pass a non-empty list of video URLs to merge_videos
  2. Check that the list is populated before calling (assert video_urls)
  3. Trace why the upstream generation steps returned no URLs if the list should have content

Example fix

# before
merge_videos(clips)
# after
assert clips, 'no clips to merge'
merged = merge_videos(clips)
Defensive patterns

Strategy: validation

Validate before calling

if not video_urls:
    raise ValueError('merge_videos requires at least one URL')

Type guard

def has_videos(video_urls) -> bool:
    return bool(video_urls)

Prevention

When it happens

Trigger: merge_videos([]) or merge_videos(videos) where videos is an empty list — typically because earlier generation steps produced nothing or were filtered out.

Common situations: Pipeline stage collected video URLs into a list that ended up empty after filtering failed generations; forgetting to pass clip URLs generated in a previous step; variable shadowing or a failed upload leaving the list empty.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/2da7966335d15b5c. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/taste/falapi.py:647

    result = submit(ENDPOINTS["extract_frame"], payload, timeout)
    return first_url(result, ENDPOINTS["extract_frame"])


def compose(tracks: list[dict], *, timeout: int = 900) -> str:
    """Composite timeline tracks into one video. Returns the output URL.

    ``tracks`` is passed straight through so the caller owns the timeline
    shape; the ffmpeg-api track schema is another default worth verifying
    before a live run.
    """
    result = submit(ENDPOINTS["compose"], {"tracks": tracks}, timeout)
    return first_url(result, ENDPOINTS["compose"])


def merge_videos(video_urls: list[str], *, timeout: int = 900) -> str:
    """Concatenate videos end to end. Returns the merged URL."""
    if not video_urls:
        raise FalError("merge_videos() needs at least one video URL")
    payload = {"video_urls": list(video_urls)}
    result = submit(ENDPOINTS["merge_videos"], payload, timeout)
    return first_url(result, ENDPOINTS["merge_videos"])


# ---------------------------------------------------------------------------
# download
# ---------------------------------------------------------------------------


MAX_DOWNLOAD_BYTES = 2 * 1024 * 1024 * 1024  # bounded large video/GLB downloads


def _validate_download_url(url: str) -> None:
    try:
        parsed = urllib.parse.urlsplit(url)
        host = parsed.hostname or ""
        valid = (parsed.scheme == "https" and not parsed.username

View on GitHub (pinned to 8321021c54)