Spaces:
Running on Zero
Running on Zero
| """Validation helpers for ARDY's continuous action timeline.""" | |
| from __future__ import annotations | |
| import math | |
| from typing import Any | |
| MIN_SEGMENT_SECONDS = 2.0 | |
| MAX_SEGMENT_SECONDS = 4.0 | |
| MAX_TOTAL_SECONDS = 8.0 | |
| MAX_SEGMENTS = 8 | |
| MAX_PROMPT_CHARS = 500 | |
| def _rows(value: Any) -> list: | |
| if value is None: | |
| return [] | |
| if hasattr(value, "values"): | |
| return value.values.tolist() | |
| if hasattr(value, "tolist") and not isinstance(value, list): | |
| return value.tolist() | |
| return list(value) | |
| def normalize_timeline(value: Any, *, fps: float) -> list[dict]: | |
| """Normalize Gradio table rows and calculate exact frame ranges.""" | |
| if not math.isfinite(float(fps)) or float(fps) <= 0: | |
| raise ValueError("FPS must be greater than zero.") | |
| clean_rows = [] | |
| for row in _rows(value): | |
| if row is None: | |
| continue | |
| row = list(row) | |
| prompt = " ".join(str(row[0] if row else "").split()) | |
| duration_value = row[1] if len(row) > 1 else None | |
| if not prompt and duration_value in (None, ""): | |
| continue | |
| if not prompt: | |
| raise ValueError("Every timeline row must contain an action prompt.") | |
| if len(prompt) > MAX_PROMPT_CHARS: | |
| raise ValueError( | |
| f"Each action prompt must contain at most {MAX_PROMPT_CHARS} characters." | |
| ) | |
| try: | |
| duration = float(duration_value) | |
| except (TypeError, ValueError) as exc: | |
| raise ValueError( | |
| f"Duration for '{prompt}' must be a number." | |
| ) from exc | |
| if not math.isfinite(duration) or not MIN_SEGMENT_SECONDS <= duration <= MAX_SEGMENT_SECONDS: | |
| raise ValueError( | |
| f"Each segment must be between {MIN_SEGMENT_SECONDS:g} " | |
| f"and {MAX_SEGMENT_SECONDS:g} seconds." | |
| ) | |
| clean_rows.append((prompt, duration)) | |
| if not clean_rows: | |
| raise ValueError("Add at least one action to the timeline.") | |
| if len(clean_rows) > MAX_SEGMENTS: | |
| raise ValueError(f"The timeline supports at most {MAX_SEGMENTS} segments.") | |
| if sum(duration for _, duration in clean_rows) > MAX_TOTAL_SECONDS + 1e-6: | |
| raise ValueError( | |
| f"The combined timeline may not exceed {MAX_TOTAL_SECONDS:g} seconds." | |
| ) | |
| segments = [] | |
| start_frame = 0 | |
| for prompt, duration in clean_rows: | |
| frame_count = max(1, int(round(duration * float(fps)))) | |
| end_frame = start_frame + frame_count | |
| segments.append( | |
| { | |
| "prompt": prompt, | |
| "duration_seconds": frame_count / float(fps), | |
| "start_frame": start_frame, | |
| "end_frame": end_frame, | |
| } | |
| ) | |
| start_frame = end_frame | |
| return segments | |