Sergidev commited on
Commit
341dbbe
·
1 Parent(s): 0250a5e
Files changed (3) hide show
  1. README.md +2 -4
  2. app.py +1 -1
  3. demo_app.py +108 -201
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Huanyan Studio
3
  emoji: ✨
4
  colorFrom: blue
5
  colorTo: indigo
@@ -8,7 +8,5 @@ sdk_version: 5.16.0
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
- short_description: Image-to-video, text-to-video, with multiple LORAS to use.
12
  ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Hunyuan Studio
3
  emoji: ✨
4
  colorFrom: blue
5
  colorTo: indigo
 
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
+ short_description: Advanced text-to-video & image-to-video generation with multiple LoRA adapters
12
  ---
 
 
app.py CHANGED
@@ -3,4 +3,4 @@ from utils import install_packages
3
  if __name__ == "__main__":
4
  install_packages()
5
  from demo_app import demo
6
- demo.queue(max_size=15).launch()
 
3
  if __name__ == "__main__":
4
  install_packages()
5
  from demo_app import demo
6
+ demo.queue(max_size=20).launch(server_name="0.0.0.0", server_port=7860)
demo_app.py CHANGED
@@ -45,36 +45,23 @@ pipe.vae.enable_tiling()
45
  pipe.vae.enable_slicing()
46
  pipe.vae.eval()
47
 
48
- # Available LoRAs in the TTV4ME repository
49
- TTV4ME_Loras = {
50
- "Top_Off.safetensors": "Top_Off.safetensors",
51
- "huanyan_helper.safetensors": "huanyan_helper.safetensors",
52
- "huanyan_helper_alpha.safetensors": "huanyan_helper_alpha.safetensors",
53
- "hunyuan-t-solo-v1.0.safetensors": "hunyuan-t-solo-v1.0.safetensors",
54
- "stripe_v2.safetensors": "stripe_v2.safetensors"
55
- }
56
-
57
- # Illustration Lora
58
- ILLUSTRATION_LORA = "sergidev/IllustrationTTV"
59
- ILLUSTRATION_LORA_NAME = "hunyuan_flat_color_v2.safetensors"
60
- ILLUSTRATION_ADAPTER_NAME = "hyvid_lora_adapter"
61
-
62
- # Load default LoRA adapters
63
- pipe.load_lora_weights(
64
- "Sergidev/TTV4ME", # Private repository
65
- weight_name="stripe_v2.safetensors",
66
- adapter_name="hunyuanvideo-lora",
67
- token=os.environ.get("HF_TOKEN") # Access token from Space secrets
68
- )
69
-
70
- pipe.load_lora_weights(
71
- "sergidev/IllustrationTTV",
72
- weight_name="hunyuan_flat_color_v2.safetensors",
73
- adapter_name="hyvid_lora_adapter"
74
- )
75
-
76
- # Set combined adapter weights
77
- pipe.set_adapters(["hunyuanvideo-lora", "hyvid_lora_adapter"], [0.9, 0.8])
78
 
79
  # Memory cleanup
80
  gc.collect()
@@ -83,57 +70,39 @@ torch.cuda.empty_cache()
83
  MAX_SEED = np.iinfo(np.int32).max
84
  MAX_IMAGE_SIZE = 1024
85
 
86
-
87
  @spaces.GPU(duration=300)
88
  def generate(
89
- prompt,
90
- uploaded_image,
91
- height,
92
- width,
93
- num_frames,
94
- num_inference_steps,
95
- seed_value,
96
- fps,
97
- lora_names,
98
- lora_weights,
99
- progress=gr.Progress(track_tqdm=True)
100
  ):
 
 
 
 
 
 
 
 
 
 
 
101
  with torch.cuda.device(0):
102
  if seed_value == -1:
103
  seed_value = torch.randint(0, MAX_SEED, (1,)).item()
104
  generator = torch.Generator('cuda').manual_seed(seed_value)
105
 
106
- # Handle image input
107
- if uploaded_image:
108
- init_image = Image.open(uploaded_image).convert("RGB").resize((width, height))
109
- if init_image.size != (width, height):
110
- raise gr.Error("Uploaded image resolution must match specified width and height.")
111
- else:
112
- init_image = None
113
-
114
- # Configure LoRA adapters
115
- adapter_names = ["hyvid_lora_adapter"] # Always include the illustration Lora
116
- adapter_weights = [0.8] # Illustration Lora weight
117
- for i, lora_name in enumerate(lora_names):
118
- if lora_name != "None":
119
- adapter_names.append("ttv4me_" + lora_name.split('.')[0]) # Create unique adapter name
120
- adapter_weights.append(lora_weights[i])
121
-
122
- # Check if the LoRA is already loaded, if not, load it
123
- if not hasattr(pipe, "ttv4me_" + lora_name.split('.')[0]):
124
- pipe.load_lora_weights(
125
- "Sergidev/TTV4ME", # Private repository
126
- weight_name=lora_name,
127
- adapter_name="ttv4me_" + lora_name.split('.')[0],
128
- token=os.environ.get("HF_TOKEN") # Access token from Space secrets
129
- )
130
-
131
- pipe.set_adapters(adapter_names, adapter_weights)
132
-
133
  with torch.amp.autocast_mode.autocast('cuda', dtype=torch.bfloat16), torch.inference_mode(), torch.no_grad():
