+ """
+
+ # JS:
+ # - finds the hidden gr.Audio upload inside the component with elem_id=target_audio_elem_id
+ # - sets the selected file onto it (DataTransfer) and dispatches change
+ js_on_load = """
+ (() => {{
+ // Helper: access Gradio shadow DOM safely
+ function grRoot() {{
+ const ga = document.querySelector("gradio-app");
+ return (ga && ga.shadowRoot) ? ga.shadowRoot : document;
+ }}
+ const root = grRoot();
+ const wrap = element.querySelector(".aud-wrap");
+ const drop = element.querySelector(".aud-drop");
+ const row = element.querySelector(".aud-row");
+ const player = element.querySelector(".aud-player");
+ const removeBtn = element.querySelector(".aud-remove");
+ const label = element.querySelector(".aud-filelabel");
+ const TARGET_ID = "__TARGET_ID__";
+ let currentUrl = null;
+ function findHiddenAudioFileInput() {{
+ const host = root.querySelector("#" + CSS.escape(TARGET_ID));
+ if (!host) return null;
+ // Gradio's Audio component contains an for upload.
+ // This selector works in most Gradio 3/4 themes.
+ const inp = host.querySelector('input[type="file"]');
+ return inp;
+ }}
+ function showDrop() {{
+ drop.style.display = "";
+ row.style.display = "none";
+ label.style.display = "none";
+ label.textContent = "";
+ }}
+ function showPlayer(filename) {{
+ drop.style.display = "none";
+ row.style.display = "flex";
+ if (filename) {{
+ label.textContent = "Loaded: " + filename;
+ label.style.display = "block";
+ }}
+ }}
+ function clearPreview() {{
+ player.pause();
+ player.removeAttribute("src");
+ player.load();
+ if (currentUrl) {{
+ URL.revokeObjectURL(currentUrl);
+ currentUrl = null;
+ }}
+ }}
+ function clearHiddenGradioAudio() {{
+ const fileInput = findHiddenAudioFileInput();
+ if (!fileInput) return;
+ // Clear file input (works by replacing its files with empty DataTransfer)
+ fileInput.value = "";
+ const dt = new DataTransfer();
+ fileInput.files = dt.files;
+ fileInput.dispatchEvent(new Event("input", { bubbles: true }));
+ fileInput.dispatchEvent(new Event("change", { bubbles: true }));
+ }}
+ function clearAll() {
+ clearPreview();
+
+ // Attempt DOM clear (still useful)
+ clearHiddenGradioAudio();
+
+ // Tell Gradio/Python explicitly to clear backend state
+ props.value = "__CLEAR__";
+ trigger("change", props.value);
+
+ showDrop();
+ }
+
+ function loadFileToPreview(file) {{
+ if (!file) return;
+ if (!file.type || !file.type.startsWith("audio/")) {{
+ alert("Please choose an audio file.");
+ return;
+ }}
+ clearPreview();
+ currentUrl = URL.createObjectURL(file);
+ player.src = currentUrl;
+ showPlayer(file.name);
+
+ }}
+ function pushFileIntoHiddenGradioAudio(file) {
+ const fileInput = findHiddenAudioFileInput();
+ if (!fileInput) {
+ console.warn("Could not find hidden gr.File input. Check elem_id:", TARGET_ID);
+ return;
+ }
+
+ // Hard reset (important for re-selecting same file)
+ fileInput.value = "";
+
+ const dt = new DataTransfer();
+ dt.items.add(file);
+ fileInput.files = dt.files;
+
+ // Trigger Gradio listeners
+ fileInput.dispatchEvent(new Event("input", { bubbles: true }));
+ fileInput.dispatchEvent(new Event("change", { bubbles: true }));
+ }
+
+ function handleFile(file) {{
+ loadFileToPreview(file);
+ pushFileIntoHiddenGradioAudio(file);
+ }}
+ // Click-to-browse uses a *local* ephemeral input (not Gradio’s),
+ // then we forward to hidden gr.Audio.
+ const localPicker = document.createElement("input");
+ localPicker.type = "file";
+ localPicker.accept = "audio/*";
+ localPicker.style.display = "none";
+ wrap.appendChild(localPicker);
+ localPicker.addEventListener("change", () => {{
+ const f = localPicker.files && localPicker.files[0];
+ if (f) handleFile(f);
+ localPicker.value = "";
+ }});
+ drop.addEventListener("click", () => localPicker.click());
+ drop.addEventListener("keydown", (e) => {{
+ if (e.key === "Enter" || e.key === " ") {{
+ e.preventDefault();
+ localPicker.click();
+ }}
+ }});
+ removeBtn.addEventListener("click", clearAll);
+ // Drag & drop
+ ["dragenter","dragover","dragleave","drop"].forEach(evt => {{
+ drop.addEventListener(evt, (e) => {{
+ e.preventDefault();
+ e.stopPropagation();
+ }});
+ }});
+ drop.addEventListener("dragover", () => drop.classList.add("dragover"));
+ drop.addEventListener("dragleave", () => drop.classList.remove("dragover"));
+ drop.addEventListener("drop", (e) => {{
+ drop.classList.remove("dragover");
+ const f = e.dataTransfer.files && e.dataTransfer.files[0];
+ if (f) handleFile(f);
+ }});
+ // init
+ showDrop();
+
+ function setPreviewFromPath(path) {
+ if (path === "__CLEAR__") path = null;
+
+ if (!path) {
+ clearPreview();
+ showDrop();
+ return;
+ }
+
+ // If path already looks like a URL, use it directly
+ // otherwise serve it through Gradio's file route.
+ let url = path;
+ if (!/^https?:\/\//.test(path) && !path.startsWith("gradio_api/file=") && !path.startsWith("/file=")) {
+ url = "gradio_api/file=" + path;
+ }
+
+ clearPreview();
+ player.src = url;
+ showPlayer(path.split("/").pop());
+ }
+
+ // ---- sync from Python (Examples / backend updates) ----
+ let last = props.value;
+ const syncFromProps = () => {
+ const v = props.value;
+
+ if (v !== last) {
+ last = v;
+ if (!v || v === "__CLEAR__") setPreviewFromPath(null);
+ else setPreviewFromPath(String(v));
+ }
+ requestAnimationFrame(syncFromProps);
+ };
+ requestAnimationFrame(syncFromProps);
+
+
+ }})();
+ """
+ js_on_load = js_on_load.replace("__TARGET_ID__", target_audio_elem_id)
+
+ super().__init__(
+ value=value,
+ html_template=html_template,
+ js_on_load=js_on_load,
+ **kwargs
+ )
+
+
+
+
+def generate_video_example(first_frame, prompt, camera_lora, resolution, radioanimated_mode, input_video, input_audio, end_frame, progress=gr.Progress(track_tqdm=True)):
+
+ w, h = apply_resolution(resolution)
+
+ with timer(f'generating with video path:{input_video} with duration:{duration} and LoRA:{camera_lora} in {w}x{h}'):
+ output_video = generate_video(
+ first_frame,
+ end_frame,
+ prompt,
+ 10,
+ input_video,
+ radioanimated_mode,
+ True,
+ 42,
+ True,
+ h,
+ w,
+ camera_lora,
+ input_audio,
+ progress
+ )
+ return output_video
+
+def get_duration(
+ first_frame,
+ end_frame,
+ prompt,
+ duration,
+ input_video,
+ radioanimated_mode,
+ enhance_prompt,
+ seed,
+ randomize_seed,
+ height,
+ width,
+ camera_lora,
+ audio_path,
+ progress
+):
+ extra_time = 0
+
+ if audio_path is not None:
+ extra_time += 10
+
+ if input_video is not None:
+ extra_time += 60
+
+ if duration <= 3:
+ return 60 + extra_time
+ elif duration <= 5:
+ return 80 + extra_time
+ elif duration <= 10:
+ return 120 + extra_time
+ else:
+ return 180 + extra_time
+
+@spaces.GPU(size="duration=get_duration)
+def generate_video(
+ first_frame,
+ end_frame,
+ prompt: str,
+ duration: float,
+ input_video = None,
+ generation_mode = "Image-to-Video",
+ enhance_prompt: bool = True,
+ seed: int = 67,
+ randomize_seed: bool = True,
+ height: int = DEFAULT_1_STAGE_HEIGHT,
+ width: int = DEFAULT_1_STAGE_WIDTH,
+ camera_lora: str = "No LoRA",
+ audio_path = None,
+ progress=gr.Progress(track_tqdm=True),
+):
+ ensure_models_loaded()
+ """
+ Generate a short cinematic video from a text prompt and optional input image using the LTX-2 distilled pipeline.
+ Args:
+ first_frame: Optional first frame for image-to-video. If provided, it is injected at frame 0 to guide motion.
+ end_frame: Optional last frame for image-to-video. If provided, it is injected at last frame to guide motion.
+ prompt: Text description of the scene, motion, and cinematic style to generate.
+ duration: Desired video length in seconds. Converted to frames using a fixed 24 FPS rate.
+ input_video: Optional conditioning video path (mp4). If provided, motion is guided by this video.
+ enhance_prompt: Whether to enhance the prompt using the prompt enhancer before encoding.
+ seed: Base random seed for reproducibility (ignored if randomize_seed is True).
+ randomize_seed: If True, a random seed is generated for each run.
+ height: Output video height in pixels.
+ width: Output video width in pixels.
+ camera_lora: Camera motion control LoRA to apply during generation (enables exactly one at runtime).
+ audio_path: Optiona audio file for soundtrack. Could be a lipsync audio or a background music or a mixture of both guiding the image-to-vidoe motion process.
+ progress: Gradio progress tracker.
+ Returns:
+
+ A tuple of:
+ - output_path: Path to the generated MP4 video file.
+ - seed: The seed used for generation.
+ Notes:
+ - Uses a fixed frame rate of 24 FPS.
+ - Prompt embeddings are generated externally to avoid reloading the text encoder.
+ - GPU cache is cleared after generation to reduce VRAM pressure.
+ - If an input image is provided, it is temporarily saved to disk for processing.
+ """
+
+ if (camera_lora != "No LoRA" or audio_path is not None) and duration == 15:
+ gr.Info("15s not avaiable when a LoRA or lipsync is activated, reducing to 10s for this generation")
+ duration = 10
+
+ if audio_path is None:
+ print(f'generating with duration:{duration} and LoRA:{camera_lora} in {width}x{height}')
+ else:
+ print(f'generating with duration:{duration} and audio in {width}x{height}')
+
+ # Randomize seed if checkbox is enabled
+ current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
+
+ # Calculate num_frames from duration (using fixed 24 fps)
+ frame_rate = 24.0
+ num_frames = int(duration * frame_rate) + 1 # +1 to ensure we meet the duration
+ video_seconds = int(duration)
+
+ with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
+ output_path = tmpfile.name
+
+
+ images = []
+ videos = []
+
+ if generation_mode == "Rotoscope":
+ if input_video is not None:
+ cond_mp4, first_png, used_frames = prepare_conditioning_video_mp4_no_pad(
+ video_path=input_video,
+ duration_frames=num_frames,
+ target_fps=frame_rate,
+ )
+
+ if first_frame is None:
+ images = [(first_png, 0, 1.0)]
+
+ if audio_path is None:
+ src_video_path = _coerce_video_path(input_video)
+ extracted_audio_tmp = extract_audio_wav_ffmpeg(src_video_path, target_sr=48000)
+
+ if extracted_audio_tmp is not None:
+ audio_path = extracted_audio_tmp
+
+ with timer("Pose selected: preprocessing conditioning video to pose..."):
+ cond_path = preprocess_video_to_pose_mp4(
+ video_path=cond_mp4,
+ width=width,
+ height=height,
+ fps=frame_rate,
+ )
+ videos = [(cond_path, 1.0)]
+ camera_lora = "Pose"
+
+ if first_frame is not None:
+ images = []
+ images.append((first_frame, 0, 1.0))
+
+ if generation_mode == "Inbetween":
+ if end_frame is not None:
+ end_idx = max(0, num_frames - 1)
+ images.append((end_frame, end_idx, 0.5))
+
+ embeddings, final_prompt, status = encode_prompt(
+ prompt=prompt,
+ enhance_prompt=enhance_prompt,
+ input_image=first_frame,
+ seed=current_seed,
+ negative_prompt="",
+ )
+
+ video_context = embeddings["video_context"].to("cuda", non_blocking=True)
+ audio_context = embeddings["audio_context"].to("cuda", non_blocking=True)
+ print("✓ Embeddings loaded successfully")
+
+
+ # free prompt enhancer / encoder temps ASAP
+ del embeddings, final_prompt, status
+ torch.cuda.empty_cache()
+
+ # ✅ if user provided audio, use a neutral audio_context
+ n_audio_context = None
+
+ if audio_path is not None:
+ with torch.inference_mode():
+ _, n_audio_context = encode_text_simple(text_encoder, "") # returns tensors on GPU already
+ del audio_context
+ audio_context = n_audio_context
+
+ if len(videos) == 0:
+ camera_lora = "Static"
+
+ torch.cuda.empty_cache()
+
+ # Map dropdown name -> adapter index
+ name_to_idx = {name: idx for name, idx in RUNTIME_LORA_CHOICES}
+ selected_idx = name_to_idx.get(camera_lora, -1)
+
+ enable_only_lora(pipeline._transformer, selected_idx)
+ torch.cuda.empty_cache()
+
+ # True video duration in seconds based on your rounding
+ video_seconds = (num_frames - 1) / frame_rate
+
+ if audio_path is not None:
+ input_waveform, input_waveform_sample_rate = match_audio_to_duration(
+ audio_path=audio_path,
+ target_seconds=video_seconds,
+ target_sr=48000, # pick what your model expects; 48k is common for AV models
+ to_mono=True, # set False if your model wants stereo
+ pad_mode="silence", # or "repeat" if you prefer looping over silence
+ device="cuda",
+ )
+ else:
+ input_waveform = None
+ input_waveform_sample_rate = None
+
+ with timer(f'generating with video path:{input_video} and LoRA:{camera_lora} in {width}x{height}'):
+ with torch.inference_mode():
+ pipeline(
+ prompt=prompt,
+ output_path=str(output_path),
+ seed=current_seed,
+ height=height,
+ width=width,
+ num_frames=num_frames,
+ frame_rate=frame_rate,
+ images=images,
+ video_conditioning=videos,
+ tiling_config=TilingConfig.default(),
+ video_context=video_context,
+ audio_context=audio_context,
+ input_waveform=input_waveform,
+ input_waveform_sample_rate=input_waveform_sample_rate,
+ )
+ del video_context, audio_context
+ torch.cuda.empty_cache()
+ print("successful generation")
+
+ return str(output_path)
+
+
+
+def apply_resolution(resolution: str):
+
+ if resolution == "16:9":
+ w, h = 768, 512
+ elif resolution == "4:3":
+ w, h = 640, 480
+ elif resolution == "1:1":
+ w, h = 512, 512
+ elif resolution == "9:16":
+ w, h = 512, 768
+
+ return int(w), int(h)
+
+def apply_duration(duration: str):
+ duration_s = int(duration[:-1])
+ return duration_s
+
+def on_mode_change(selected: str):
+ is_motion = (selected == "Rotoscope")
+ is_interpolate = (selected == "Inbetween")
+
+ return (gr.update(visible=is_motion), gr.update(visible=is_interpolate))
+
+
+
+css = """
+
+ /* Make the row behave nicely */
+ #controls-row {
+ display: none !important;
+ align-items: center;
+ gap: 12px;
+ flex-wrap: nowrap; /* or wrap if you prefer on small screens */
+ }
+
+ /* Stop these components from stretching */
+ #controls-row > * {
+ flex: 0 0 auto !important;
+ width: auto !important;
+ min-width: 0 !important;
+ }
+
+
+ #col-container {
+ margin: 0 auto;
+ max-width: 1600px;
+ }
+ #modal-container {
+ width: 100vw; /* Take full viewport width */
+ height: 100vh; /* Take full viewport height (optional) */
+ display: flex;
+ justify-content: center; /* Center content horizontally */
+ align-items: center; /* Center content vertically if desired */
+ }
+ #modal-content {
+ width: 100%;
+ max-width: 700px; /* Limit content width */
+ margin: 0 auto;
+ border-radius: 8px;
+ padding: 1.5rem;
+ }
+ #step-column {
+ padding: 10px;
+ border-radius: 8px;
+ box-shadow: var(--card-shadow);
+ margin: 10px;
+ }
+ #col-showcase {
+ margin: 0 auto;
+ max-width: 1100px;
+ }
+ .button-gradient {
+ background: linear-gradient(45deg, rgb(255, 65, 108), rgb(255, 75, 43), rgb(255, 155, 0), rgb(255, 65, 108)) 0% 0% / 400% 400%;
+ border: none;
+ padding: 14px 28px;
+ font-size: 16px;
+ font-weight: bold;
+ color: white;
+ border-radius: 10px;
+ cursor: pointer;
+ transition: 0.3s ease-in-out;
+ animation: 2s linear 0s infinite normal none running gradientAnimation;
+ box-shadow: rgba(255, 65, 108, 0.6) 0px 4px 10px;
+ }
+ .toggle-container {
+ display: inline-flex;
+ background-color: #ffd6ff; /* light pink background */
+ border-radius: 9999px;
+ padding: 4px;
+ position: relative;
+ width: fit-content;
+ font-family: sans-serif;
+ }
+ .toggle-container input[type="radio"] {
+ display: none;
+ }
+ .toggle-container label {
+ position: relative;
+ z-index: 2;
+ flex: 1;
+ text-align: center;
+ font-weight: 700;
+ color: #4b2ab5; /* dark purple text for unselected */
+ padding: 6px 22px;
+ border-radius: 9999px;
+ cursor: pointer;
+ transition: color 0.25s ease;
+ }
+ /* Moving highlight */
+ .toggle-highlight {
+ position: absolute;
+ top: 4px;
+ left: 4px;
+ width: calc(50% - 4px);
+ height: calc(100% - 8px);
+ background-color: #4b2ab5; /* dark purple background */
+ border-radius: 9999px;
+ transition: transform 0.25s ease;
+ z-index: 1;
+ }
+ /* When "True" is checked */
+ #true:checked ~ label[for="true"] {
+ color: #ffd6ff; /* light pink text */
+ }
+ /* When "False" is checked */
+ #false:checked ~ label[for="false"] {
+ color: #ffd6ff; /* light pink text */
+ }
+ /* Move highlight to right side when False is checked */
+ #false:checked ~ .toggle-highlight {
+ transform: translateX(100%);
+ }
+
+ /* Center items inside that row */
+ #mode-row{
+ justify-content: center !important;
+ align-items: center !important;
+ }
+
+ /* Center the mode row contents */
+ #mode-row {
+ display: flex !important;
+ justify-content: center !important;
+ align-items: center !important;
+ width: 100% !important;
+ }
+
+ /* Stop Gradio from making children stretch */
+ #mode-row > * {
+ flex: 0 0 auto !important;
+ width: auto !important;
+ min-width: 0 !important;
+ }
+
+ /* Specifically ensure the HTML component wrapper doesn't take full width */
+ #mode-row .gr-html,
+ #mode-row .gradio-html,
+ #mode-row .prose,
+ #mode-row .block {
+ width: auto !important;
+ flex: 0 0 auto !important;
+ display: inline-block !important;
+ }
+
+ /* Center the pill itself */
+ #radioanimated_mode {
+ display: inline-flex !important;
+ justify-content: center !important;
+ width: auto !important;
+ }
+
+ """
+
+css += """
+ .cd-trigger-icon{
+ color: rgba(255,255,255,0.9);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 18px;
+ height: 18px;
+ }
+ .cd-trigger-icon svg {
+ width: 18px;
+ height: 18px;
+ display: block;
+ }
+ """
+
+
+css += """
+ /* ---- radioanimated ---- */
+ .ra-wrap{
+ width: fit-content;
+ }
+ .ra-inner{
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ gap: 0;
+ padding: 6px;
+ background: #0b0b0b;
+ border-radius: 9999px;
+ overflow: hidden;
+ user-select: none;
+ }
+ .ra-input{
+ display: none;
+ }
+ .ra-label{
+ position: relative;
+ z-index: 2;
+ padding: 10px 18px;
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
+ font-size: 14px;
+ font-weight: 600;
+ color: rgba(255,255,255,0.7);
+ cursor: pointer;
+ transition: color 180ms ease;
+ white-space: nowrap;
+ }
+ .ra-highlight{
+ position: absolute;
+ z-index: 1;
+ top: 6px;
+ left: 6px;
+ height: calc(100% - 12px);
+ border-radius: 9999px;
+ background: #8bff97; /* green knob */
+ transition: transform 200ms ease, width 200ms ease;
+ }
+ /* selected label becomes darker like your screenshot */
+ .ra-input:checked + .ra-label{
+ color: rgba(0,0,0,0.75);
+ }
+ """
+
+css += """
+.cd-icn svg{
+ width: 18px;
+ height: 18px;
+ display: block;
+}
+.cd-icn svg *{
+ stroke: rgba(255,255,255,0.9);
+}
+"""
+
+
+css += """
+ /* --- prompt box --- */
+ .ds-prompt{
+ width: 100%;
+ max-width: 720px;
+ margin-top: 3px;
+ }
+
+ .ds-textarea{
+ width: 100%;
+ box-sizing: border-box;
+ background: #2b2b2b;
+ color: rgba(255,255,255,0.9);
+ border: 1px solid rgba(255,255,255,0.12);
+ border-radius: 14px;
+ padding: 14px 16px;
+ outline: none;
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
+ font-size: 15px;
+ line-height: 1.35;
+ resize: none;
+ min-height: 210px;
+ max-height: 210px;
+ overflow-y: auto;
+
+ /* IMPORTANT: space for the footer controls */
+ padding-bottom: 72px;
+ }
+
+
+ .ds-card{
+ width: 100%;
+ max-width: 720px;
+ margin: 0 auto;
+ }
+ .ds-top{
+ position: relative;
+ }
+
+ /* Make room for footer inside textarea */
+ .ds-textarea{
+ padding-bottom: 72px;
+ }
+
+ /* Footer positioning */
+ .ds-footer{
+ position: absolute;
+ right: 12px;
+ bottom: 10px;
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ justify-content: flex-end;
+ z-index: 3;
+ }
+
+ /* Smaller pill buttons inside footer */
+ .ds-footer .cd-trigger{
+ min-height: 32px;
+ padding: 6px 10px;
+ font-size: 12px;
+ gap: 6px;
+ border-radius: 9999px;
+ }
+ .ds-footer .cd-trigger-icon,
+ .ds-footer .cd-icn{
+ width: 14px;
+ height: 14px;
+ }
+ .ds-footer .cd-trigger-icon svg,
+ .ds-footer .cd-icn svg{
+ width: 14px;
+ height: 14px;
+ }
+ .ds-footer .cd-caret{
+ font-size: 11px;
+ }
+
+ /* Bottom safe area bar (optional but looks nicer) */
+ .ds-top::after{
+ content: "";
+ position: absolute;
+ left: 1px;
+ right: 1px;
+ bottom: 1px;
+ height: 56px;
+ background: #2b2b2b;
+ border-bottom-left-radius: 13px;
+ border-bottom-right-radius: 13px;
+ pointer-events: none;
+ z-index: 2;
+ }
+
+ """
+
+css += """
+ /* ---- camera dropdown ---- */
+
+ /* 1) Fix overlap: make the Gradio HTML block shrink-to-fit when it contains a CameraDropdown.
+ Gradio uses .gr-html for HTML components in most versions; older themes sometimes use .gradio-html.
+ This keeps your big header HTML unaffected because it doesn't contain .cd-wrap.
+ */
+
+ /* 2) Actual dropdown layout */
+ .cd-wrap{
+ position: relative;
+ display: inline-block;
+ }
+
+ /* 3) Match RadioAnimated pill size/feel */
+ .cd-trigger{
+ margin-top: 2px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+
+ border: none;
+
+ box-sizing: border-box;
+ padding: 10px 18px;
+ min-height: 52px;
+ line-height: 1.2;
+
+ border-radius: 9999px;
+ background: #0b0b0b;
+
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
+ font-size: 14px;
+
+ /* ✅ match .ra-label exactly */
+ color: rgba(255,255,255,0.7) !important;
+ font-weight: 600 !important;
+
+ cursor: pointer;
+ user-select: none;
+ white-space: nowrap;
+ }
+
+ /* Ensure inner spans match too */
+ .cd-trigger .cd-trigger-text,
+ .cd-trigger .cd-caret{
+ color: rgba(255,255,255,0.7) !important;
+ }
+
+ /* keep caret styling */
+ .cd-caret{
+ opacity: 0.8;
+ font-weight: 900;
+ }
+
+ /* 4) Ensure menu overlays neighbors and isn't clipped */
+ /* Move dropdown a tiny bit up (closer to the trigger) */
+ .cd-menu{
+ position: absolute;
+ top: calc(100% + 4px); /* was +10px */
+ left: 0;
+
+ min-width: 240px;
+ background: #2b2b2b;
+ border: 1px solid rgba(255,255,255,0.14);
+ border-radius: 14px;
+ box-shadow: 0 18px 40px rgba(0,0,0,0.35);
+ padding: 10px;
+
+ opacity: 0;
+ transform: translateY(-6px);
+ pointer-events: none;
+ transition: opacity 160ms ease, transform 160ms ease;
+
+ z-index: 9999;
+ }
+
+ .cd-title{
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+
+ color: rgba(255,255,255,0.45); /* 👈 muted grey */
+ margin-bottom: 6px;
+ padding: 0 6px;
+ pointer-events: none; /* title is non-interactive */
+ }
+
+
+ .cd-menu.open{
+ opacity: 1;
+ transform: translateY(0);
+ pointer-events: auto;
+ }
+
+ .cd-items{
+ display: flex;
+ flex-direction: column;
+ gap: 0px; /* tighter, more like a native menu */
+ }
+
+ /* Items: NO "boxed" buttons by default */
+ .cd-item{
+ width: 100%;
+ text-align: left;
+ border: none;
+ background: transparent; /* ✅ removes box look */
+ color: rgba(255,255,255,0.92);
+ padding: 8px 34px 8px 12px; /* right padding leaves room for tick */
+ border-radius: 10px; /* only matters on hover */
+ cursor: pointer;
+
+ font-size: 14px;
+ font-weight: 700;
+
+ position: relative;
+ transition: background 120ms ease;
+ }
+
+ /* “Box effect” only on hover (not always) */
+ .cd-item:hover{
+ background: rgba(255,255,255,0.08);
+ }
+
+ /* Tick on the right ONLY on hover */
+ .cd-item::after{
+ content: "✓";
+ position: absolute;
+ right: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ opacity: 0; /* hidden by default */
+ transition: opacity 120ms ease;
+ color: rgba(255,255,255,0.9);
+ font-weight: 900;
+ }
+
+ /* show tick ONLY for selected item */
+ .cd-item[data-selected="true"]::after{
+ opacity: 1;
+ }
+
+ /* keep hover box effect, but no tick change */
+ .cd-item:hover{
+ background: rgba(255,255,255,0.08);
+ }
+
+
+ /* Kill any old “selected” styling just in case */
+ .cd-item.selected{
+ background: transparent !important;
+ border: none !important;
+ }
+
+
+ """
+
+css += """
+/* icons in dropdown items */
+.cd-item{
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+.cd-icn{
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 18px;
+ height: 18px;
+ flex: 0 0 18px;
+}
+.cd-label{
+ flex: 1;
+}
+
+/* =========================
+ FIX: prompt border + scrollbar bleed
+ ========================= */
+
+/* Put the border + background on the wrapper, not the textarea */
+.ds-top{
+ position: relative;
+ background: #2b2b2b;
+ border: 1px solid rgba(255,255,255,0.12);
+ border-radius: 14px;
+ overflow: hidden; /* ensures the footer bar is clipped to rounded corners */
+}
+
+/* Make textarea "transparent" so wrapper owns the border/background */
+.ds-textarea{
+ background: transparent !important;
+ border: none !important;
+ border-radius: 0 !important; /* wrapper handles radius */
+ outline: none;
+
+ /* keep your spacing */
+ padding: 14px 16px;
+ padding-bottom: 72px; /* room for footer */
+ width: 100%;
+ box-sizing: border-box;
+
+ /* keep scroll behavior */
+ overflow-y: auto;
+
+ /* prevent scrollbar bleed by hiding native scrollbar */
+ scrollbar-width: none; /* Firefox */
+}
+.ds-textarea::-webkit-scrollbar{ /* Chrome/Safari */
+ width: 0;
+ height: 0;
+}
+
+/* Safe-area bar: now it matches perfectly because it's inside the same bordered wrapper */
+.ds-top::after{
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: 56px;
+ background: #2b2b2b;
+ pointer-events: none;
+ z-index: 2;
+}
+
+/* Footer above the bar */
+.ds-footer{
+ position: absolute;
+ right: 12px;
+ bottom: 10px;
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ justify-content: flex-end;
+ z-index: 3;
+}
+
+/* Ensure textarea content sits below overlays */
+.ds-textarea{
+ position: relative;
+ z-index: 1;
+}
+
+/* ===== FIX dropdown menu being clipped/behind ===== */
+
+/* Let the dropdown menu escape the prompt wrapper */
+.ds-top{
+ overflow: visible !important; /* IMPORTANT: do not clip the menu */
+}
+
+/* Keep the rounded "safe area" look without clipping the menu */
+.ds-top::after{
+ left: 0 !important;
+ right: 0 !important;
+ bottom: 0 !important;
+ border-bottom-left-radius: 14px !important;
+ border-bottom-right-radius: 14px !important;
+}
+
+/* Ensure the footer stays above the safe-area bar */
+.ds-footer{
+ z-index: 20 !important;
+}
+
+/* Make sure the opened menu is above EVERYTHING */
+.ds-footer .cd-menu{
+ z-index: 999999 !important;
+}
+
+/* Sometimes Gradio/columns/cards create stacking contexts;
+ force the whole prompt card above nearby panels */
+.ds-card{
+ position: relative;
+ z-index: 50;
+}
+
+/* --- Fix focus highlight shape (make it match rounded container) --- */
+
+/* Kill any theme focus ring on the textarea itself */
+.ds-textarea:focus,
+.ds-textarea:focus-visible{
+ outline: none !important;
+ box-shadow: none !important;
+}
+
+/* Optional: if some themes apply it even when not focused */
+.ds-textarea{
+ outline: none !important;
+}
+
+/* Apply the focus ring to the rounded wrapper instead */
+.ds-top:focus-within{
+ border-color: rgba(255,255,255,0.22) !important;
+ box-shadow: 0 0 0 3px rgba(255,255,255,0.06) !important;
+ border-radius: 14px !important;
+}
+
+/* If you see any tiny square corners, ensure the wrapper clips its own shadow properly */
+.ds-top{
+ border-radius: 14px !important;
+}
+
+/* =========================
+ CameraDropdown: force readable menu text in BOTH themes
+ ========================= */
+
+/* Menu surface */
+.cd-menu{
+ background: #2b2b2b !important;
+ border: 1px solid rgba(255,255,255,0.14) !important;
+}
+
+/* Title */
+.cd-title{
+ color: rgba(255,255,255,0.55) !important;
+}
+
+/* Items + all descendants (fixes spans / inherited theme colors) */
+.cd-item,
+.cd-item *{
+ color: rgba(255,255,255,0.92) !important;
+}
+
+/* Hover state */
+.cd-item:hover{
+ background: rgba(255,255,255,0.10) !important;
+}
+
+/* Checkmark */
+.cd-item::after{
+ color: rgba(255,255,255,0.92) !important;
+}
+
+/* (Optional) make sure the trigger stays readable too */
+.cd-trigger,
+.cd-trigger *{
+ color: rgba(255,255,255,0.75) !important;
+}
+
+/* ---- preset gallery ---- */
+.pg-wrap{
+ width: 100%;
+ max-width: 1100px;
+ margin: 18px auto 0 auto;
+}
+.pg-title{
+ text-align: center;
+ margin-bottom: 14px;
+}
+.pg-h1{
+ font-size: 34px;
+ font-weight: 800;
+ line-height: 1.1;
+
+ /* ✅ theme-aware */
+ color: var(--body-text-color);
+}
+.pg-h2{
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--body-text-color-subdued);
+ margin-top: 6px;
+}
+
+.pg-grid{
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr)); /* 3 per row */
+ gap: 18px;
+}
+
+.pg-card{
+ border: none;
+ background: transparent;
+ padding: 0;
+ cursor: pointer;
+ border-radius: 12px;
+ overflow: hidden;
+ position: relative;
+ transform: translateZ(0);
+}
+
+.pg-img{
+ width: 100%;
+ height: 220px; /* adjust to match your look */
+ object-fit: cover;
+ display: block;
+ border-radius: 12px;
+ transition: transform 160ms ease, filter 160ms ease, opacity 160ms ease;
+}
+
+/* hover: slight zoom on hovered card */
+.pg-card:hover .pg-img{
+ transform: scale(1.02);
+}
+
+/* dim others while hovering */
+.pg-card[data-dim="true"] .pg-img{
+ opacity: 0.35;
+ filter: saturate(0.9);
+}
+
+/* keep hovered/active crisp */
+.pg-card[data-active="true"] .pg-img{
+ opacity: 1.0;
+ filter: none;
+}
+
+
+"""
+
+
+css += """
+/* ---- AudioDropUpload ---- */
+.aud-wrap{
+ width: 100%;
+ max-width: 720px;
+}
+.aud-drop{
+ border: 2px dashed var(--body-text-color-subdued);
+ border-radius: 16px;
+ padding: 18px;
+ text-align: center;
+ cursor: pointer;
+ user-select: none;
+ color: var(--body-text-color);
+ background: var(--block-background-fill);
+}
+.aud-drop.dragover{
+ border-color: rgba(255,255,255,0.35);
+ background: rgba(255,255,255,0.06);
+}
+.aud-hint{
+ color: var(--body-text-color-subdued);
+ font-size: 0.95rem;
+ margin-top: 6px;
+}
+/* pill row like your other controls */
+.aud-row{
+ display: none;
+ align-items: center;
+ gap: 10px;
+ background: #0b0b0b;
+ border-radius: 9999px;
+ padding: 8px 10px;
+}
+.aud-player{
+ flex: 1;
+ width: 100%;
+ height: 34px;
+ border-radius: 9999px;
+}
+.aud-remove{
+ appearance: none;
+ border: none;
+ background: transparent;
+ color: rgba(255,255,255);
+ cursor: pointer;
+ width: 36px;
+ height: 36px;
+ border-radius: 9999px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ transition: background 120ms ease, color 120ms ease, opacity 120ms ease;
+ opacity: 0.9;
+ flex: 0 0 auto;
+}
+.aud-remove:hover{
+ background: rgba(255,255,255,0.08);
+ color: rgb(255,255,255);
+ opacity: 1;
+}
+.aud-filelabel{
+ margin: 10px 6px 0;
+ color: var(--body-text-color-subdued);
+ font-size: 0.95rem;
+ display: none;
+}
+#audio_input_hidden { display: none !important; }
+
+footer {
+ visibility: hidden;
+}
+"""
+
+
+def apply_example(idx: str):
+ idx = int(idx)
+
+ # Read the example row from your list
+ img, prompt_txt, cam, res, mode, vid, aud, end_img = examples_list[idx]
+
+ img_path = img if img else None
+ vid_path = vid if vid else None
+ aud_path = aud if aud else None
+
+ input_image_update = img_path
+ prompt_update = prompt_txt
+ camera_update = cam
+ resolution_update = res
+ mode_update = mode
+ video_update = gr.update(value=vid_path, visible=(mode == "Rotoscope"))
+ audio_update = aud_path
+ end_image = end_img
+
+ return (
+ input_image_update,
+ prompt_update,
+ camera_update,
+ resolution_update,
+ mode_update,
+ video_update,
+ audio_update,
+ audio_update,
+ end_image,
+ )
+
+
+
+with gr.Blocks(title="Generative Video") as demo:
+
+ gr.HTML(
+ """
+
+
+ 🎥 Generative Video
+
+
+ """
+ )
+
+ with gr.Column(elem_id="col-container"):
+ with gr.Row(elem_id="mode-row"):
+ radioanimated_mode = RadioAnimated(
+ choices=["Guided", "Inbetween", "Rotoscope"],
+ value="Guided",
+ elem_id="radioanimated_mode"
+ )
+ with gr.Row():
+ with gr.Column(elem_id="step-column"):
+
+ with gr.Row():
+
+ first_frame = gr.Image(
+ label="First Frame (Optional)",
+ type="filepath",
+ height=256
+ )
+
+ end_frame = gr.Image(
+ label="Last Frame (Optional)",
+ type="filepath",
+ height=256,
+ visible=False,
+ )
+
+ input_video = gr.Video(
+ label="Motion Reference Video",
+ height=256,
+ visible=False,
+ )
+
+ relocate = gr.HTML(
+ value="",
+ html_template="",
+ js_on_load=r"""
+ (() => {
+ function moveIntoFooter() {
+ const promptRoot = document.querySelector("#prompt_ui");
+ if (!promptRoot) return false;
+
+ const footer = promptRoot.querySelector(".ds-footer");
+ if (!footer) return false;
+
+ const dur = document.querySelector("#duration_ui .cd-wrap");
+ const res = document.querySelector("#resolution_ui .cd-wrap");
+ const cam = document.querySelector("#camera_ui .cd-wrap");
+
+ if (!dur || !res || !cam) return false;
+
+ footer.appendChild(dur);
+ footer.appendChild(res);
+ footer.appendChild(cam);
+
+ return true;
+ }
+
+ const tick = () => {
+ if (!moveIntoFooter()) requestAnimationFrame(tick);
+ };
+ requestAnimationFrame(tick);
+ })();
+ """
+ )
+
+
+ # Hidden real audio input (backend value)
+ audio_input = gr.File(
+ label="Audio (Optional)",
+ file_types=["audio"],
+ type="filepath",
+ elem_id="audio_input_hidden",
+ )
+
+ # Custom UI that feeds the hidden gr.Audio above
+ audio_ui = AudioDropUpload(
+ target_audio_elem_id="audio_input_hidden",
+ elem_id="audio_ui",
+ )
+
+ prompt_ui = PromptBox(
+ value="Make this image come alive with cinematic motion, smooth animation",
+ elem_id="prompt_ui",
+ )
+
+ prompt = gr.Textbox(
+ label="Prompt",
+ value="Make this image come alive with cinematic motion, smooth animation",
+ lines=3,
+ max_lines=3,
+ placeholder="Describe the motion and animation you want...",
+ visible=False
+ )
+
+ enhance_prompt = gr.Checkbox(
+ label="Enhance Prompt",
+ value=False,
+ visible=True
+ )
+
+ with gr.Accordion("Advanced Settings", open=False, visible=False):
+ seed = gr.Slider(
+ label="Seed",
+ minimum=0,
+ maximum=MAX_SEED,
+ value=DEFAULT_SEED,
+ step=1
+ )
+
+ randomize_seed = gr.Checkbox(label="Randomize Seed", value=False)
+
+
+ with gr.Column(elem_id="step-column"):
+ output_video = gr.Video(label="Generated Video", autoplay=True, height=512)
+
+ with gr.Row(elem_id="controls-row"):
+
+ duration_ui = CameraDropdown(
+ choices=["3s", "5s", "10s", "15s"],
+ value="5s",
+ title="Clip Duration",
+ elem_id="duration_ui"
+ )
+
+ duration = gr.Slider(
+ label="Duration (seconds)",
+ minimum=1.0,
+ maximum=15.0,
+ value=5.0,
+ step=0.1,
+ visible=False
+ )
+
+ ICON_16_9 = """"""
+
+ ICON_4_3 = """"""
+
+ ICON_1_1 = """"""
+
+ ICON_9_16 = """"""
+
+
+ resolution_ui = CameraDropdown(
+ choices=[
+ {"label": "16:9", "value": "16:9", "icon": ICON_16_9},
+ {"label": "4:3", "value": "4:3", "icon": ICON_4_3},
+ {"label": "1:1", "value": "1:1", "icon": ICON_1_1},
+ {"label": "9:16", "value": "9:16", "icon": ICON_9_16},
+ ],
+ value="16:9",
+ title="Resolution",
+ elem_id="resolution_ui"
+ )
+
+
+ width = gr.Number(label="Width", value=DEFAULT_1_STAGE_WIDTH, precision=0, visible=False)
+ height = gr.Number(label="Height", value=DEFAULT_1_STAGE_HEIGHT, precision=0, visible=False)
+
+ camera_ui = CameraDropdown(
+ choices=[name for name, _ in VISIBLE_RUNTIME_LORA_CHOICES],
+ value="No LoRA",
+ title="Camera LoRA",
+ elem_id="camera_ui",
+ )
+
+ # Hidden real dropdown (backend value)
+ camera_lora = gr.Dropdown(
+ label="Camera Control LoRA",
+ choices=[name for name, _ in VISIBLE_RUNTIME_LORA_CHOICES],
+ value="No LoRA",
+ visible=False
+ )
+
+ generate_btn = gr.Button("Generate Video", variant="primary", elem_classes="button-gradient")
+
+
+ camera_ui.change(
+ fn=lambda x: x,
+ inputs=camera_ui,
+ outputs=camera_lora,
+ api_visibility="private"
+ )
+
+ radioanimated_mode.change(
+ fn=on_mode_change,
+ inputs=radioanimated_mode,
+ outputs=[input_video, end_frame],
+ api_visibility="private",
+ )
+
+
+ duration_ui.change(
+ fn=apply_duration,
+ inputs=duration_ui,
+ outputs=[duration],
+ api_visibility="private"
+ )
+ resolution_ui.change(
+ fn=apply_resolution,
+ inputs=resolution_ui,
+ outputs=[width, height],
+ api_visibility="private"
+ )
+ prompt_ui.change(
+ fn=lambda x: x,
+ inputs=prompt_ui,
+ outputs=prompt,
+ api_visibility="private"
+ )
+
+
+ generate_btn.click(
+ fn=generate_video,
+ inputs=[
+ first_frame,
+ end_frame,
+ prompt,
+ duration,
+ input_video,
+ radioanimated_mode,
+ enhance_prompt,
+ seed,
+ randomize_seed,
+ height,
+ width,
+ camera_lora,
+ audio_input
+ ],
+ outputs=[output_video]
+ )
+
+ examples_list = [
+ [
+ "examples/frank.png",
+ "Use lip-sync with the Style Reference. the background stay solid Make this image come alive with cinematic motion, smooth animation. Be alive in the real world. Look into the camera. Frank face with a mischievous smirk, glancing at the camera with faux innocence Says:‘We got to get out of this place...‘ Frank walking slowly and deliberately toward the lens, the magic now behind it Full lip-sync natural like time-lapse with subtle drifting magic and soft ambient motion in the background, screens play, maintaining a calm, dreamlike atmosphere while they bloom as the camera pans down and pushes forward, '—if it's'. The being stops, stares directly into the camera with an unapologetic, stone-cold expression, and lets out a single dismissive 'the last thing we ever do.' Use End frame as style reference holds on the grumpy Frank face, flames reflecting in its eyes, pauses.",
+ "Static",
+ "16:9",
+ "Guided",
+ None,
+ None,
+ None,
+ ],
+ [
+ "examples/longlong.png",
+ "Smiles. Use lip-sync with the Style Reference. Make this image come alive with cinematic motion, smooth animation. Be alive in the real world. Look into the camera. Says:Once upon a time. A long, long time ago... In a land far, far away. Oh, My. And it begins a little something like this. Smiles.",
+ "Static",
+ "16:9",
+ "Guided",
+ None,
+ None,
+ None,
+ ],
+ [
+ "examples/oversg.jpg",
+ "Use lip-sync with the Style Reference. Make this image come alive with cinematic motion, smooth animation. Be alive in the real world. Look into the camera. She Says: Hey, Wow, check this out my T.V. is going crazy. She looks at the T.V. screens broadcasting strange programs. Camera pulls back to show scene she says:WOW, just wow.",
+ "Static",
+ "4:3",
+ "Guided",
+ None,
+ None,
+ None,
+ ],
+ [
+ "examples/0.png",
+ "Lip-sync be alive, Minimalist toned scene in a soft classic Horror film style. A vintage hanging studio microphone, looking up sing. Calm, intimate atmosphere, simple gradient background, gentle lighting, smooth 4K.",
+ "Static",
+ "16:9",
+ "Guided",
+ None,
+ None,
+ None,
+ ],
+ [
+ "examples/1.png",
+ "Use lip-sync with the Style Reference. Make this image come alive with cinematic motion, smooth animation. Be alive in the real world. Look into the camera. Says: Hello, Welcome to Agent 5, I’m ready to help you create something wonderful.",
+ "Static",
+ "1:1",
+ "Guided",
+ None,
+ None,
+ None,
+ ],
+ [
+ "examples/2.png",
+ "You are a top professional animator, Use First frame as style reference, is a close-up of the face with a mischievous smirk, glancing at the camera and says with faux innocence, ‘Some say the world is insane—‘ A surreal movement and natural like time-lapse with subtle drifting magic and soft ambient motion in the background, maintaining a calm, dreamlike atmosphere while they bloom as the camera pans down and pushes forward, '—talk to him.'. The camera reveals a grumpy-face walking slowly and deliberately toward the lens, the magic now behind it. The being stops, stares directly into the camera with an unapologetic, stone-cold expression, and lets out a single dismissive 'meow.' Use End frame as style reference holds on the grumpy face, flames reflecting in its eyes.",
+ "No LoRA",
+ "1:1",
+ "Inbetween",
+ None,
+ None,
+ "examples/frame2.png",
+ ],
+ [
+ "examples/3.png",
+ "Use the Style Reference for A puppet character, a puppet stands inside an icy cave made of frozen walls and icicles, looks panicked and frantic, rapidly turning head left and right and scanning the room while waving arms and shouting angrily and desperately, mouthing the words “where the hell is my dog,” puppets movements exaggerated and puppet-like with high energy and urgency, suddenly a second puppet dog bursts into frame from the side, jumping up excitedly and tackling affectionately while licking face repeatedly, they freezes in surprise and then breaks into relief and laughter as the dog continues licking, the scene feels chaotic, comedic, and emotional with expressive puppet reactions, cinematic lighting, smooth camera motion, shallow depth of field, and high-quality puppet-style animation",
+ "No LoRA",
+ "1:1",
+ "Guided",
+ None,
+ None,
+ None,
+ ],
+ [
+ "examples/4.png",
+ "a character doing a tiktok dance by moving their heads side to side with dramatic lighting and cinematic effects and singing",
+ "No LoRA",
+ "9:16",
+ "Rotoscope",
+ "examples/tiktok.mp4",
+ None,
+ None,
+ ],
+ [
+ "examples/5.png",
+ "a character doing a tiktok dance by moving their heads side to side with dramatic lighting and cinematic effects and singing",
+ "No LoRA",
+ "9:16",
+ "Rotoscope",
+ "examples/tiktok.mp4",
+ None,
+ None,
+ ],
+ [
+ "examples/6.png",
+ "Using the Style Reference, Realistic POV selfie-style video in a snowy, foggy field. Two shaggy Highland cows with long curved horns stand ahead. The camera is handheld and slightly shaky. The woman filming talks nervously and excitedly in a vlog tone: \"Oh my god guys… look how big those horns are… I’m kinda scared.\" The cow on the left walks toward the camera in a cute, bouncy, hopping way, curious and gentle. Snow crunches under its hooves, breath visible in the cold air. The horns look massive from the POV. As the cow gets very close, its wet nose with slight dripping fills part of the frame. She laughs nervously but reaches out and pets the cow. The cow makes deep, soft, interesting mooing and snorting sounds, calm and friendly. Ultra-realistic, natural lighting, immersive audio, documentary-style realism, cow hits camera.",
+ "No LoRA",
+ "16:9",
+ "Generative",
+ None,
+ None,
+ None,
+ ],
+ [
+ "examples/7.png",
+ "A cinematic dolly out of person frozen mid-dance on a dark, blue-lit ballroom floor as students move indistinctly behind, their footsteps and muffled music reduced to a distant, underwater thrum; the audio foregrounds subjects steady breathing and the faint rustle of fabric slowly raises one arm, never breaking eye contact with the camera, then after a deliberately long silence she speaks in a flat, dry, perfectly controlled voice, “I don’t dance… I am. There for I am,” each word crisp and unemotional, followed by an abrupt cutoff of her voice as the background sound swells slightly, reinforcing the deadpan humor, with precise lip sync, minimal facial movement, stark gothic lighting, and cinematic realism.",
+ "Zoom Out",
+ "16:9",
+ "Generative",
+ None,
+ None,
+ None,
+ ],
+ [
+ "examples/onceupon.png",
+ "You are a top professional animator, Use First frame as style reference, A fragile egg the shell cracking and peeling apart in gentle low-gravity motion. Fine lunar dust lifts and drifts outward with each movement, floating in slow arcs before settling back onto the ground, weightless motion, small fragments of the egg tumbling and spinning through the air. In the background, the deep darkness of space subtly shifts as stars glide with the camera's movement, emphasizing vast depth and scale. The camera performs a smooth, cinematic slow push-in, with natural parallax between the foreground dust, the Egg, and the distant starfield. Ultra-realistic detail, physically accurate low-gravity motion, cinematic lighting, and a breath-music, movie-like shot.",
+ "Static",
+ "1:1",
+ "Generative",
+ None,
+ None,
+ None,
+ ],
+ [
+ "examples/inaland.png",
+ "Use the First Frame as Style Reference. Cinematic action packed shot. the man says silently: We need to run. the camera zooms in on his mouth then immediately screams: NOW! the camera zooms back out, he turns around on fire, and starts running away smoke following, the camera tracks his run in hand held style. the camera cranes up and show him run into the distance down the street at a busy New York night.",
+ "No LoRA",
+ "9:16",
+ "Guided",
+ None,
+ None,
+ None,
+ ]
+ ]
+
+ examples_obj = create_examples(
+ examples=examples_list,
+ fn=generate_video_example,
+ inputs=[first_frame, prompt_ui, camera_ui, resolution_ui, radioanimated_mode, input_video, audio_input, end_frame],
+ outputs = [output_video],
+ label="Templates",
+ cache_examples=True,
+ visible=False
+ )
+
+ preset_gallery = PresetGallery(
+ items=[
+ {"thumb": "examples/frank.png", "label": "Example 0", "title": "Image + Image to Video" },
+ {"thumb": "examples/longlong.png", "label": "Example 1", "title": "Image + Image to Video" },
+ {"thumb": "examples/oversg.jpg", "label": "Example 2", "title": "Image to Video" },
+ {"thumb": "examples/0.png", "label": "Example 3", "title": "Image + Audio to Video" },
+ {"thumb": "examples/1.png", "label": "Example 4", "title": "Image + Audio to Video" },
+ {"thumb": "examples/2.png", "label": "Example 5", "title": "First and Last Frame" },
+ {"thumb": "examples/3.png", "label": "Example 6", "title": "Image to Video" },
+ {"thumb": "examples/4.png", "label": "Example 7", "title": "Pose to Video" },
+ {"thumb": "examples/5.png", "label": "Example 8", "title": "Pose to Video" },
+ {"thumb": "examples/6.png", "label": "Example 9", "title": "Image to Video" },
+ {"thumb": "examples/7.png", "label": "Example 10", "title": "Image to Video" },
+ {"thumb": "examples/onceupon.png", "label": "Example 11", "title": "Image to Video" },
+ {"thumb": "examples/chuck.jpg", "label": "Example 12", "title": "Image to Video" },
+ {"thumb": "examples/inaland.png", "label": "Example 13", "title": "Image to Video" },
+ {"thumb": "examples/spaceg.jpg", "label": "Example 14", "title": "Image to Video" },
+
+ ],
+ title="Click on Our Examples",
+ )
+
+ def on_audio_ui_change(v):
+ # Our JS sends "__CLEAR__" when the user presses the X
+ if v == "__CLEAR__" or v is None or v == "":
+ return None
+ # For normal events (uploads), do nothing (keep whatever gr.File already has)
+ return gr.update()
+
+ audio_ui.change(
+ fn=on_audio_ui_change,
+ inputs=audio_ui,
+ outputs=audio_input,
+ api_visibility="private",
+ )
+
+
+ def run_cached_example_by_index(idx):
+ idx = int(idx)
+ cached_outputs = examples_obj.load_from_cache(idx)
+ return cached_outputs[0] if len(cached_outputs) == 1 else cached_outputs
+
+
+ preset_gallery.change(
+ fn=apply_example,
+ inputs=preset_gallery,
+ outputs=[
+ first_frame,
+ prompt_ui,
+ camera_ui,
+ resolution_ui,
+ radioanimated_mode,
+ input_video,
+ audio_input,
+ audio_ui,
+ end_frame,
+ ],
+ api_visibility="private",
+ ).then(
+ fn=run_cached_example_by_index,
+ inputs=preset_gallery,
+ outputs=[output_video],
+ postprocess=False,
+ api_visibility="private",
+ )
+
+
+
+if __name__ == "__main__":
+ demo.launch(ssr_mode=False, mcp_server=False, css=css, allowed_paths=["./examples"])
\ No newline at end of file