affaan-m/ECC · error · ValueError
fps must be a reduced positive rational
Error message
fps must be a reduced positive rational
What it means
_rate validates an fps object as a {numerator, denominator} pair of positive integers and additionally requires gcd(n, d) == 1, i.e. the rational must be in lowest terms. Non-reduced fractions like 48/2 raise ValueError('fps must be a reduced positive rational').
Solutions
- Reduce the fraction: divide both parts by math.gcd(numerator, denominator) before inserting into the bundle
- Use Fraction(n, d) and write Fraction.numerator/.denominator, which are always reduced
- Ensure both parts are >= 1 (zero or negative parts also fail _integer)
Example fix
// before
fps = {"numerator": 48, "denominator": 2}
// after
from math import gcd
fps = {"numerator": 48 // gcd(48, 2), "denominator": 2 // gcd(48, 2)} # 24/1 Defensive patterns
Strategy: validation
Validate before calling
from math import gcd
def reduced_rate(n, d):
assert n >= 1 and d >= 1 and gcd(n, d) == 1, f"unreduced rate {n}/{d}"
return {"numerator": n, "denominator": d} Try / catch
try:
bundle = build_application_bundle(data)
except ValueError as e:
if "reduced positive rational" in str(e):
print("reduce the fps fraction with gcd before building the bundle")
else:
raise Prevention
- Always build rates via Fraction(n, d) and read .numerator/.denominator
- Re-reduce rates after any arithmetic (speed changes, concatenations)
- Assert gcd == 1 in rate-producing helper functions
When it happens
Trigger: Building a bundle with fps given as 30000/1001 un-normalized after multiplication, or any numerator/denominator sharing a common factor (e.g. 24/2 instead of 12/1).
Common situations: Computing rates by multiplying/editing rationals without re-reducing, copying rates from tools that emit unreduced fractions, or hand-writing 30000/1001-style rates after scaling.
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
- candidate fps/duration/retime ambiguity
- fps must be finite and positive
- fps must be finite and positive
- fps must be positive, got
- fps must be positive, got
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/d8f168f6103858e2.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/integration.py:63
def _text(value: Any) -> str:
if not isinstance(value, str) or not value.strip():
raise ValueError("nonempty text required")
return value
def _integer(value: Any, minimum: int = 0) -> int:
if type(value) is not int or value < minimum:
raise ValueError("frame/count must be an exact integer in range")
return value
def _rate(value: Any) -> tuple[int, int]:
_object(value, {"numerator", "denominator"})
n, d = (_integer(value[k], 1) for k in ("numerator", "denominator"))
if math.gcd(n, d) != 1:
raise ValueError("fps must be a reduced positive rational")
return n, d
def _range(value: Any, bounds: list[int] | None = None) -> list[int]:
if not isinstance(value, list) or len(value) != 2:
raise ValueError("range must contain two frame integers")
start, end = (_integer(v) for v in value)
if start >= end or (bounds is not None and (start < bounds[0] or end > bounds[1])):
raise ValueError("frame range is empty or outside its bounds")
return value
def _overlap(a: list[int], b: list[int]) -> bool:
return a[0] < b[1] and b[0] < a[1]
def _sha(value: Any) -> str:
if not isinstance(value, str) or not re.fullmatch(r"[a-f0-9]{64}", value):View on GitHub (pinned to 8321021c54)