Spaces:
Running on Zero
Running on Zero
Update app.py
#4
by linoyts HF Staff - opened
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 1005 |
-
|
| 1006 |
-
|
| 1007 |
-
|
| 1008 |
-
|
| 1009 |
-
|
| 1010 |
-
|
| 1011 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1012 |
|
| 1013 |
-
|
| 1014 |
-
|
| 1015 |
-
|
|
|
|
|
|
|
|
|
|
| 1016 |
with gr.Row():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1017 |
input_image = gr.Image(
|
| 1018 |
-
label="
|
| 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=
|
| 1047 |
-
placeholder="Describe the
|
| 1048 |
)
|
| 1049 |
|
| 1050 |
with gr.Row():
|
| 1051 |
duration = gr.Slider(
|
| 1052 |
-
label="Duration (
|
| 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=
|
| 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 |
-
|
| 1067 |
-
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 1079 |
-
height = gr.Number(label="Height", value=
|
| 1080 |
|
| 1081 |
-
|
| 1082 |
-
|
|
|
|
| 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=
|
|
|
|
| 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)
|