from __future__ import annotations from collections.abc import Mapping, Sequence def detect_zerogpu_env( environ: Mapping[str, str], *, bool_keys: Sequence[str], text_keys: Sequence[str], true_values: set[str], ) -> bool: if any(str(environ.get(key, "")).strip().lower() in true_values for key in bool_keys): return True markers = " ".join(str(environ.get(key, "")) for key in text_keys).strip().lower() return "zerogpu" in markers or ("zero" in markers and "gpu" in markers) def parse_resolution_value(value: str, *, resolutions: Mapping[str, tuple[int, int]]) -> tuple[int, int]: text = str(value or "").strip() if text in resolutions: return resolutions[text] head = text.split("·", 1)[0].strip().lower().replace("×", "x") parts = [part.strip() for part in head.split("x")] if len(parts) != 2: raise ValueError("resolution must look like WIDTH × HEIGHT") width, height = int(parts[0]), int(parts[1]) if width < 64 or height < 64 or width % 64 != 0 or height % 64 != 0: raise ValueError("width and height must be positive multiples of 64") return width, height def safe_config_float( name: str, value, default: float, minimum: float, maximum: float, *, warnings: list[str], ) -> float: try: result = float(value) except Exception: warnings.append(f"Invalid {name}={value!r}; using {default}.") return default if result < minimum or result > maximum: clamped = min(maximum, max(minimum, result)) warnings.append(f"Out-of-range {name}={result}; clamped to {clamped}.") return clamped return result def safe_config_int(name: str, value, default: int, *, warnings: list[str]) -> int: try: return int(value) except Exception: warnings.append(f"Invalid {name}={value!r}; using {default}.") return default def safe_config_bool(value, default: bool, *, warnings: list[str], true_values: set[str]) -> bool: if isinstance(value, bool): return value if isinstance(value, str): normalized = value.strip().lower() if normalized in true_values: return True if normalized in {"0", "false", "f", "no", "n", "off"}: return False if isinstance(value, (int, float)): return bool(value) warnings.append(f"Invalid boolean default {value!r}; using {default}.") return default def frames_from_seconds( seconds: float, *, frame_rate: float, experimental_max_seconds: float, max_frames: int, ) -> int: seconds = min(experimental_max_seconds, max(1.0, float(seconds))) raw = round(seconds * frame_rate) frames = round((raw - 1) / 8) * 8 + 1 return min(max_frames, max(25, int(frames))) def supports_experimental_long( resolution_key: str, *, resolutions: Mapping[str, tuple[int, int]], ) -> bool: try: return parse_resolution_value(resolution_key, resolutions=resolutions) == (512, 512) except Exception: return False