{"record":{"id":"8702cd7edefdf7d5","repo":"affaan-m/ECC","slug":"fps-must-be-finite-and-positive-resolve","errorCode":null,"errorMessage":"fps must be finite and positive","messagePattern":"fps must be finite and positive","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/resolve.py","lineNumber":29,"sourceCode":"import copy\nimport json\nimport math\nimport subprocess\nfrom fractions import Fraction\nfrom pathlib import Path\n\nfrom .timeline import fps_fraction\n\n\ndef _integer(value, label, minimum=0):\n    if isinstance(value, bool) or not isinstance(value, int) or value < minimum:\n        raise ValueError(f\"{label} must be an integer >= {minimum}\")\n    return value\n\n\ndef _fps(value):\n    if isinstance(value, bool):\n        raise ValueError(\"fps must be finite and positive\")\n    try:\n        number = float(Fraction(str(value)))\n        if not math.isfinite(number) or number <= 0:\n            raise ValueError(\"fps must be finite and positive\")\n        return fps_fraction(number)\n    except (TypeError, ZeroDivisionError, OverflowError) as exc:\n        raise ValueError(\"invalid fps\") from exc\n\n\ndef probe_asset(path):\n    \"\"\"Count decoded video frames; never infer source length from duration.\"\"\"\n    result = subprocess.run(\n        [\n            \"ffprobe\",\n            \"-v\",\n            \"error\",\n            \"-select_streams\",\n            \"v:0\",","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/resolve.py#L11-L47","documentation":"resolve._fps normalizes an fps value (accepting int, float, or fractional string like '30000/1001') through Fraction and rejects anything non-finite or <= 0 with 'fps must be finite and positive'. Booleans are rejected outright since bool is an int subclass. It is used by probe_asset, allocate_placements, and apply_placements.","triggerScenarios":"Passing fps=0, fps=-29.97, fps=float('nan') or inf, fps=True/False, or a value whose Fraction(str(value)) conversion raises TypeError/ZeroDivisionError (e.g. '1/0', None, a list); also unparseable strings fall into the except branch.","commonSituations":"Probe metadata returning 0 fps for a corrupt media file; dividing by a missing duration to compute fps producing inf/nan; config where fps is null; copy-pasted fractional strings like '30000/1001 ' with stray characters.","solutions":["Pass a positive finite number, e.g. fps=29.97 or fps='30000/1001' for exact NTSC rates","Guard upstream: if not (fps > 0) or not math.isfinite(fps): raise/skip before calling resolve functions","Fix the probe step so asset fps is read correctly (ffprobe) instead of defaulting to 0/None"],"exampleFix":"// before\napply_placements(asset, fps=None)  # raises\n// after\nfps = probe_asset(path).fps\nif not fps or not math.isfinite(fps) or fps <= 0:\n    fps = 30.0\napply_placements(asset, fps=fps)","handlingStrategy":"validation","validationCode":"import math\nok = (fps is not None and not isinstance(fps, bool)\n      and math.isfinite(float(fps)) and float(fps) > 0)\nassert ok, f\"fps must be finite and positive, got {fps!r}\"","typeGuard":null,"tryCatchPattern":"try:\n    result = allocate_placements(..., fps=fps)\nexcept ValueError:\n    fps = 30.0  # safe default for corrupt/missing probe metadata\n    result = allocate_placements(..., fps=fps)","preventionTips":["Read fps via ffprobe and fail early when it is 0 or missing","Never pass None or bool into fps parameters","Use fractional strings like '30000/1001' for exact NTSC rates"],"tags":["validation","fps","numeric","resolve"],"backgroundTag":"invalid-argument-value","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"}