134
  output = pipe(
135
  prompt=prompt,
136
- image=init_image,
137
  height=height,
138
  width=width,
139
  num_frames=num_frames,
@@ -147,7 +116,6 @@ def generate(
147
  gc.collect()
148
  return output_path
149
 
150
-
151
  def apply_preset(preset_name, *current_values):
152
  if preset_name == "Higher Resolution":
153
  return [608, 448, 24, 29, 12]
@@ -155,102 +123,41 @@ def apply_preset(preset_name, *current_values):
155
  return [512, 320, 42, 27, 14]
156
  return current_values
157
 
158
-
159
  css = """
160
- #col-container {
161
- margin: 0 auto;
162
- max-width: 850px;
163
- }
164
-
165
- .dark-theme {
166
- background-color: #1f1f1f;
167
- color: #ffffff;
168
- }
169
-
170
- .container {
171
- margin: 0 auto;
172
- padding: 20px;
173
- border-radius: 10px;
174
- background-color: #2d2d2d;
175
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
176
- }
177
-
178
- .title {
179
- text-align: center;
180
- margin-bottom: 1em;
181
- color: #ffffff;
182
- }
183
-
184
- .description {
185
- text-align: center;
186
- margin-bottom: 2em;
187
- color: #cccccc;
188
- font-size: 0.95em;
189
- line-height: 1.5;
190
- }
191
-
192
- .prompt-container {
193
- background-color: #363636;
194
- padding: 15px;
195
- border-radius: 8px;
196
- margin-bottom: 1em;
197
- width: 100%;
198
- }
199
-
200
- .prompt-textbox {
201
- min-height: 80px !important;
202
- }
203
-
204
- .preset-buttons {
205
- display: flex;
206
- gap: 10px;
207
- justify-content: center;
208
- margin-bottom: 1em;
209
- }
210
-
211
- .support-text {
212
- text-align: center;
213
- margin-top: 1em;
214
- color: #cccccc;
215
- font-size: 0.9em;
216
- }
217
-
218
- a {
219
- color: #00a7e1;
220
- text-decoration: none;
221
- }
222
-
223
- a:hover {
224
- text-decoration: underline;
225
  }
226
  """
227
 
228
  with gr.Blocks(css=css, theme="dark") as demo:
229
  with gr.Column(elem_id="col-container"):
230
- gr.Markdown("# 🎬 Huanyan Studio", elem_classes=["title"])
231
  gr.Markdown(
232
- """Image-to-video, text-to-video, with multiple LORAS to use.
233
- This space uses the 'hunyuan flat color v2' LORA by Motimalu to generate better 2d animated sequences. Prompt only handles 77 tokens.
234
- If you find this useful, please consider giving the space a ❤️ and supporting me on [Ko-Fi](https://ko-fi.com/sergidev)!""",
235
  elem_classes=["description"]
236
  )
237
 
238
  with gr.Column(elem_classes=["prompt-container"]):
239
  prompt = gr.Textbox(
240
  label="Prompt",
241
- placeholder="Enter your prompt here (Include the terms 'flat color, no lineart, blending' for 2d illustration)",
242
  show_label=False,
243
  elem_classes=["prompt-textbox"],
244
  lines=3
245
  )
246
- with gr.Column(elem_classes=["prompt-container"]):
247
- image_input = gr.Image(label="Upload Image (Optional)", image_types=["png", "jpg", "jpeg"])
248
 
249
  with gr.Row():
250
  run_button = gr.Button("🎨 Generate", variant="primary", size="lg")
 
251
  with gr.Row(elem_classes=["preset-buttons"]):
252
  preset_high_res = gr.Button("📺 Higher Resolution Preset")
253
  preset_more_frames = gr.Button("🎞️ More Frames Preset")
 
254
  with gr.Row():
255
  result = gr.Video(label="Generated Video")
256
 
@@ -271,7 +178,6 @@ with gr.Blocks(css=css, theme="dark") as demo:
271
  step=16,
272
  value=608,
273
  )
274
-
275
  width = gr.Slider(
276
  label="Width",
277
  minimum=256,
@@ -282,72 +188,73 @@ with gr.Blocks(css=css, theme="dark") as demo:
282
 
283
  with gr.Row():
284
  num_frames = gr.Slider(
285
- label="Number of frames to generate",
286
  minimum=1.0,
287
  maximum=257.0,
288
  step=1,
289
  value=24,
290
  )
291
-
292
  num_inference_steps = gr.Slider(
293
- label="Number of inference steps",
294
  minimum=1,
295
  maximum=50,
296
  step=1,
297
  value=29,
298
  )
 
 
 
 
 
 
 
299
 
300
- fps = gr.Slider(
301
- label="Frames per second",
302
- minimum=1,
303
- maximum=60,
304
- step=1,
305
- value=12,
306
- )
307
-
308
- # LoRA Selection
309
- lora_names = gr.CheckboxGroup(
310
- choices=list(TTV4ME_Loras.keys()),
311
- label="Select TTV4ME LoRAs"
312
- )
313
-
314
- lora_weights = []
315
- for i in range(len(TTV4ME_Loras)):
316
- lora_weights.append(gr.Slider(
317
- label=f"Weight for LoRA {i + 1}",
318
- minimum=0.0,
319
- maximum=1.0,
320
- step=0.05,
321
- value=0.5,
322
- visible=False # Initially hidden
323
- ))
324
-
325
- def update_lora_visibility(selected_loras):
326
- visibility = [lora in selected_loras for lora in TTV4ME_Loras.keys()]
327
- return visibility
328
-
329
- lora_names.change(
330
- update_lora_visibility,
331
- inputs=[lora_names],
332
- outputs=lora_weights
333
- )
334
-
335
- # Event handling
336
- input_components = [prompt, image_input, height, width, num_frames, num_inference_steps, seed, fps, lora_names]
337
- input_components.extend(lora_weights)
338
-
339
- run_button.click(
340
- fn=generate,
341
- inputs=input_components,
342
- outputs=[result],
343
- )
344
-
345
- # Preset button handlers
346
- preset_high_res.click(
347
- fn=lambda: apply_preset("Higher Resolution"),
348
- outputs=[height, width, num_frames, num_inference_steps, fps]
349
- )
350
 
351
- preset_more_frames.click(
352
- fn=lambda: apply_preset("More Frames"),
353
- outputs=[height, width, num_frames, num_inference_steps, fps]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  pipe.vae.enable_slicing()
46
  pipe.vae.eval()
47
 
48
+ # Available LORAs with display names
49
+ LORA_CHOICES = [
50
+ ("stripe_v2.safetensors", "Stripe Style"),
51
+ ("Top_Off.safetensors", "Top Off Effect"),
52
+ ("huanyan_helper.safetensors", "Hunyuan Helper"),
53
+ ("huanyan_helper_alpha.safetensors", "Hunyuan Alpha"),
54
+ ("hunyuan-t-solo-v1.0.safetensors", "Solo Animation")
55
+ ]
56
+
57
+ # Load all LORAs with hunyuanvideo-lora adapter
58
+ for weight_name, display_name in LORA_CHOICES:
59
+ pipe.load_lora_weights(
60
+ "Sergidev/TTV4ME",
61
+ weight_name=weight_name,
62
+ adapter_name=display_name.replace(" ", "_").lower(),
63
+ token=os.environ.get("HF_TOKEN")
64
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  # Memory cleanup
67
  gc.collect()
 
70
  MAX_SEED = np.iinfo(np.int32).max
71
  MAX_IMAGE_SIZE = 1024
72
 
 
73
  @spaces.GPU(duration=300)
74
  def generate(
75
+ prompt,
76
+ image_input,
77
+ height,
78
+ width,
79
+ num_frames,
80
+ num_inference_steps,
81
+ seed_value,
82
+ fps,
83
+ selected_loras,
84
+ lora_weights,
85
+ progress=gr.Progress(track_tqdm=True)
86
  ):
87
+ # Validate image resolution
88
+ if image_input is not None:
89
+ img = Image.open(image_input)
90
+ if img.size != (width, height):
91
+ raise gr.Error(f"Image resolution {img.size} must match video resolution ({width}x{height})")
92
+
93
+ # Configure LORAs
94
+ active_adapters = [lora[1].replace(" ", "_").lower() for lora in LORA_CHOICES if lora[1] in selected_loras]
95
+ weights = [float(lora_weights[selected_loras.index(lora[1])]) for lora in LORA_CHOICES if lora[1] in selected_loras]
96
+ pipe.set_adapters(active_adapters, weights)
97
+
98
  with torch.cuda.device(0):
99
  if seed_value == -1:
100
  seed_value = torch.randint(0, MAX_SEED, (1,)).item()
101
  generator = torch.Generator('cuda').manual_seed(seed_value)
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  with torch.amp.autocast_mode.autocast('cuda', dtype=torch.bfloat16), torch.inference_mode(), torch.no_grad():
104
  output = pipe(
105
  prompt=prompt,
 
106
  height=height,
107
  width=width,
108
  num_frames=num_frames,
 
116
  gc.collect()
117
  return output_path
118
 
 
119
  def apply_preset(preset_name, *current_values):
120
  if preset_name == "Higher Resolution":
121
  return [608, 448, 24, 29, 12]
 
123
  return [512, 320, 42, 27, 14]
124
  return current_values
125
 
 
126
  css = """
127
+ /* Existing CSS remains unchanged */
128
+ .lora-sliders {
129
+ margin-top: 15px;
130
+ border-top: 1px solid #444;
131
+ padding-top: 15px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  }
133
  """
134
 
135
  with gr.Blocks(css=css, theme="dark") as demo:
136
  with gr.Column(elem_id="col-container"):
137
+ gr.Markdown("# 🎬 Hunyuan Studio", elem_classes=["title"])
138
  gr.Markdown(
139
+ """Generate videos from text or images using multiple LoRA adapters.
140
+ Requires matching resolution between input image and output settings.""",
 
141
  elem_classes=["description"]
142
  )
143
 
144
  with gr.Column(elem_classes=["prompt-container"]):
145
  prompt = gr.Textbox(
146
  label="Prompt",
147
+ placeholder="Enter text prompt or upload image below",
148
  show_label=False,
149
  elem_classes=["prompt-textbox"],
150
  lines=3
151
  )
152
+ image_input = gr.Image(type="filepath", label="Upload Image (Optional)")
 
153
 
154
  with gr.Row():
155
  run_button = gr.Button("🎨 Generate", variant="primary", size="lg")
156
+
157
  with gr.Row(elem_classes=["preset-buttons"]):
158
  preset_high_res = gr.Button("📺 Higher Resolution Preset")
159
  preset_more_frames = gr.Button("🎞️ More Frames Preset")
160
+
161
  with gr.Row():
162
  result = gr.Video(label="Generated Video")
163
 
 
178
  step=16,
179
  value=608,
180
  )
 
181
  width = gr.Slider(
182
  label="Width",
183
  minimum=256,
 
188
 
189
  with gr.Row():
190
  num_frames = gr.Slider(
191
+ label="Number of frames",
192
  minimum=1.0,
193
  maximum=257.0,
194
  step=1,
195
  value=24,
196
  )
 
197
  num_inference_steps = gr.Slider(
198
+ label="Inference steps",
199
  minimum=1,
200
  maximum=50,
201
  step=1,
202
  value=29,
203
  )
204
+ fps = gr.Slider(
205
+ label="Frames per second",
206
+ minimum=1,
207
+ maximum=60,
208
+ step=1,
209
+ value=12,
210
+ )
211
 
212
+ with gr.Column(elem_classes=["lora-sliders"]):
213
+ gr.Markdown("### LoRA Adapters")
214
+ lora_checkboxes = gr.CheckboxGroup(
215
+ label="Select LoRAs",
216
+ choices=[display for (_, display) in LORA_CHOICES],
217
+ value=["Stripe Style", "Top Off Effect"]
218
+ )
219
+ lora_weight_sliders = []
220
+ for _, display_name in LORA_CHOICES:
221
+ lora_weight_sliders.append(
222
+ gr.Slider(
223
+ label=f"{display_name} Weight",
224
+ minimum=0.0,
225
+ maximum=1.0,
226
+ value=0.9 if "Stripe" in display_name else 0.8,
227
+ visible=False
228
+ )
229
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
+ # Event handling
232
+ run_button.click(
233
+ fn=generate,
234
+ inputs=[prompt, image_input, height, width, num_frames,
235
+ num_inference_steps, seed, fps, lora_checkboxes, lora_weight_sliders],
236
+ outputs=[result],
237
+ )
238
+
239
+ # Preset button handlers
240
+ preset_high_res.click(
241
+ fn=lambda: apply_preset("Higher Resolution"),
242
+ outputs=[height, width, num_frames, num_inference_steps, fps]
243
+ )
244
+ preset_more_frames.click(
245
+ fn=lambda: apply_preset("More Frames"),
246
+ outputs=[height, width, num_frames, num_inference_steps, fps]
247
+ )
248
+
249
+ # Show/hide LORA weight sliders based on checkbox selection
250
+ def toggle_lora_sliders(selected_loras):
251
+ updates = []
252
+ for lora in LORA_CHOICES:
253
+ updates.append(gr.update(visible=lora[1] in selected_loras))
254
+ return updates
255
+
256
+ lora_checkboxes.change(
257
+ fn=toggle_lora_sliders,
258
+ inputs=lora_checkboxes,
259
+ outputs=lora_weight_sliders
260
+ )