Download frames.py from build-small-hackathon/ai-video-generation: direct link, hf CLI and curl.
- Browser
- Download file 3.11 kB
-
https://huggingface.co/spaces/build-small-hackathon/ai-video-generation/resolve/main/frames.py
- Command line
-
hf download hf://spaces/build-small-hackathon/ai-video-generation/frames.py
-
curl -L -o frames.py https://huggingface.co/spaces/build-small-hackathon/ai-video-generation/resolve/main/frames.py
3.11 kB
| """ShotCraft Stage 2 — Frame Generator (FLUX.1-schnell, 12B). Owner: Rafal. | |
| Rendering runs on the Modal backend (model_runtime.flux_generate_batch): | |
| the whole reel is one backend call; a single-frame regen is a 1-element batch. | |
| """ | |
| from __future__ import annotations | |
| from model_runtime import flux_generate_batch | |
| from schemas import ConceptPackage, STYLE_SUFFIXES | |
| MODEL_ID = "black-forest-labs/FLUX.1-schnell" | |
| NUM_STEPS = 4 # schnell sweet spot, fixed server-side (NFR-1) | |
| SEED_BASE = 42 # FR-2.3: deterministic seed base | |
| SIZES = {"1:1": (1024, 1024), "16:9": (1024, 576)} # FR-2.6 | |
| def build_prompt(shot, style_preset: str, product_desc: str = "") -> str: | |
| """FR-2.3: prompt + palette keywords + shared style suffix. | |
| product_desc: locked canonical product description (Stage 1). FLUX weighs | |
| the START and END of a prompt most, so the locked product identity is | |
| stated FIRST and repeated LAST (after the style keywords), and the scene | |
| palette is explicitly confined to backdrop/props. This pins the product's | |
| own colors even in bold/colorful styles. | |
| """ | |
| palette = ", ".join(shot.color_palette[:5]) | |
| suffix = STYLE_SUFFIXES.get(style_preset, "") | |
| if not product_desc: | |
| return (f"{shot.image_prompt}. " | |
| f"Backdrop and props color palette (do not recolor the product): {palette}. {suffix}") | |
| desc = product_desc.strip().rstrip(".") | |
| return (f"Commercial product photo of {desc}. " | |
| f"{shot.image_prompt}. " | |
| f"Colorful palette for the backdrop, surfaces and props ONLY: {palette} - " | |
| f"the background may be colorful, the product may not change. {suffix}. " | |
| f"The product itself stays in its exact original colors: {desc}, " | |
| f"identical shape, materials and branding, unaltered") | |
| def seed_for(shot_id: int, regen_counter: int = 0) -> int: | |
| """FR-2.3/FR-2.4: stable per-shot seed; bump regen_counter to reroll one frame.""" | |
| return SEED_BASE + shot_id * 1000 + regen_counter | |
| def generate_frames(shots, style_preset: str, aspect: str = "1:1", | |
| regen_counters: dict | None = None, | |
| product_desc: str = "") -> list: | |
| """Render the given shots in ONE backend call. Returns PIL.Images in order. | |
| regen_counters: {shot_id: int} — bumps the seed of rerolled frames (FR-2.4). | |
| product_desc: locked product identity prefixed to every prompt.""" | |
| w, h = SIZES.get(aspect, SIZES["1:1"]) | |
| regen_counters = regen_counters or {} | |
| seeds = [seed_for(s.id, regen_counters.get(s.id, 0)) for s in shots] | |
| prompts = [build_prompt(s, style_preset, product_desc) for s in shots] | |
| return flux_generate_batch(prompts, w, h, seeds) | |
| def generate_frame(shot, style_preset: str, aspect: str = "1:1", regen_counter: int = 0, | |
| product_desc: str = ""): | |
| """Render one shot (used by per-frame regen). Returns PIL.Image.""" | |
| return generate_frames([shot], style_preset, aspect, | |
| regen_counters={shot.id: regen_counter}, | |
| product_desc=product_desc)[0] | |