# ============================================ # PATCH 1: Fix huggingface_hub HfFolder removal # ============================================ try: from huggingface_hub import HfFolder except ImportError: import huggingface_hub class _HfFolderCompat: @classmethod def get_token(cls): try: return huggingface_hub.get_token() except Exception: return None @classmethod def save_token(cls, token): try: huggingface_hub.login(token=token, add_to_git_credential=False) except Exception: pass @classmethod def delete_token(cls): try: huggingface_hub.logout() except Exception: pass huggingface_hub.HfFolder = _HfFolderCompat # ============================================ """ TimeLapseForge v3.0 - Full Creative Studio Timelapse + T2I + I2I + T2V + I2V + Frames2Video + Ingredients2Video """ import os import json import tempfile import gradio as gr import numpy as np from PIL import Image from typing import List, Optional # ============================================ # PATCH 2: Fix gradio_client schema bug # ============================================ try: import gradio_client.utils as _gc_utils _orig_jst = _gc_utils._json_schema_to_python_type def _patched_jst(schema, defs=None): if isinstance(schema, bool) or not isinstance(schema, dict): return "Any" return _orig_jst(schema, defs) _gc_utils._json_schema_to_python_type = _patched_jst _orig_gt = _gc_utils.get_type def _patched_gt(schema): if isinstance(schema, bool) or not isinstance(schema, dict): return "Any" return _orig_gt(schema) _gc_utils.get_type = _patched_gt except Exception: pass # ============================================ from prompt_parser import PromptParser, QuickGenerator from frame_interpolator import FrameInterpolator from video_assembler import VideoAssembler from api_providers import ( PROVIDERS, PROVIDER_DISPLAY_NAMES, get_models_for_provider, get_provider_info, get_provider, ) from video_providers import ( VIDEO_PROVIDERS, VIDEO_PROVIDER_DISPLAY_NAMES, get_video_provider, get_video_provider_info, get_video_models_for_provider, ) # --- Init modules --- prompt_parser = PromptParser() quick_gen = QuickGenerator() interpolator = FrameInterpolator() assembler = VideoAssembler() # --- Constants --- LOCAL_MODELS = { "SDXL Turbo (Fast)": "stabilityai/sdxl-turbo", "SDXL Base (HQ)": "stabilityai/stable-diffusion-xl-base-1.0", "SD 1.5 (Light)": "runwayml/stable-diffusion-v1-5", } IMG_PROVIDER_CHOICES = [p["display_name"] for p in get_provider_info()] VID_PROVIDER_CHOICES = [p["display_name"] for p in get_video_provider_info()] def img_display_to_key(name): return PROVIDER_DISPLAY_NAMES.get(name, "openai") def vid_display_to_key(name): return VIDEO_PROVIDER_DISPLAY_NAMES.get(name, "fal_video") def update_img_models(name): key = img_display_to_key(name) models = get_models_for_provider(key) if models: return gr.update(choices=models, value=models[0]) return gr.update(choices=["custom"], value="custom") def update_vid_models(name): key = vid_display_to_key(name) models = get_video_models_for_provider(key) if models: return gr.update(choices=models, value=models[0]) return gr.update(choices=["custom"], value="custom") # ============================================ # TIMELAPSE FUNCTIONS (existing) # ============================================ def parse_input(json_text, quick_text, num_panels, mode): if json_text and json_text.strip(): result = prompt_parser.parse(json_text) if result["success"]: data = result["data"] prompts = prompt_parser.extract_prompts(data) summary = prompt_parser.get_summary(data) table = [[p["panel_id"], p["phase"], p["panel_title"], p["main_prompt"][:100] + "..."] for p in prompts] return summary, table, json.dumps(data, indent=2), gr.update(visible=True) return "Parse Error: " + str(result["error"]), [], "", gr.update(visible=False) elif quick_text and quick_text.strip(): data = quick_gen.generate(quick_text, int(num_panels), mode) prompts = prompt_parser.extract_prompts(data) summary = prompt_parser.get_summary(data) table = [[p["panel_id"], p["phase"], p["panel_title"], p["main_prompt"][:100] + "..."] for p in prompts] return (summary + "\n\n*Quick text -- use GPT JSON for better results*", table, json.dumps(data, indent=2), gr.update(visible=True)) return "Please paste JSON or enter quick text.", [], "", gr.update(visible=False) def generate_panels( parsed_json, gen_mode, local_model, provider_name, api_key, api_model, custom_base_url, custom_endpoint_url, strength, base_seed, steps, guidance, width, height, ref_image, progress=gr.Progress(), ): if not parsed_json: return [], [], "No JSON. Parse input first." try: data = json.loads(parsed_json) except json.JSONDecodeError: return [], [], "Invalid JSON." prompts = prompt_parser.extract_prompts(data) if not prompts: return [], [], "No panels in JSON." from image_generator import ImageGenerator if gen_mode == "Local (Free GPU)": mid = LOCAL_MODELS.get(local_model, "stabilityai/sdxl-turbo") gen = ImageGenerator(mode="local", local_model_id=mid) else: pkey = img_display_to_key(provider_name) if not api_key or not api_key.strip(): return [], [], "API key required." gen = ImageGenerator( mode="api", provider_name=pkey, api_key=api_key.strip(), api_model=api_model, custom_base_url=custom_base_url, custom_endpoint_url=custom_endpoint_url) progress(0, desc="Starting...") def cb(c, t): progress(c / t, desc="Panel " + str(c) + "/" + str(t)) gs = int(steps) if steps and int(steps) > 0 else None gg = float(guidance) if guidance is not None and float(guidance) >= 0 else None w = int(width) if width and int(width) > 0 else None h = int(height) if height and int(height) > 0 else None images = gen.generate_all_panels( prompts=prompts, strength=float(strength), base_seed=int(base_seed), steps=gs, guidance=gg, width=w, height=h, reference_image=ref_image, progress_callback=cb) gallery = [(img, prompts[i]["panel_title"] if i < len(prompts) else "Panel " + str(i+1)) for i, img in enumerate(images)] return gallery, images, "Generated " + str(len(images)) + " panels!" def interpolate_and_assemble( images_state, parsed_json, interp_mult, interp_method, fps, hold_sec, add_labels, add_progress, add_bookend, music_file, export_gif, progress=gr.Progress(), ): if not images_state: return None, None, None, "No images. Generate first." images = images_state mult = int(interp_mult) progress(0.1, desc="Interpolating...") if mult > 1: def icb(c, t): progress(0.1 + (c/t)*0.3) smooth = interpolator.interpolate_sequence(images, mult, interp_method, icb) else: smooth = list(images) labels = None if add_labels and parsed_json: try: data = json.loads(parsed_json) prm = prompt_parser.extract_prompts(data) if mult > 1: labels = [] for p in prm: labels.append(p.get("timestamp_label", "")) labels.extend([""] * mult) labels = labels[:len(smooth)] else: labels = [p.get("timestamp_label", "") for p in prm] except Exception: pass progress(0.5, desc="Video...") adj = float(hold_sec) / max(mult, 1) if mult > 1 else float(hold_sec) vpath = assembler.create_video( smooth, fps=int(fps), hold_seconds=adj, add_labels=add_labels, labels=labels, add_progress=add_progress, add_bookend_labels=add_bookend) if music_file: progress(0.8, desc="Audio...") vpath = assembler.add_audio_to_video(vpath, music_file) progress(0.9) comp = assembler.create_comparison_image(images[0], images[-1]) gpath = None if export_gif: gpath = assembler.create_gif(images) return vpath, comp, gpath, "Done! " + str(len(smooth)) + " frames" def regenerate_single( pnum, pjson, imgs, gmode, lmodel, prov, akey, amodel, curl, eurl, stren, seed, ): if not imgs or not pjson: return imgs, [], "No data." try: data = json.loads(pjson) prompts = prompt_parser.extract_prompts(data) except Exception: return imgs, [], "Invalid JSON." idx = int(pnum) - 1 if idx < 0 or idx >= len(imgs): return imgs, [], "Invalid panel number." from image_generator import ImageGenerator if gmode == "Local (Free GPU)": mid = LOCAL_MODELS.get(lmodel, "stabilityai/sdxl-turbo") gen = ImageGenerator(mode="local", local_model_id=mid) else: gen = ImageGenerator( mode="api", provider_name=img_display_to_key(prov), api_key=akey.strip(), api_model=amodel, custom_base_url=curl, custom_endpoint_url=eurl) _, updated = gen.regenerate_single_panel(idx, prompts, imgs, float(stren), int(seed)) gal = [(img, "Panel " + str(i+1)) for i, img in enumerate(updated)] return updated, gal, "Panel " + str(int(pnum)) + " regenerated!" # ============================================ # TEXT TO IMAGE # ============================================ def do_text_to_image(prompt, neg, provider_name, api_key, model, w, h, seed): if not prompt: return None, "Enter a prompt." if not api_key or not api_key.strip(): return None, "API key required." pkey = img_display_to_key(provider_name) prov = get_provider(pkey, api_key.strip()) try: img = prov.generate_image( prompt=prompt, negative_prompt=neg, width=int(w), height=int(h), seed=int(seed) if seed else None, model=model) return img, "Image generated!" except Exception as e: return None, "Error: " + str(e) # ============================================ # IMAGE TO IMAGE # ============================================ def do_image_to_image(source_img, prompt, neg, provider_name, api_key, model, strength, seed): if source_img is None: return None, "Upload a source image." if not prompt: return None, "Enter a prompt." if not api_key or not api_key.strip(): return None, "API key required." pkey = img_display_to_key(provider_name) prov = get_provider(pkey, api_key.strip()) try: if prov.supports_img2img: img = prov.img2img( prompt=prompt, image=source_img, strength=float(strength), negative_prompt=neg, seed=int(seed) if seed else None, model=model) else: img = prov.generate_image( prompt=prompt, negative_prompt=neg, width=source_img.width, height=source_img.height, seed=int(seed) if seed else None, model=model) return img, "Image transformed!" except Exception as e: return None, "Error: " + str(e) # ============================================ # TEXT TO VIDEO # ============================================ def do_text_to_video(prompt, provider_name, api_key, model, duration, seed, progress=gr.Progress()): if not prompt: return None, "Enter a prompt." if not api_key or not api_key.strip(): return None, "API key required." pkey = vid_display_to_key(provider_name) prov = get_video_provider(pkey, api_key.strip()) if not prov.supports_t2v: return None, "This provider does not support text-to-video." try: progress(0.1, desc="Generating video...") vpath = prov.text_to_video( prompt=prompt, duration=int(duration), seed=int(seed) if seed else None, model=model) progress(1.0, desc="Done!") return vpath, "Video generated!" except Exception as e: return None, "Error: " + str(e) # ============================================ # IMAGE TO VIDEO # ============================================ def do_image_to_video(source_img, prompt, provider_name, api_key, model, duration, seed, progress=gr.Progress()): if source_img is None: return None, "Upload an image." if not api_key or not api_key.strip(): return None, "API key required." pkey = vid_display_to_key(provider_name) prov = get_video_provider(pkey, api_key.strip()) if not prov.supports_i2v: return None, "This provider does not support image-to-video." try: progress(0.1, desc="Generating video...") vpath = prov.image_to_video( image=source_img, prompt=prompt or "", duration=int(duration), seed=int(seed) if seed else None, model=model) progress(1.0, desc="Done!") return vpath, "Video generated!" except Exception as e: return None, "Error: " + str(e) # ============================================ # FRAMES TO VIDEO # ============================================ def do_frames_to_video(frame_files, fps, hold_sec, interp_mult, interp_method, add_progress, add_bookend, music_file, progress=gr.Progress()): if not frame_files: return None, "Upload frames." images = [] for f in frame_files: try: if hasattr(f, 'name'): img = Image.open(f.name).convert("RGB") else: img = Image.open(f).convert("RGB") images.append(img) except Exception as e: return None, "Error loading frame: " + str(e) if len(images) < 2: return None, "Need at least 2 frames." # Resize all to match first image target_size = images[0].size images = [img.resize(target_size, Image.LANCZOS) for img in images] mult = int(interp_mult) progress(0.2, desc="Interpolating...") if mult > 1: smooth = interpolator.interpolate_sequence(images, mult, interp_method) else: smooth = images progress(0.5, desc="Assembling...") adj = float(hold_sec) / max(mult, 1) if mult > 1 else float(hold_sec) vpath = assembler.create_video( smooth, fps=int(fps), hold_seconds=adj, add_labels=False, add_progress=add_progress, add_bookend_labels=add_bookend) if music_file: progress(0.8, desc="Audio...") if hasattr(music_file, 'name'): vpath = assembler.add_audio_to_video(vpath, music_file.name) else: vpath = assembler.add_audio_to_video(vpath, music_file) progress(1.0) return vpath, "Video created from " + str(len(images)) + " frames!" # ============================================ # INGREDIENTS TO VIDEO # ============================================ def do_ingredients_to_video( ingredient_files, story_prompt, provider_name, api_key, model, num_transition_frames, strength, seed, fps, hold_sec, interp_mult, music_file, progress=gr.Progress(), ): if not ingredient_files: return None, None, "Upload ingredient images." if not api_key or not api_key.strip(): return None, None, "API key required." # Load ingredient images ingredients = [] for f in ingredient_files: try: if hasattr(f, 'name'): img = Image.open(f.name).convert("RGB") else: img = Image.open(f).convert("RGB") ingredients.append(img) except Exception as e: return None, None, "Error loading: " + str(e) if len(ingredients) < 2: return None, None, "Need at least 2 ingredient images." # Resize all to match first target = ingredients[0].size ingredients = [img.resize(target, Image.LANCZOS) for img in ingredients] pkey = img_display_to_key(provider_name) prov = get_provider(pkey, api_key.strip()) # Generate transition frames between ingredients all_frames = [ingredients[0]] total_pairs = len(ingredients) - 1 ntf = int(num_transition_frames) for pair_idx in range(total_pairs): progress(pair_idx / total_pairs * 0.7, desc="Transitions " + str(pair_idx+1) + "/" + str(total_pairs)) img_a = ingredients[pair_idx] img_b = ingredients[pair_idx + 1] if prov.supports_img2img: for t in range(1, ntf + 1): frac = t / (ntf + 1) # Blend images as reference arr_a = np.array(img_a).astype(np.float32) arr_b = np.array(img_b).astype(np.float32) blended = ((1 - frac) * arr_a + frac * arr_b).astype(np.uint8) blend_img = Image.fromarray(blended) trans_prompt = story_prompt or "smooth transition between scenes" trans_prompt += ", transition frame " + str(t) + " of " + str(ntf) try: gen_img = prov.img2img( prompt=trans_prompt, image=blend_img, strength=float(strength), seed=(int(seed) + pair_idx * 100 + t) if seed else None, model=model) all_frames.append(gen_img) except Exception: all_frames.append(blend_img) else: # Pure blend fallback for t in range(1, ntf + 1): frac = t / (ntf + 1) arr_a = np.array(img_a).astype(np.float32) arr_b = np.array(img_b).astype(np.float32) blended = ((1 - frac) * arr_a + frac * arr_b).astype(np.uint8) all_frames.append(Image.fromarray(blended)) all_frames.append(img_b) # Interpolate for smoothness mult = int(interp_mult) progress(0.75, desc="Smoothing...") if mult > 1: smooth = interpolator.interpolate_sequence(all_frames, mult, "blend") else: smooth = all_frames progress(0.85, desc="Assembling...") adj = float(hold_sec) / max(mult, 1) if mult > 1 else float(hold_sec) vpath = assembler.create_video( smooth, fps=int(fps), hold_seconds=adj, add_labels=False, add_progress=True, add_bookend_labels=True) if music_file: if hasattr(music_file, 'name'): vpath = assembler.add_audio_to_video(vpath, music_file.name) else: vpath = assembler.add_audio_to_video(vpath, music_file) comp = assembler.create_comparison_image(ingredients[0], ingredients[-1]) progress(1.0) return vpath, comp, "Video from " + str(len(ingredients)) + " ingredients, " + str(len(smooth)) + " frames!" # =============================================== # GRADIO UI # =============================================== HEADER = ( "# TimeLapseForge v3.0 -- Full Creative Studio\n" "### Timelapse | Text-to-Image | Image-to-Image | Text-to-Video | Image-to-Video " "| Frames-to-Video | Ingredients-to-Video\n\n" "**13+ Image APIs** + **7 Video APIs** -- Use YOUR API keys" ) API_HELP = ( "### Image Generation APIs\n\n" "| Provider | Key | Extra Package? |\n|---|---|---|\n" "| Stability AI | [Get Key](https://platform.stability.ai/account/keys) | No |\n" "| HuggingFace | [Get Key](https://huggingface.co/settings/tokens) | No |\n" "| Fireworks AI | [Get Key](https://fireworks.ai/account/api-keys) | No |\n" "| Ideogram | [Get Key](https://ideogram.ai/manage-api) | No |\n" "| Leonardo | [Get Key](https://app.leonardo.ai/api-access) | No |\n" "| OpenAI | [Get Key](https://platform.openai.com/api-keys) | openai |\n" "| Replicate | [Get Key](https://replicate.com/account/api-tokens) | replicate |\n" "| Together AI | [Get Key](https://api.together.xyz/settings/api-keys) | together |\n" "| Fal.ai | [Get Key](https://fal.ai/dashboard/keys) | fal-client |\n" "| Google Gemini | [Get Key](https://aistudio.google.com/apikey) | google-generativeai |\n" "\n### Video Generation APIs\n\n" "| Provider | Key | Extra Package? |\n|---|---|---|\n" "| Stability Video | [Get Key](https://platform.stability.ai/account/keys) | No |\n" "| Runway | [Get Key](https://dev.runwayml.com/) | No |\n" "| Luma | [Get Key](https://lumalabs.ai/api) | No |\n" "| MiniMax | [Get Key](https://platform.minimaxi.com/) | No |\n" "| HuggingFace Video | [Get Key](https://huggingface.co/settings/tokens) | No |\n" "| Replicate Video | [Get Key](https://replicate.com/account/api-tokens) | replicate |\n" "| Fal.ai Video | [Get Key](https://fal.ai/dashboard/keys) | fal-client |\n" ) with gr.Blocks( title="TimeLapseForge v3.0", theme=gr.themes.Soft(primary_hue="emerald", secondary_hue="blue"), ) as app: images_state = gr.State(value=[]) parsed_json_state = gr.State(value="") gr.Markdown(HEADER) with gr.Tabs(): # ============================== # TAB: TIMELAPSE PIPELINE # ============================== with gr.Tab("Timelapse Pipeline"): with gr.Tabs(): with gr.Tab("1. Parse JSON"): with gr.Row(): with gr.Column(scale=2): tl_json = gr.Textbox(label="Paste GPT JSON", lines=12) with gr.Column(scale=1): gr.Markdown("**-- OR -- Quick Text:**") tl_quick = gr.Textbox(label="Quick Command", lines=2, placeholder="Restore a rusty 1969 Camaro SS") tl_panels = gr.Slider(minimum=8, maximum=40, value=20, step=1, label="Panels") tl_mode = gr.Dropdown(choices=["restoration", "creation"], value="restoration", label="Mode") tl_parse_btn = gr.Button("Parse and Preview", variant="primary", size="lg") tl_parse_status = gr.Markdown("") tl_table = gr.Dataframe(headers=["ID", "Phase", "Title", "Preview"], visible=False, wrap=True) with gr.Tab("2. Generate"): with gr.Row(): with gr.Column(scale=1): tl_gen_mode = gr.Radio(choices=["Local (Free GPU)", "API (Your Key)"], value="API (Your Key)", label="Mode") with gr.Group(visible=False) as tl_local_grp: tl_local_model = gr.Dropdown(choices=list(LOCAL_MODELS.keys()), value="SDXL Turbo (Fast)", label="Local Model") with gr.Group(visible=True) as tl_api_grp: tl_img_prov = gr.Dropdown(choices=IMG_PROVIDER_CHOICES, value=IMG_PROVIDER_CHOICES[0], label="Provider") tl_api_key = gr.Textbox(label="API Key", type="password") tl_api_model = gr.Dropdown(choices=["dall-e-3"], value="dall-e-3", label="Model", allow_custom_value=True) with gr.Accordion("Custom API", open=False): tl_curl = gr.Textbox(label="Custom Base URL") tl_eurl = gr.Textbox(label="Direct Endpoint URL") tl_strength = gr.Slider(minimum=0.15, maximum=0.70, value=0.38, step=0.01, label="Change Strength") tl_seed = gr.Number(value=42, label="Seed", precision=0) with gr.Row(): tl_steps = gr.Slider(minimum=0, maximum=50, value=0, step=1, label="Steps (0=auto)") tl_cfg = gr.Slider(minimum=-1, maximum=15, value=-1, step=0.5, label="CFG (-1=auto)") with gr.Row(): tl_w = gr.Number(value=1024, label="Width", precision=0) tl_h = gr.Number(value=1024, label="Height", precision=0) tl_ref = gr.Image(label="Reference Image", type="pil") with gr.Column(scale=3): tl_gen_btn = gr.Button("Generate All Panels", variant="primary", size="lg") tl_gen_status = gr.Markdown("") tl_gallery = gr.Gallery(label="Panels", columns=5, rows=3, height=400, object_fit="contain") with gr.Accordion("Regenerate Single", open=False): with gr.Row(): tl_regen_num = gr.Number(value=1, label="Panel #", precision=0) tl_regen_btn = gr.Button("Regenerate", variant="secondary") tl_regen_status = gr.Markdown("") with gr.Tab("3. Assemble Video"): with gr.Row(): with gr.Column(scale=1): tl_interp = gr.Slider(minimum=1, maximum=8, value=3, step=1, label="Frame Multiplier") tl_interp_m = gr.Dropdown(choices=["blend", "flow"], value="blend", label="Method") tl_fps = gr.Slider(minimum=12, maximum=60, value=24, step=1, label="FPS") tl_hold = gr.Slider(minimum=0.3, maximum=5.0, value=1.5, step=0.1, label="Sec/Panel") tl_labels = gr.Checkbox(value=True, label="Timestamps") tl_prog = gr.Checkbox(value=True, label="Progress bar") tl_book = gr.Checkbox(value=True, label="BEFORE/AFTER") tl_music = gr.File(label="Music", file_types=[".mp3", ".wav", ".ogg"]) tl_gif = gr.Checkbox(value=False, label="Export GIF") with gr.Column(scale=2): tl_asm_btn = gr.Button("Create Video", variant="primary", size="lg") tl_asm_status = gr.Markdown("") tl_video = gr.Video(label="Timelapse Video") tl_comp = gr.Image(label="Before/After") tl_gif_out = gr.File(label="GIF") # ============================== # TAB: TEXT TO IMAGE # ============================== with gr.Tab("Text to Image"): with gr.Row(): with gr.Column(scale=1): t2i_prov = gr.Dropdown(choices=IMG_PROVIDER_CHOICES, value=IMG_PROVIDER_CHOICES[0], label="Provider") t2i_key = gr.Textbox(label="API Key", type="password") t2i_model = gr.Dropdown(choices=["dall-e-3"], value="dall-e-3", label="Model", allow_custom_value=True) t2i_prompt = gr.Textbox(label="Prompt", lines=4, placeholder="A futuristic city at sunset...") t2i_neg = gr.Textbox(label="Negative Prompt", lines=2, placeholder="blurry, low quality") with gr.Row(): t2i_w = gr.Number(value=1024, label="Width", precision=0) t2i_h = gr.Number(value=1024, label="Height", precision=0) t2i_seed = gr.Number(value=42, label="Seed", precision=0) with gr.Column(scale=2): t2i_btn = gr.Button("Generate Image", variant="primary", size="lg") t2i_status = gr.Markdown("") t2i_output = gr.Image(label="Generated Image", type="pil") # ============================== # TAB: IMAGE TO IMAGE # ============================== with gr.Tab("Image to Image"): with gr.Row(): with gr.Column(scale=1): i2i_prov = gr.Dropdown(choices=IMG_PROVIDER_CHOICES, value=IMG_PROVIDER_CHOICES[0], label="Provider") i2i_key = gr.Textbox(label="API Key", type="password") i2i_model = gr.Dropdown(choices=["dall-e-3"], value="dall-e-3", label="Model", allow_custom_value=True) i2i_source = gr.Image(label="Source Image", type="pil") i2i_prompt = gr.Textbox(label="Prompt", lines=3, placeholder="Transform into oil painting style...") i2i_neg = gr.Textbox(label="Negative Prompt", lines=2) i2i_strength = gr.Slider(minimum=0.1, maximum=0.9, value=0.5, step=0.05, label="Strength") i2i_seed = gr.Number(value=42, label="Seed", precision=0) with gr.Column(scale=2): i2i_btn = gr.Button("Transform Image", variant="primary", size="lg") i2i_status = gr.Markdown("") i2i_output = gr.Image(label="Result", type="pil") # ============================== # TAB: TEXT TO VIDEO # ============================== with gr.Tab("Text to Video"): with gr.Row(): with gr.Column(scale=1): t2v_prov = gr.Dropdown(choices=VID_PROVIDER_CHOICES, value=VID_PROVIDER_CHOICES[0], label="Provider") t2v_key = gr.Textbox(label="API Key", type="password") t2v_model = gr.Dropdown(choices=["auto"], value="auto", label="Model", allow_custom_value=True) t2v_prompt = gr.Textbox(label="Prompt", lines=4, placeholder="A drone shot flying over a mountain valley at sunrise...") t2v_dur = gr.Slider(minimum=2, maximum=10, value=5, step=1, label="Duration (sec)") t2v_seed = gr.Number(value=42, label="Seed", precision=0) with gr.Column(scale=2): t2v_btn = gr.Button("Generate Video", variant="primary", size="lg") t2v_status = gr.Markdown("") t2v_output = gr.Video(label="Generated Video") # ============================== # TAB: IMAGE TO VIDEO # ============================== with gr.Tab("Image to Video"): with gr.Row(): with gr.Column(scale=1): i2v_prov = gr.Dropdown(choices=VID_PROVIDER_CHOICES, value=VID_PROVIDER_CHOICES[0], label="Provider") i2v_key = gr.Textbox(label="API Key", type="password") i2v_model = gr.Dropdown(choices=["auto"], value="auto", label="Model", allow_custom_value=True) i2v_source = gr.Image(label="Source Image", type="pil") i2v_prompt = gr.Textbox(label="Motion Prompt (optional)", lines=3, placeholder="Camera slowly zooms in, leaves rustling...") i2v_dur = gr.Slider(minimum=2, maximum=10, value=4, step=1, label="Duration (sec)") i2v_seed = gr.Number(value=42, label="Seed", precision=0) with gr.Column(scale=2): i2v_btn = gr.Button("Animate Image", variant="primary", size="lg") i2v_status = gr.Markdown("") i2v_output = gr.Video(label="Generated Video") # ============================== # TAB: FRAMES TO VIDEO # ============================== with gr.Tab("Frames to Video"): with gr.Row(): with gr.Column(scale=1): f2v_files = gr.File(label="Upload Frames (in order)", file_count="multiple", file_types=[".png", ".jpg", ".jpeg", ".webp"]) f2v_fps = gr.Slider(minimum=12, maximum=60, value=24, step=1, label="FPS") f2v_hold = gr.Slider(minimum=0.3, maximum=5.0, value=1.5, step=0.1, label="Sec/Frame") f2v_interp = gr.Slider(minimum=1, maximum=8, value=3, step=1, label="Frame Multiplier") f2v_method = gr.Dropdown(choices=["blend", "flow"], value="blend", label="Interpolation") f2v_prog = gr.Checkbox(value=True, label="Progress bar") f2v_book = gr.Checkbox(value=True, label="BEFORE/AFTER") f2v_music = gr.File(label="Music", file_types=[".mp3", ".wav", ".ogg"]) with gr.Column(scale=2): f2v_btn = gr.Button("Create Video from Frames", variant="primary", size="lg") f2v_status = gr.Markdown("") f2v_output = gr.Video(label="Video") # ============================== # TAB: INGREDIENTS TO VIDEO # ============================== with gr.Tab("Ingredients to Video"): gr.Markdown( "Upload multiple **ingredient images** (key scenes/objects). " "AI generates smooth transition frames between them to create a cohesive video." ) with gr.Row(): with gr.Column(scale=1): ing_files = gr.File(label="Ingredient Images (in order)", file_count="multiple", file_types=[".png", ".jpg", ".jpeg", ".webp"]) ing_prompt = gr.Textbox(label="Story/Transition Prompt", lines=3, placeholder="Smooth cinematic transition between scenes...") ing_prov = gr.Dropdown(choices=IMG_PROVIDER_CHOICES, value=IMG_PROVIDER_CHOICES[0], label="Provider") ing_key = gr.Textbox(label="API Key", type="password") ing_model = gr.Dropdown(choices=["auto"], value="auto", label="Model", allow_custom_value=True) ing_trans = gr.Slider(minimum=2, maximum=10, value=4, step=1, label="Transition Frames per Pair") ing_strength = gr.Slider(minimum=0.2, maximum=0.7, value=0.4, step=0.05, label="AI Strength") ing_seed = gr.Number(value=42, label="Seed", precision=0) ing_fps = gr.Slider(minimum=12, maximum=60, value=24, step=1, label="FPS") ing_hold = gr.Slider(minimum=0.3, maximum=3.0, value=1.0, step=0.1, label="Sec/Frame") ing_interp = gr.Slider(minimum=1, maximum=6, value=3, step=1, label="Smooth Multiplier") ing_music = gr.File(label="Music", file_types=[".mp3", ".wav", ".ogg"]) with gr.Column(scale=2): ing_btn = gr.Button("Create Ingredients Video", variant="primary", size="lg") ing_status = gr.Markdown("") ing_video = gr.Video(label="Video") ing_comp = gr.Image(label="First vs Last") # ============================== # TAB: API KEYS GUIDE # ============================== with gr.Tab("API Keys Guide"): gr.Markdown(API_HELP) # ============================== # DYNAMIC UI # ============================== def toggle_tl_mode(mode): if mode == "Local (Free GPU)": return gr.update(visible=True), gr.update(visible=False) return gr.update(visible=False), gr.update(visible=True) tl_gen_mode.change(fn=toggle_tl_mode, inputs=[tl_gen_mode], outputs=[tl_local_grp, tl_api_grp]) tl_img_prov.change(fn=update_img_models, inputs=[tl_img_prov], outputs=[tl_api_model]) t2i_prov.change(fn=update_img_models, inputs=[t2i_prov], outputs=[t2i_model]) i2i_prov.change(fn=update_img_models, inputs=[i2i_prov], outputs=[i2i_model]) t2v_prov.change(fn=update_vid_models, inputs=[t2v_prov], outputs=[t2v_model]) i2v_prov.change(fn=update_vid_models, inputs=[i2v_prov], outputs=[i2v_model]) ing_prov.change(fn=update_img_models, inputs=[ing_prov], outputs=[ing_model]) # ============================== # EVENTS # ============================== # Timelapse tl_parse_btn.click(fn=parse_input, inputs=[tl_json, tl_quick, tl_panels, tl_mode], outputs=[tl_parse_status, tl_table, parsed_json_state, tl_table]) tl_gen_btn.click(fn=generate_panels, inputs=[parsed_json_state, tl_gen_mode, tl_local_model, tl_img_prov, tl_api_key, tl_api_model, tl_curl, tl_eurl, tl_strength, tl_seed, tl_steps, tl_cfg, tl_w, tl_h, tl_ref], outputs=[tl_gallery, images_state, tl_gen_status]) tl_regen_btn.click(fn=regenerate_single, inputs=[tl_regen_num, parsed_json_state, images_state, tl_gen_mode, tl_local_model, tl_img_prov, tl_api_key, tl_api_model, tl_curl, tl_eurl, tl_strength, tl_seed], outputs=[images_state, tl_gallery, tl_regen_status]) tl_asm_btn.click(fn=interpolate_and_assemble, inputs=[images_state, parsed_json_state, tl_interp, tl_interp_m, tl_fps, tl_hold, tl_labels, tl_prog, tl_book, tl_music, tl_gif], outputs=[tl_video, tl_comp, tl_gif_out, tl_asm_status]) # Text to Image t2i_btn.click(fn=do_text_to_image, inputs=[t2i_prompt, t2i_neg, t2i_prov, t2i_key, t2i_model, t2i_w, t2i_h, t2i_seed], outputs=[t2i_output, t2i_status]) # Image to Image i2i_btn.click(fn=do_image_to_image, inputs=[i2i_source, i2i_prompt, i2i_neg, i2i_prov, i2i_key, i2i_model, i2i_strength, i2i_seed], outputs=[i2i_output, i2i_status]) # Text to Video t2v_btn.click(fn=do_text_to_video, inputs=[t2v_prompt, t2v_prov, t2v_key, t2v_model, t2v_dur, t2v_seed], outputs=[t2v_output, t2v_status]) # Image to Video i2v_btn.click(fn=do_image_to_video, inputs=[i2v_source, i2v_prompt, i2v_prov, i2v_key, i2v_model, i2v_dur, i2v_seed], outputs=[i2v_output, i2v_status]) # Frames to Video f2v_btn.click(fn=do_frames_to_video, inputs=[f2v_files, f2v_fps, f2v_hold, f2v_interp, f2v_method, f2v_prog, f2v_book, f2v_music], outputs=[f2v_output, f2v_status]) # Ingredients to Video ing_btn.click(fn=do_ingredients_to_video, inputs=[ing_files, ing_prompt, ing_prov, ing_key, ing_model, ing_trans, ing_strength, ing_seed, ing_fps, ing_hold, ing_interp, ing_music], outputs=[ing_video, ing_comp, ing_status]) # =============================================== if __name__ == "__main__": app.launch( server_name="0.0.0.0", server_port=7860, show_api=False, share=False, )