linoyts HF Staff commited on
Commit
c5b3355
Β·
1 Parent(s): 249b00b

Update app.py (#4)

Browse files

- Update app.py (ca3281de46522307a1d46618e0f4f5a87701660b)
- Update app.py (7a30fa76527e39db0226262a021207b4939d2a42)

Files changed (1) hide show
  1. app.py +78 -59
app.py CHANGED
@@ -837,13 +837,37 @@ def on_image_upload(image, video, high_res):
837
  return gr.update(value=w), gr.update(value=h)
838
 
839
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
840
  def on_video_upload(video, image, high_res):
841
- """Auto-set resolution when video is uploaded."""
842
  media = video if video is not None else image
843
  aspect = detect_aspect_ratio(media)
844
  tier = "high" if high_res else "low"
845
  w, h = RESOLUTIONS[tier][aspect]
846
- return gr.update(value=w), gr.update(value=h)
 
 
 
 
 
 
 
 
847
 
848
 
849
  def on_highres_toggle(image, video, high_res):
@@ -999,87 +1023,82 @@ def generate_video(
999
 
1000
 
1001
  # ─────────────────────────────────────────────────────────────────────────────
1002
- # Gradio UI
1003
  # ─────────────────────────────────────────────────────────────────────────────
1004
- with gr.Blocks(title="LTX-2.3 Unified: V2V + I2V + A2V") as demo:
1005
- gr.Markdown("# LTX-2.3 Unified: Video/Image/Audio β†’ Video")
1006
- gr.Markdown(
1007
- "Unified pipeline for **video-to-video** (IC-LoRA), **image-to-video**, "
1008
- "and **audio-conditioned** generation with LTX-2.3 β€” use any combination of inputs. "
1009
- "[[model]](https://huggingface.co/Lightricks/LTX-2.3) "
1010
- "[[code]](https://github.com/Lightricks/LTX-2)"
1011
- )
 
 
 
 
 
 
1012
 
1013
- with gr.Row():
1014
- with gr.Column():
1015
- # All three inputs visible at once
 
 
 
1016
  with gr.Row():
 
 
 
 
1017
  input_image = gr.Image(
1018
- label="πŸ–ΌοΈ Input Image (I2V β€” first frame)",
1019
  type="filepath",
1020
  )
1021
- with gr.Column():
1022
- input_video = gr.Video(
1023
- label="🎬 Reference Video (V2V)",
1024
- sources=["upload"],
1025
- )
1026
- video_preprocess = gr.Dropdown(
1027
- label="Video Preprocessing",
1028
- choices=[
1029
- "Pose (DWPose)",
1030
- "Canny Edge",
1031
- "Depth (Laplacian)",
1032
- "Raw (no preprocessing)",
1033
- ],
1034
- value="Pose (DWPose)",
1035
- info="Strips appearance from video β†’ style comes from image/prompt instead",
1036
- )
1037
- input_audio = gr.Audio(
1038
- label="πŸ”Š Input Audio (A2V β€” lipsync / BGM)",
1039
- type="filepath",
1040
- )
1041
 
1042
  prompt = gr.Textbox(
1043
  label="Prompt",
1044
- info="Describe the desired output β€” be as detailed as possible",
1045
  value="Make this come alive with cinematic motion, smooth animation",
1046
- lines=3,
1047
- placeholder="Describe the motion, style, and content you want...",
1048
  )
1049
 
1050
  with gr.Row():
1051
  duration = gr.Slider(
1052
- label="Duration (seconds)",
1053
- minimum=1.0, maximum=10.0, value=3.0, step=0.1,
1054
  )
 
 
 
 
 
 
 
1055
  conditioning_strength = gr.Slider(
1056
  label="V2V Conditioning Strength",
1057
- info="How closely to follow the reference video",
1058
- minimum=0.0, maximum=1.0, value=1.0, step=0.05,
1059
  )
1060
-
1061
- with gr.Row():
1062
- enhance_prompt = gr.Checkbox(label="Enhance Prompt", value=True)
1063
- high_res = gr.Checkbox(label="High Resolution", value=True)
1064
  use_video_audio = gr.Checkbox(
1065
- label="Use Audio from Video",
1066
- value=True,
1067
- info="Extract and use the audio track from the reference video",
 
 
 
1068
  )
1069
-
1070
- generate_btn = gr.Button("Generate Video", variant="primary", size="lg")
1071
-
1072
- with gr.Accordion("Advanced Settings", open=False):
1073
  seed = gr.Slider(
1074
  label="Seed", minimum=0, maximum=MAX_SEED, value=42, step=1,
1075
  )
1076
  randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
1077
  with gr.Row():
1078
- width = gr.Number(label="Width", value=1536, precision=0)
1079
- height = gr.Number(label="Height", value=1024, precision=0)
1080
 
1081
- with gr.Column():
1082
- output_video = gr.Video(label="Generated Video", autoplay=True)
 
1083
 
1084
  # ── Event handlers ───────────────────────────────────────────────────
1085
  input_image.change(
@@ -1090,7 +1109,7 @@ with gr.Blocks(title="LTX-2.3 Unified: V2V + I2V + A2V") as demo:
1090
  input_video.change(
1091
  fn=on_video_upload,
1092
  inputs=[input_video, input_image, high_res],
1093
- outputs=[width, height],
1094
  )
1095
  high_res.change(
1096
  fn=on_highres_toggle,
@@ -1109,4 +1128,4 @@ with gr.Blocks(title="LTX-2.3 Unified: V2V + I2V + A2V") as demo:
1109
 
1110
 
1111
  if __name__ == "__main__":
1112
- demo.launch(theme=gr.themes.Citrus())
 
837
  return gr.update(value=w), gr.update(value=h)
838
 
839
 
840
+ def _get_video_duration(video_path) -> float | None:
841
+ """Get video duration in seconds via ffprobe."""
842
+ if video_path is None:
843
+ return None
844
+ try:
845
+ result = subprocess.run(
846
+ ["ffprobe", "-v", "error", "-select_streams", "v:0",
847
+ "-show_entries", "format=duration", "-of", "default=nw=1:nk=1",
848
+ str(video_path)],
849
+ capture_output=True, text=True,
850
+ )
851
+ return float(result.stdout.strip())
852
+ except Exception:
853
+ return None
854
+
855
+
856
  def on_video_upload(video, image, high_res):
857
+ """Auto-set resolution and duration when video is uploaded."""
858
  media = video if video is not None else image
859
  aspect = detect_aspect_ratio(media)
860
  tier = "high" if high_res else "low"
861
  w, h = RESOLUTIONS[tier][aspect]
862
+
863
+ # Auto-adjust duration to min(video_length, 10)
864
+ vid_dur = _get_video_duration(video)
865
+ if vid_dur is not None:
866
+ dur = round(min(vid_dur, 10.0), 1)
867
+ else:
868
+ dur = 3.0
869
+
870
+ return gr.update(value=w), gr.update(value=h), gr.update(value=dur)
871
 
872
 
873
  def on_highres_toggle(image, video, high_res):
 
1023
 
1024
 
1025
  # ─────────────────────────────────────────────────────────────────────────────
1026
+ # Gradio UI β€” LTX 2.3 Move
1027
  # ─────────────────────────────────────────────────────────────────────────────
1028
+ css = """
1029
+ .main-title { text-align: center; margin-bottom: 0.5em; }
1030
+ .generate-btn { min-height: 52px !important; font-size: 1.1em !important; }
1031
+ footer { display: none !important; }
1032
+ """
1033
+
1034
+ purple_citrus = gr.themes.Citrus(
1035
+ primary_hue=gr.themes.colors.purple,
1036
+ secondary_hue=gr.themes.colors.purple,
1037
+ neutral_hue=gr.themes.colors.gray,
1038
+ )
1039
+
1040
+ with gr.Blocks(title="LTX 2.3 Move") as demo:
1041
+ gr.Markdown("<h1 class='main-title'>LTX 2.3 Move</h1>")
1042
 
1043
+ # Hidden state β€” preprocessing is always Pose
1044
+ video_preprocess = gr.State("Pose (DWPose)")
1045
+
1046
+ with gr.Row(equal_height=True):
1047
+ # ── Left column: inputs ──────────────────────────────────────
1048
+ with gr.Column(scale=1):
1049
  with gr.Row():
1050
+ input_video = gr.Video(
1051
+ label="Motion Source (video)",
1052
+ sources=["upload"],
1053
+ )
1054
  input_image = gr.Image(
1055
+ label="Style Source (image, optional)",
1056
  type="filepath",
1057
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1058
 
1059
  prompt = gr.Textbox(
1060
  label="Prompt",
 
1061
  value="Make this come alive with cinematic motion, smooth animation",
1062
+ lines=2,
1063
+ placeholder="Describe the style, motion, and content you want...",
1064
  )
1065
 
1066
  with gr.Row():
1067
  duration = gr.Slider(
1068
+ label="Duration (s)", minimum=1.0, maximum=10.0, value=3.0, step=0.1,
 
1069
  )
1070
+ enhance_prompt = gr.Checkbox(label="Enhance Prompt", value=True)
1071
+
1072
+ generate_btn = gr.Button(
1073
+ "Generate", variant="primary", size="lg", elem_classes=["generate-btn"],
1074
+ )
1075
+
1076
+ with gr.Accordion("Advanced Settings", open=False):
1077
  conditioning_strength = gr.Slider(
1078
  label="V2V Conditioning Strength",
1079
+ info="How closely to follow the reference video's structure",
1080
+ minimum=0.0, maximum=1.0, value=0.85, step=0.05,
1081
  )
1082
+ high_res = gr.Checkbox(label="High Resolution (2Γ—)", value=False)
 
 
 
1083
  use_video_audio = gr.Checkbox(
1084
+ label="Use Audio from Video", value=True,
1085
+ info="Extract the audio track from the motion source video",
1086
+ )
1087
+ input_audio = gr.Audio(
1088
+ label="Override Audio (optional β€” replaces video audio)",
1089
+ type="filepath",
1090
  )
 
 
 
 
1091
  seed = gr.Slider(
1092
  label="Seed", minimum=0, maximum=MAX_SEED, value=42, step=1,
1093
  )
1094
  randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
1095
  with gr.Row():
1096
+ width = gr.Number(label="Width", value=768, precision=0)
1097
+ height = gr.Number(label="Height", value=512, precision=0)
1098
 
1099
+ # ── Right column: output ─────────────────────────────────────
1100
+ with gr.Column(scale=1):
1101
+ output_video = gr.Video(label="Result", autoplay=True)
1102
 
1103
  # ── Event handlers ───────────────────────────────────────────────────
1104
  input_image.change(
 
1109
  input_video.change(
1110
  fn=on_video_upload,
1111
  inputs=[input_video, input_image, high_res],
1112
+ outputs=[width, height, duration],
1113
  )
1114
  high_res.change(
1115
  fn=on_highres_toggle,
 
1128
 
1129
 
1130
  if __name__ == "__main__":
1131
+ demo.launch(css=css, theme=purple_citrus)