"""Pure helper functions (no torch / gradio imports, so they are easy to test).""" from __future__ import annotations DEFAULT_FRAME_RATE = 24.0 # Resolution presets: (width, height). All values are multiples of 64, which the # two-stage pipeline (half-res stage 1 -> x2 upscale -> stage 2) requires. RESOLUTIONS = { "high": {"16:9": (1536, 1024), "9:16": (1024, 1536), "1:1": (1024, 1024)}, "low": {"16:9": (768, 512), "9:16": (512, 768), "1:1": (768, 768)}, } RESOLUTION_MULTIPLE = 64 MIN_SIDE = 256 MAX_SIDE = 2048 def snap_dimension(value, multiple: int = RESOLUTION_MULTIPLE, lo: int = MIN_SIDE, hi: int = MAX_SIDE) -> int: """Round ``value`` to the nearest multiple of ``multiple`` inside [lo, hi]. The UI exposes free-form Width/Height boxes; an arbitrary value such as 1000 would crash the pipeline, so we always snap before generating. """ try: v = float(value) except (TypeError, ValueError): v = float(lo) snapped = int(round(v / multiple)) * multiple return max(lo, min(hi, snapped)) def compute_num_frames(duration: float, frame_rate: float = DEFAULT_FRAME_RATE) -> int: """Number of frames for ``duration`` seconds, in the 8k+1 form LTX expects.""" frames = int(float(duration) * frame_rate) + 1 return ((frames - 1 + 7) // 8) * 8 + 1 def detect_aspect_ratio(image) -> str: """Closest of '16:9', '9:16', '1:1' for a PIL image / numpy array (default 16:9).""" if image is None: return "16:9" size = getattr(image, "size", None) if isinstance(size, tuple) and len(size) == 2: # PIL.Image -> (w, h) w, h = size elif hasattr(image, "shape"): # numpy array -> (h, w, ...); its .size is an int h, w = image.shape[:2] else: return "16:9" if not w or not h: return "16:9" ratio = w / h candidates = {"16:9": 16 / 9, "9:16": 9 / 16, "1:1": 1.0} return min(candidates, key=lambda k: abs(ratio - candidates[k])) def pick_resolution(image, high_res: bool) -> tuple[int, int]: """(width, height) preset matching the image's aspect ratio and the quality tier.""" tier = "high" if high_res else "low" return RESOLUTIONS[tier][detect_aspect_ratio(image)]