affaan-m/ECC · error · ValueError
fps must be positive, got
Error message
fps must be positive, got {fps!r} What it means
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.
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.
Example fix
// before
fps = tl.GetSetting("timelineFrameRate") or 0
frac = fps_fraction(fps)
// after
fps = float(tl.GetSetting("timelineFrameRate") or proj.GetSetting("timelineFrameRate"))
if fps <= 0:
raise ValueError(f"timeline reports invalid fps: {fps!r}")
frac = fps_fraction(fps) Defensive patterns
Strategy: type-guard
Validate before calling
def safe_fps(raw):
try:
value = float(raw)
except (TypeError, ValueError):
return None
return value if value > 0 else None
fps = safe_fps(timeline.GetSetting("timelineFrameRate")) or safe_fps(project.GetSetting("timelineFrameRate")) Type guard
def is_positive_fps(value):
try:
return float(value) > 0 and float(value) == float(value) # excludes NaN
except (TypeError, ValueError):
return False Try / catch
try:
frac = fps_fraction(fps)
except (ValueError, TypeError) as e:
print(f"fps {fps!r} unusable: {e}; fall back to project frame rate")
frac = fps_fraction(float(project.GetSetting("timelineFrameRate"))) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
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
- fps must be finite and positive
- target timeline fps mismatch
- base_track_count exceeds target video tracks
- candidate fps/duration/retime ambiguity
- fps must be a reduced positive rational
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/4c05588ca11ca410.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/timeline.py:39
# 29.97 is exactly 30000/1001; a decimal timebase drifts ~3.6s/hour.
_NTSC: dict[float, Fraction] = {
23.976: Fraction(24000, 1001),
29.97: Fraction(30000, 1001),
47.952: Fraction(48000, 1001),
59.94: Fraction(60000, 1001),
119.88: Fraction(120000, 1001),
}
_NTSC_TOL = 0.02
def fps_fraction(fps: float | Fraction) -> Fraction:
"""Exact frame rate as a Fraction, snapping NTSC-family decimals."""
if isinstance(fps, Fraction):
return fps
fps = float(fps)
if fps <= 0:
raise ValueError(f"fps must be positive, got {fps!r}")
for nominal, exact in _NTSC.items():
if abs(fps - nominal) < _NTSC_TOL:
return exact
if abs(fps - round(fps)) < 1e-9:
return Fraction(int(round(fps)), 1)
return Fraction(fps).limit_denominator(100000)
def frame_duration(fps: float | Fraction) -> Fraction:
"""Duration of one frame, in seconds, as an exact fraction."""
return 1 / fps_fraction(fps)
def seconds_to_frames(seconds: float, fps: float | Fraction) -> int:
"""Quantise seconds to whole frames, rounding half away from zero."""
f = fps_fraction(fps)
exact = Fraction(float(seconds)).limit_denominator(1_000_000) * f
floor = exact.numerator // exact.denominatorView on GitHub (pinned to 8321021c54)