alexnasa commited on
Commit
09eccb3
·
verified ·
1 Parent(s): 1049f7c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +150 -22
app.py CHANGED
@@ -275,6 +275,93 @@ def get_duration(
275
  return 80
276
  else:
277
  return 120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
  @spaces.GPU(duration=get_duration)
280
  def generate_video(
@@ -290,7 +377,6 @@ def generate_video(
290
  ):
291
  """
292
  Generate a short cinematic video from a text prompt and optional input image using the LTX-2 distilled pipeline.
293
-
294
  Args:
295
  input_image: Optional input image for image-to-video. If provided, it is injected at frame 0 to guide motion.
296
  prompt: Text description of the scene, motion, and cinematic style to generate.
@@ -301,12 +387,10 @@ def generate_video(
301
  height: Output video height in pixels.
302
  width: Output video width in pixels.
303
  progress: Gradio progress tracker.
304
-
305
  Returns:
306
  A tuple of:
307
  - output_path: Path to the generated MP4 video file.
308
  - seed: The seed used for generation.
309
-
310
  Notes:
311
  - Uses a fixed frame rate of 24 FPS.
312
  - Prompt embeddings are generated externally to avoid reloading the text encoder.
@@ -484,7 +568,55 @@ css = """
484
  }
485
  """
486
 
487
- with gr.Blocks(css=css, title="LTX-2 Video Distilled 🎥🔈") as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488
  gr.HTML(
489
  """
490
  <div style="text-align: center;">
@@ -511,6 +643,7 @@ with gr.Blocks(css=css, title="LTX-2 Video Distilled 🎥🔈") as demo:
511
  with gr.Column(elem_id="col-container"):
512
  with gr.Row():
513
  with gr.Column(elem_id="step-column"):
 
514
  input_image = gr.Image(
515
  label="Input Image (Optional)",
516
  type="pil",
@@ -547,31 +680,26 @@ with gr.Blocks(css=css, title="LTX-2 Video Distilled 🎥🔈") as demo:
547
 
548
  randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
549
 
550
- with gr.Row():
551
- width = gr.Number(label="Width", value=DEFAULT_1_STAGE_WIDTH, precision=0)
552
- height = gr.Number(label="Height", value=DEFAULT_1_STAGE_HEIGHT, precision=0)
553
-
554
-
555
- with gr.Column(elem_id="step-column"):
556
 
 
 
557
  output_video = gr.Video(label="Generated Video", autoplay=True, height=512)
558
-
559
- resolution = gr.Radio(
560
- show_label=False,
561
  choices=["768x512", "512x512", "512x768"],
562
  value=f"{DEFAULT_1_STAGE_WIDTH}x{DEFAULT_1_STAGE_HEIGHT}",
563
- )
564
- generate_btn = gr.Button("🤩 Generate Video", variant="primary", elem_classes="button-gradient")
565
-
566
-
567
 
 
 
568
 
569
- # When radio changes, update the existing width/height inputs
570
- resolution.change(
 
571
  fn=apply_resolution,
572
- inputs=resolution,
573
  outputs=[width, height],
574
- show_api=False
575
  )
576
 
577
  generate_btn.click(
@@ -619,4 +747,4 @@ with gr.Blocks(css=css, title="LTX-2 Video Distilled 🎥🔈") as demo:
619
 
620
 
621
  if __name__ == "__main__":
622
- demo.launch(ssr_mode=False, mcp_server=True)
 
275
  return 80
276
  else:
277
  return 120
278
+
279
+ class RadioAnimated(gr.HTML):
280
+ """
281
+ Animated segmented radio (like iOS pill selector).
282
+ Outputs: selected option string, e.g. "768x512"
283
+ """
284
+ def __init__(self, choices, value=None, **kwargs):
285
+ if not choices or len(choices) < 2:
286
+ raise ValueError("RadioAnimated requires at least 2 choices.")
287
+ if value is None:
288
+ value = choices[0]
289
+
290
+ # Build labels/inputs HTML
291
+ inputs_html = "\n".join(
292
+ f"""
293
+ <input class="ra-input" type="radio" name="ra" id="ra-{i}" value="{c}">
294
+ <label class="ra-label" for="ra-{i}">{c}</label>
295
+ """
296
+ for i, c in enumerate(choices)
297
+ )
298
+
299
+ html_template = f"""
300
+ <div class="ra-wrap" id="ra-wrap">
301
+ <div class="ra-inner" id="ra-inner">
302
+ <div class="ra-highlight" id="ra-highlight"></div>
303
+ {inputs_html}
304
+ </div>
305
+ </div>
306
+ """
307
+
308
+ js_on_load = r"""
309
+ (() => {
310
+ const wrap = element.querySelector('#ra-wrap');
311
+ const inner = element.querySelector('#ra-inner');
312
+ const highlight = element.querySelector('#ra-highlight');
313
+ const inputs = Array.from(element.querySelectorAll('.ra-input'));
314
+ const labels = Array.from(element.querySelectorAll('.ra-label'));
315
+
316
+ if (!inputs.length) return;
317
+
318
+ const choices = inputs.map(i => i.value);
319
+
320
+ function setHighlightByIndex(idx) {
321
+ const n = choices.length;
322
+ const pct = 100 / n;
323
+ highlight.style.width = `calc(${pct}% - 6px)`;
324
+ highlight.style.transform = `translateX(${idx * 100}%)`;
325
+ }
326
+
327
+ function setCheckedByValue(val, shouldTrigger=false) {
328
+ const idx = Math.max(0, choices.indexOf(val));
329
+ inputs.forEach((inp, i) => { inp.checked = (i === idx); });
330
+ setHighlightByIndex(idx);
331
+
332
+ // Update props + fire change if requested
333
+ props.value = choices[idx];
334
+ if (shouldTrigger) trigger('change', props.value);
335
+ }
336
+
337
+ // Init from props.value
338
+ setCheckedByValue(props.value ?? choices[0], false);
339
+
340
+ // Click handlers
341
+ inputs.forEach((inp) => {
342
+ inp.addEventListener('change', () => {
343
+ setCheckedByValue(inp.value, true);
344
+ });
345
+ });
346
+
347
+ // Watch for python-side value updates
348
+ let last = props.value;
349
+ setInterval(() => {
350
+ if (props.value !== last) {
351
+ last = props.value;
352
+ setCheckedByValue(props.value ?? choices[0], false);
353
+ }
354
+ }, 100);
355
+ })();
356
+ """
357
+
358
+ super().__init__(
359
+ value=value,
360
+ html_template=html_template,
361
+ js_on_load=js_on_load,
362
+ **kwargs
363
+ )
364
+
365
 
366
  @spaces.GPU(duration=get_duration)
367
  def generate_video(
 
377
  ):
378
  """
379
  Generate a short cinematic video from a text prompt and optional input image using the LTX-2 distilled pipeline.
 
380
  Args:
381
  input_image: Optional input image for image-to-video. If provided, it is injected at frame 0 to guide motion.
382
  prompt: Text description of the scene, motion, and cinematic style to generate.
 
387
  height: Output video height in pixels.
388
  width: Output video width in pixels.
389
  progress: Gradio progress tracker.
 
390
  Returns:
391
  A tuple of:
392
  - output_path: Path to the generated MP4 video file.
393
  - seed: The seed used for generation.
 
394
  Notes:
395
  - Uses a fixed frame rate of 24 FPS.
396
  - Prompt embeddings are generated externally to avoid reloading the text encoder.
 
568
  }
569
  """
570
 
571
+ css += """
572
+ /* ---- radioanimated ---- */
573
+ .ra-wrap{
574
+ width: fit-content;
575
+ }
576
+ .ra-inner{
577
+ position: relative;
578
+ display: inline-flex;
579
+ align-items: center;
580
+ gap: 0;
581
+ padding: 6px;
582
+ background: #0b0b0b;
583
+ border-radius: 9999px;
584
+ overflow: hidden;
585
+ user-select: none;
586
+ }
587
+ .ra-input{
588
+ display: none;
589
+ }
590
+ .ra-label{
591
+ position: relative;
592
+ z-index: 2;
593
+ padding: 10px 18px;
594
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
595
+ font-size: 14px;
596
+ font-weight: 600;
597
+ color: rgba(255,255,255,0.7);
598
+ cursor: pointer;
599
+ transition: color 180ms ease;
600
+ white-space: nowrap;
601
+ }
602
+ .ra-highlight{
603
+ position: absolute;
604
+ z-index: 1;
605
+ top: 6px;
606
+ left: 6px;
607
+ height: calc(100% - 12px);
608
+ border-radius: 9999px;
609
+ background: #8bff97; /* green knob */
610
+ transition: transform 200ms ease, width 200ms ease;
611
+ }
612
+ /* selected label becomes darker like your screenshot */
613
+ .ra-input:checked + .ra-label{
614
+ color: rgba(0,0,0,0.75);
615
+ }
616
+ """
617
+
618
+
619
+ with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
620
  gr.HTML(
621
  """
622
  <div style="text-align: center;">
 
643
  with gr.Column(elem_id="col-container"):
644
  with gr.Row():
645
  with gr.Column(elem_id="step-column"):
646
+
647
  input_image = gr.Image(
648
  label="Input Image (Optional)",
649
  type="pil",
 
680
 
681
  randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
682
 
 
 
 
 
 
 
683
 
684
+
685
+ with gr.Column(elem_id="step-column"):
686
  output_video = gr.Video(label="Generated Video", autoplay=True, height=512)
687
+
688
+ radioanimated = RadioAnimated(
 
689
  choices=["768x512", "512x512", "512x768"],
690
  value=f"{DEFAULT_1_STAGE_WIDTH}x{DEFAULT_1_STAGE_HEIGHT}",
691
+ elem_id="radioanimated"
692
+ )
 
 
693
 
694
+ width = gr.Number(label="Width", value=DEFAULT_1_STAGE_WIDTH, precision=0, visible=False)
695
+ height = gr.Number(label="Height", value=DEFAULT_1_STAGE_HEIGHT, precision=0, visible=False)
696
 
697
+ generate_btn = gr.Button("🤩 Generate Video", variant="primary", elem_classes="button-gradient")
698
+
699
+ radioanimated.change(
700
  fn=apply_resolution,
701
+ inputs=radioanimated,
702
  outputs=[width, height],
 
703
  )
704
 
705
  generate_btn.click(
 
747
 
748
 
749
  if __name__ == "__main__":
750
+ demo.launch(ssr_mode=False, mcp_server=True, css=css)