| from __future__ import annotations |
|
|
| import math |
| import re |
|
|
| import gradio as gr |
|
|
| TIMED_PROMPT_EXAMPLE = "00:00\nA calm cinematic opening shot.\n\n00:30\nThe mood becomes tense and dramatic." |
| TIMED_PROMPT_TIMESTAMP_RE = re.compile(r"^\d{1,2}:\d{2}(?::\d{2})?(?:\.\d+)?$") |
|
|
|
|
| def parse_time_input(value, *, label: str, allow_empty: bool) -> float | None: |
| if value is None: |
| return None if allow_empty else 0.0 |
| if isinstance(value, (int, float)): |
| if not math.isfinite(float(value)): |
| raise gr.Error(f"{label} must be a finite time value.") |
| return max(0.0, float(value)) |
| text = str(value).strip() |
| if len(text) == 0: |
| return None if allow_empty else 0.0 |
| if ":" not in text: |
| try: |
| seconds = float(text) |
| if not math.isfinite(seconds): |
| raise ValueError |
| return max(0.0, seconds) |
| except ValueError as exc: |
| raise gr.Error(f"{label} must be a number of seconds, MM:SS(.xx), or HH:MM:SS(.xx).") from exc |
| parts = text.split(":") |
| if len(parts) not in (2, 3): |
| raise gr.Error(f"{label} must be a number of seconds, MM:SS(.xx), or HH:MM:SS(.xx).") |
| try: |
| if len(parts) == 2: |
| minutes = int(parts[0]) |
| seconds = float(parts[1]) |
| if not math.isfinite(seconds): |
| raise ValueError |
| return max(0.0, minutes * 60.0 + seconds) |
| hours = int(parts[0]) |
| minutes = int(parts[1]) |
| seconds = float(parts[2]) |
| if not math.isfinite(seconds): |
| raise ValueError |
| return max(0.0, hours * 3600.0 + minutes * 60.0 + seconds) |
| except ValueError as exc: |
| raise gr.Error(f"{label} must be a number of seconds, MM:SS(.xx), or HH:MM:SS(.xx).") from exc |
|
|
|
|
| def parse_prompt_schedule(prompt_text: str) -> list[tuple[float, str]]: |
| text = str(prompt_text or "").replace("\r\n", "\n").replace("\r", "\n").strip() |
| if len(text) == 0: |
| return [(0.0, "")] |
| blocks = [block.strip() for block in re.split(r"\n\s*\n", text) if len(block.strip()) > 0] |
| first_line = text.split("\n", 1)[0].strip() |
| if len(blocks) <= 1 and not TIMED_PROMPT_TIMESTAMP_RE.fullmatch(first_line): |
| return [(0.0, text)] |
| schedule: list[tuple[float, str]] = [] |
| for block in blocks: |
| lines = block.split("\n") |
| timestamp_line = lines[0].strip() |
| if not TIMED_PROMPT_TIMESTAMP_RE.fullmatch(timestamp_line): |
| raise gr.Error( |
| "Timed prompts must be separated by blank lines, and each block must start with a timestamp like MM:SS(.xx) or HH:MM:SS(.xx).\n\n" |
| f"Example:\n{TIMED_PROMPT_EXAMPLE}" |
| ) |
| prompt_body = "\n".join(lines[1:]).strip() |
| if len(prompt_body) == 0: |
| raise gr.Error( |
| "Each timed prompt block must contain prompt text after its timestamp.\n\n" |
| f"Example:\n{TIMED_PROMPT_EXAMPLE}" |
| ) |
| schedule.append((float(parse_time_input(timestamp_line, label="Timed prompt timestamp", allow_empty=False) or 0.0), prompt_body)) |
| return sorted(schedule, key=lambda item: item[0]) |
|
|
|
|
| def resolve_prompt_for_chunk(prompt_schedule: list[tuple[float, str]], chunk_start_seconds: float, default_prompt: str) -> str: |
| prompt_text = str(default_prompt or "") |
| for start_seconds, scheduled_prompt in prompt_schedule: |
| if float(start_seconds) <= float(chunk_start_seconds) + 1e-9: |
| prompt_text = scheduled_prompt |
| else: |
| break |
| return prompt_text |
|
|