{"record":{"id":"4c05588ca11ca410","repo":"affaan-m/ECC","slug":"fps-must-be-positive-got-fps-r-timeline","errorCode":null,"errorMessage":"fps must be positive, got {fps!r}","messagePattern":"fps must be positive, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/timeline.py","lineNumber":39,"sourceCode":"\n# 29.97 is exactly 30000/1001; a decimal timebase drifts ~3.6s/hour.\n_NTSC: dict[float, Fraction] = {\n    23.976: Fraction(24000, 1001),\n    29.97: Fraction(30000, 1001),\n    47.952: Fraction(48000, 1001),\n    59.94: Fraction(60000, 1001),\n    119.88: Fraction(120000, 1001),\n}\n_NTSC_TOL = 0.02\n\n\ndef fps_fraction(fps: float | Fraction) -> Fraction:\n    \"\"\"Exact frame rate as a Fraction, snapping NTSC-family decimals.\"\"\"\n    if isinstance(fps, Fraction):\n        return fps\n    fps = float(fps)\n    if fps <= 0:\n        raise ValueError(f\"fps must be positive, got {fps!r}\")\n    for nominal, exact in _NTSC.items():\n        if abs(fps - nominal) < _NTSC_TOL:\n            return exact\n    if abs(fps - round(fps)) < 1e-9:\n        return Fraction(int(round(fps)), 1)\n    return Fraction(fps).limit_denominator(100000)\n\n\ndef frame_duration(fps: float | Fraction) -> Fraction:\n    \"\"\"Duration of one frame, in seconds, as an exact fraction.\"\"\"\n    return 1 / fps_fraction(fps)\n\n\ndef seconds_to_frames(seconds: float, fps: float | Fraction) -> int:\n    \"\"\"Quantise seconds to whole frames, rounding half away from zero.\"\"\"\n    f = fps_fraction(fps)\n    exact = Fraction(float(seconds)).limit_denominator(1_000_000) * f\n    floor = exact.numerator // exact.denominator","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/timeline.py#L21-L57","documentation":"fps_fraction converts an fps value to an exact Fraction, snapping common NTSC decimals (23.976, 29.97, ...) to their exact ratios. It rejects any fps that is not strictly positive with this ValueError, including 0, negative numbers, NaN, and non-numeric types that cannot float()-convert cleanly (TypeError) or evaluate to <= 0.","triggerScenarios":"Calling fps_fraction(0), fps_fraction(-24), fps_fraction(float('nan')), or passing None/'' so float() yields 0.0 or raises; calling _format_name/_tc_to_frames with a timeline setting that returned an empty or zero string.","commonSituations":"timeline.GetSetting('timelineFrameRate') returning '' or '0' on an unsaved/new timeline; a config file with fps: 0 as a placeholder; forgetting to default fps when a project setting is missing.","solutions":["Pass a positive numeric fps (e.g. float from timeline.GetSetting('timelineFrameRate')) and validate > 0 before use.","Handle missing timeline settings by defaulting to the project frame rate: proj.GetSetting('timelineFrameRate').","For NTSC rates, pass the exact decimal (23.976, 29.97) or a Fraction — the function snaps within tolerance."],"exampleFix":"// before\nfps = tl.GetSetting(\"timelineFrameRate\") or 0\nfrac = fps_fraction(fps)\n// after\nfps = float(tl.GetSetting(\"timelineFrameRate\") or proj.GetSetting(\"timelineFrameRate\"))\nif fps <= 0:\n    raise ValueError(f\"timeline reports invalid fps: {fps!r}\")\nfrac = fps_fraction(fps)","handlingStrategy":"type-guard","validationCode":"def safe_fps(raw):\n    try:\n        value = float(raw)\n    except (TypeError, ValueError):\n        return None\n    return value if value > 0 else None\n\nfps = safe_fps(timeline.GetSetting(\"timelineFrameRate\")) or safe_fps(project.GetSetting(\"timelineFrameRate\"))","typeGuard":"def is_positive_fps(value):\n    try:\n        return float(value) > 0 and float(value) == float(value)  # excludes NaN\n    except (TypeError, ValueError):\n        return False","tryCatchPattern":"try:\n    frac = fps_fraction(fps)\nexcept (ValueError, TypeError) as e:\n    print(f\"fps {fps!r} unusable: {e}; fall back to project frame rate\")\n    frac = fps_fraction(float(project.GetSetting(\"timelineFrameRate\")))","preventionTips":["Never trust timelineFrameRate on unsaved/new timelines — it can be empty or '0'.","Default to the project-level frame rate when the timeline setting is missing.","Validate fps > 0 at config-load time, not deep inside frame math."],"tags":["fps","validation","argument","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"}