calesthio/OpenMontage · error · ValueError
seconds must be one of: 4, 8, 12
Error message
seconds must be one of: 4, 8, 12
What it means
ValueError raised by SoraVideo._normalize_seconds when the duration value — taken from 'seconds' or fallback 'duration', stripped, lowercased, and trailing 's' removed — is not in _ALLOWED_SECONDS {4, 8, 12}. OpenAI's video API only generates clips at these discrete lengths; arbitrary durations like 6 or 15 are rejected client-side.
Source
Thrown at tools/video/sora_video.py:256
if model not in _ALLOWED_MODELS:
raise ValueError("model must be one of: sora-2, sora-2-pro")
return model
@staticmethod
def _normalize_size(inputs: dict[str, Any], model: str) -> str:
default_size = "1280x720" if inputs.get("aspect_ratio") == "16:9" else _DEFAULT_SIZE
size = str(inputs.get("size", default_size)).strip().lower()
allowed = {"1280x720", "720x1280"} if model == "sora-2" else set(_ALLOWED_SIZES)
if size not in allowed:
raise ValueError(f"size must be one of: {', '.join(sorted(allowed))} for model {model}")
return size
@staticmethod
def _normalize_seconds(inputs: dict[str, Any]) -> str:
seconds = str(inputs.get("seconds") or inputs.get("duration") or _DEFAULT_SECONDS).strip().lower()
seconds = seconds[:-1] if seconds.endswith("s") else seconds
if seconds not in _ALLOWED_SECONDS:
raise ValueError("seconds must be one of: 4, 8, 12")
return seconds
@staticmethod
def _get_status_value(video: Any) -> str:
if isinstance(video, dict):
return str(video.get("status") or video.get("state") or "unknown")
return str(getattr(video, "status", None) or getattr(video, "state", None) or "unknown")
@staticmethod
def _get_video_id(video: Any) -> str | None:
if isinstance(video, dict):
value = video.get("id")
return value if isinstance(value, str) else None
value = getattr(video, "id", None)
return value if isinstance(value, str) else None
@staticmethod
def _file_to_data_uri(path: Path) -> str:View on GitHub (pinned to 95e1c3d0ab)
Solutions
- Snap the desired duration to the nearest allowed value of 4, 8, or 12 seconds.
- For non-standard lengths, generate at 8 or 12 and trim with ffmpeg afterwards.
- Pass plain integer strings ('4', '8', '12') — avoid decimals and unit suffixes other than a bare 's'.
Example fix
# before
inputs = {"seconds": "6"}
# after
inputs = {"seconds": "8"} # trim to 6s in post if exact length matters Defensive patterns
Strategy: validation
Validate before calling
raw = str(inputs.get("seconds") or inputs.get("duration") or "8").strip().lower().rstrip("s")
if raw not in {"4", "8", "12"}:
target = float(raw)
inputs["seconds"] = min(("4", "8", "12"), key=lambda s: abs(int(s) - target)) Type guard
def is_valid_sora_seconds(v) -> bool:
raw = str(v).strip().lower()
raw = raw[:-1] if raw.endswith("s") else raw
return raw in {"4", "8", "12"} Prevention
- Snap storyboard beat durations to 4/8/12 before calling Sora.
- Never pass decimal durations; trim to exact length in post instead.
When it happens
Trigger: Passing seconds='6', seconds='15s', duration=10, or '4.0' (string '4.0' does not match '4' after normalization). A single trailing 's' is tolerated ('8s' -> '8') but '8sec' or '4.5s' are not.
Common situations: Storyboards computed in arbitrary seconds (e.g. one 6-second scene per beat); unit mismatches passing milliseconds; LLM-generated inputs carrying decimal durations.
Related errors
- ${res.status} ${url}
- fetch failed ${r.status}: ${url}
- path escapes project
- Checkpoint failed schema validation: {exc.message}
- duration must be an integer from 4 to {max_seconds} or -1
AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15).
Data as JSON: /api/errors/dffcec5aef33c378.
Report an issue: GitHub.