affaan-m/ECC · error · ValueError

clip ( ) has non-positive duration

Error message

clip {i} ({path}) has non-positive duration {dur!r}

What it means

_normalise() rejects clips whose duration is missing, zero, or negative (float(c.get('duration') or 0.0) then dur <= 0). A timeline event with no positive length is meaningless, so the builder raises with the clip index, path, and offending duration.

Solutions

  1. Set a positive 'duration' (seconds) on every clip dict
  2. Filter or repair zero-duration clips before building
  3. Check upstream in/out-point math (end - start) for degenerate ranges
  4. Measure clip durations with ffprobe and fill them into the manifest

Example fix

// before
clips = [{"path": "a.mp4", "duration": 0}]
// after
clips = [{"path": "a.mp4", "duration": max(0.04, end - start)}]
Defensive patterns

Strategy: validation

Validate before calling

bad = [i for i, c in enumerate(clips) if float(c.get('duration') or 0) <= 0]
if bad:
    raise ValueError(f'clips with non-positive duration: {bad}')

Type guard

def has_positive_duration(c: dict) -> bool:
    try:
        return float(c.get('duration') or 0) > 0
    except (TypeError, ValueError):
        return False

Try / catch

try:
    build_edl(clips, fps=24)
except ValueError as e:
    if 'non-positive duration' in str(e):
        logger.error('bad clip durations: %s', e)
        raise

Prevention

When it happens

Trigger: Calling build_fcpxml/build_edl with a clip {"path": p} lacking 'duration' (None -> 0.0), or duration=0, or a negative value from a bad cut calculation.

Common situations: Duration not yet measured when the manifest was generated; end-start computing to 0 for zero-length segments; None passed where seconds were expected.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

Thrown at skills/taste-distillation/scripts/taste/timeline.py:221

# ---------------------------------------------------------------------------


def _normalise(clips: Iterable[dict], fps: float | Fraction) -> list[dict]:
    """Validate clips and pre-compute integer frame counts and offsets.

    Returns dicts with ``path``, ``name``, ``frames`` (int, >= 1) and
    ``offset_frames`` (int). Working in frames from here down is what makes the
    sequence duration exactly the sum of the clip durations.
    """
    out: list[dict] = []
    offset = 0
    for i, c in enumerate(clips):
        path = str(c.get("path") or "")
        if not path:
            raise ValueError(f"clip {i} has no 'path'")
        dur = float(c.get("duration") or 0.0)
        if dur <= 0:
            raise ValueError(f"clip {i} ({path}) has non-positive duration {dur!r}")
        frames = max(1, seconds_to_frames(dur, fps))  # never emit a zero-length event
        name = str(c.get("name") or Path(path).stem)
        out.append(
            {
                "path": path,
                "name": name,
                "frames": frames,
                "offset_frames": offset,
                "seconds": dur,
            }
        )
        offset += frames
    if not out:
        raise ValueError("no clips to write - a timeline needs at least one event")
    return out


def _file_uri(path: str) -> str:

View on GitHub (pinned to 8321021c54)