affaan-m/ECC · error · ValueError

fps must be finite and positive

Error message

fps must be finite and positive

What it means

forge() accepts an optional fps override; when provided it must be a finite number greater than zero. NaN, infinity, zero, or negative values would produce invalid ffmpeg output, so it raises ValueError immediately.

Solutions

  1. Pass a positive finite fps, e.g. fps=24.0.
  2. Omit fps entirely to let forge use the first take's probed fps.
  3. Sanitize parsed values: reject non-finite and non-positive numbers before calling forge.
  4. Fix the config/CLI source so it provides a numeric frame rate.

Example fix

# before
fps = float(cfg.get('fps', 'nan'))
forge('noir', takes, fps=fps, out='edit.mp4')  # ValueError

# after
fps = cfg.get('fps')
fps = float(fps) if fps and math.isfinite(float(fps)) and float(fps) > 0 else None
forge('noir', takes, fps=fps, out='edit.mp4')  # None -> probe take fps
Defensive patterns

Strategy: validation

Validate before calling

import math

def fps_ok(fps):
    return fps is None or (isinstance(fps, (int, float)) and math.isfinite(fps) and fps > 0)

Type guard

def is_valid_fps(v):
    return isinstance(v, (int, float)) and not isinstance(v, bool) and math.isfinite(v) and v > 0

Try / catch

try:
    forge(genre, takes, fps=fps, out=out)
except ValueError as e:
    if 'fps' in str(e):
        forge(genre, takes, out=out)  # drop override, probe from take

Prevention

When it happens

Trigger: Calling forge(..., fps=0) or fps=-30; passing float('nan') or float('inf') (e.g. from a failed parse of a config/CLI value); passing a string that was never converted to float.

Common situations: Parsing fps from a config file where the value is 'auto' or empty and a float() call yields NaN via data paths; dividing by zero when computing fps from frame counts; hand-editing a YAML/JSON pipeline with fps: 0.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at skills/taste-application/scripts/forge.py:77

    duration: float | None = None,
    strength: float = 1.0,
    grade_base: bool = True,
    width: int | None = None,
    height: int | None = None,
    work: str = "out/forge_work",
    plan: list[dict] | None = None,
    fps: float | None = None,
) -> Path:
    out_path = Path(out)
    validate_output(out_path)
    if not takes or any(not Path(t).is_file() for t in takes):
        raise ValueError("all takes must be readable local files")
    if base_video and not Path(base_video).is_file():
        raise ValueError("base video must be a readable local file")
    if any(not Path(o).is_file() for o in (overlays or [])):
        raise ValueError("all overlays must be readable local files")
    if fps is not None and (not math.isfinite(fps) or fps <= 0):
        raise ValueError("fps must be finite and positive")
    sp = pack_mod.load(genre, root=root)
    tgt = grade_mod.load_stats(sp.grade_path)
    cad = cad_mod.load(sp.cadence_path)

    # Geometry comes from the first take unless overridden; everything else is
    # normalized to it so concat does not silently fail on a size mismatch.
    info0 = frame_mod.probe(takes[0])
    W = width or info0.width
    H = height or info0.height
    FPS = fps if fps is not None else info0.fps
    if not math.isfinite(FPS) or FPS <= 0:
        raise ValueError("source fps must be finite and positive; provide --fps")
    if W <= 0 or H <= 0:
        raise ValueError("output width and height must be positive")
    # Prior timelines reference these shot files. Each run owns a fresh child,
    # including failed runs, so retries cannot erase an existing edit.
    work_root = Path(work)
    work_root.mkdir(parents=True, exist_ok=True)

View on GitHub (pinned to 8321021c54)