Spaces:
Runtime error
Runtime error
| # ============================================ | |
| # PATCH 1: Fix huggingface_hub HfFolder removal | |
| # ============================================ | |
| try: | |
| from huggingface_hub import HfFolder | |
| except ImportError: | |
| import huggingface_hub | |
| class _HfFolderCompat: | |
| def get_token(cls): | |
| try: | |
| return huggingface_hub.get_token() | |
| except Exception: | |
| return None | |
| def save_token(cls, token): | |
| try: | |
| huggingface_hub.login(token=token, add_to_git_credential=False) | |
| except Exception: | |
| pass | |
| 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, | |
| ) | |