TimeLapseForge / app.py
Adnan
Update app.py
184a7a7 verified
Raw
History Blame Contribute Delete
39.5 kB
# ============================================
# 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,
)