seawolf2357 commited on
Commit
a816f3f
ยท
verified ยท
1 Parent(s): ee3d852

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -82
app.py CHANGED
@@ -33,19 +33,19 @@ class VideoGenerationConfig:
33
  lora_repo_id: str = "Kijai/WanVideo_comfy"
34
  lora_filename: str = "Wan21_CausVid_14B_T2V_lora_rank32.safetensors"
35
  mod_value: int = 32
36
- # Zero GPU๋ฅผ ์œ„ํ•œ ๋ณด์ˆ˜์ ์ธ ๊ธฐ๋ณธ๊ฐ’
37
- default_height: int = 384
38
- default_width: int = 384
39
- max_area: float = 384.0 * 384.0 # Zero GPU์— ์ตœ์ ํ™”
40
  slider_min_h: int = 128
41
- slider_max_h: int = 640 # ๋” ๋‚ฎ์€ ์ตœ๋Œ€๊ฐ’
42
  slider_min_w: int = 128
43
- slider_max_w: int = 640 # ๋” ๋‚ฎ์€ ์ตœ๋Œ€๊ฐ’
44
  fixed_fps: int = 24
45
  min_frames: int = 8
46
- max_frames: int = 36 # ๋” ๋‚ฎ์€ ์ตœ๋Œ€ ํ”„๋ ˆ์ž„
47
- default_prompt: str = "make this image come alive, cinematic motion"
48
- default_negative_prompt: str = "static, blurred, low quality"
49
  # GPU ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™” ์„ค์ •
50
  enable_model_cpu_offload: bool = True
51
  enable_vae_slicing: bool = True
@@ -101,23 +101,37 @@ class VideoGenerator:
101
 
102
  aspect_ratio = orig_h / orig_w
103
 
104
- # Zero GPU์— ์ตœ์ ํ™”๋œ ์ž‘์€ ํ•ด์ƒ๋„
105
- max_area = 384.0 * 384.0
 
 
 
 
 
 
106
 
107
  calc_h = round(np.sqrt(max_area * aspect_ratio))
108
  calc_w = round(np.sqrt(max_area / aspect_ratio))
109
 
 
110
  calc_h = max(self.config.mod_value, (calc_h // self.config.mod_value) * self.config.mod_value)
111
  calc_w = max(self.config.mod_value, (calc_w // self.config.mod_value) * self.config.mod_value)
112
 
113
- # ์ตœ๋Œ€ 640์œผ๋กœ ์ œํ•œ
114
- new_h = int(np.clip(calc_h, self.config.slider_min_h, 640))
115
- new_w = int(np.clip(calc_w, self.config.slider_min_w, 640))
116
 
117
  # mod_value์— ๋งž์ถค
118
  new_h = (new_h // self.config.mod_value) * self.config.mod_value
119
  new_w = (new_w // self.config.mod_value) * self.config.mod_value
120
 
 
 
 
 
 
 
 
121
  return new_h, new_w
122
 
123
  def validate_inputs(self, image: Image.Image, prompt: str, height: int,
@@ -128,26 +142,31 @@ class VideoGenerator:
128
  if not prompt or len(prompt.strip()) == 0:
129
  return False, "โœ๏ธ Please provide a prompt"
130
 
131
- if len(prompt) > 300: # ๋” ์งง์€ ํ”„๋กฌํ”„ํŠธ ์ œํ•œ
132
- return False, "โš ๏ธ Prompt is too long (max 300 characters)"
133
 
134
  # Zero GPU์— ์ตœ์ ํ™”๋œ ์ œํ•œ
135
  if duration < 0.3:
136
  return False, "โฑ๏ธ Duration too short (min 0.3s)"
137
 
138
- if duration > 1.5:
139
- return False, "โฑ๏ธ Duration too long (max 1.5s for stability)"
140
 
141
- # ํ”ฝ์…€ ์ˆ˜ ์ œํ•œ (384x384 = 147,456 ํ”ฝ์…€)
142
- max_pixels = 384 * 384
143
  if height * width > max_pixels:
144
- return False, f"๐Ÿ“ Total pixels limited to {max_pixels:,} (e.g., 384ร—384)"
 
 
 
145
 
146
- if height > 640 or width > 640:
147
- return False, "๐Ÿ“ Maximum dimension is 640 pixels"
 
 
148
 
149
- if steps > 6:
150
- return False, "๐Ÿ”ง Maximum 6 steps in Zero GPU environment"
151
 
152
  return True, None
153
 
@@ -179,27 +198,34 @@ def handle_image_upload(image):
179
  def get_duration(input_image, prompt, height, width, negative_prompt,
180
  duration_seconds, guidance_scale, steps, seed, randomize_seed, progress):
181
  # Zero GPU ํ™˜๊ฒฝ์—์„œ ๋งค์šฐ ๋ณด์ˆ˜์ ์ธ ์‹œ๊ฐ„ ํ• ๋‹น
182
- base_duration = 40 # ๊ธฐ๋ณธ 40์ดˆ
183
 
184
  # ํ”ฝ์…€ ์ˆ˜์— ๋”ฐ๋ฅธ ์ถ”๊ฐ€ ์‹œ๊ฐ„
185
  pixels = height * width
186
- if pixels > 200000: # 448x448 ์ด์ƒ
187
  base_duration += 20
188
- elif pixels > 147456: # 384x384 ์ด์ƒ
189
  base_duration += 10
190
 
191
  # ์Šคํ… ์ˆ˜์— ๋”ฐ๋ฅธ ์ถ”๊ฐ€ ์‹œ๊ฐ„
192
  if steps > 4:
 
 
193
  base_duration += 10
194
 
195
- # ์ตœ๋Œ€ 70์ดˆ๋กœ ์ œํ•œ (Zero GPU์˜ ์•ˆ์ „ํ•œ ํ•œ๊ณ„)
196
- return min(base_duration, 70)
 
 
 
 
 
197
 
198
  @spaces.GPU(duration=get_duration)
199
  @measure_time
200
  def generate_video(input_image, prompt, height, width,
201
  negative_prompt=config.default_negative_prompt,
202
- duration_seconds=1.0, guidance_scale=1, steps=3,
203
  seed=42, randomize_seed=False,
204
  progress=gr.Progress(track_tqdm=True)):
205
 
@@ -218,6 +244,7 @@ def generate_video(input_image, prompt, height, width,
218
  input_image, prompt, height, width, duration_seconds, steps
219
  )
220
  if not is_valid:
 
221
  raise gr.Error(error_msg)
222
 
223
  # ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ
@@ -228,6 +255,8 @@ def generate_video(input_image, prompt, height, width,
228
  # ๋ชจ๋ธ ๋กœ๋”ฉ (GPU ํ•จ์ˆ˜ ๋‚ด์—์„œ)
229
  if pipe is None:
230
  try:
 
 
231
  # ์ปดํฌ๋„ŒํŠธ ๋กœ๋“œ
232
  image_encoder = CLIPVisionModel.from_pretrained(
233
  config.model_id,
@@ -257,16 +286,8 @@ def generate_video(input_image, prompt, height, width,
257
  pipe.scheduler.config, flow_shift=8.0
258
  )
259
 
260
- # LoRA ๋กœ๋“œ (์„ ํƒ์ )
261
- try:
262
- causvid_path = hf_hub_download(
263
- repo_id=config.lora_repo_id, filename=config.lora_filename
264
- )
265
- pipe.load_lora_weights(causvid_path, adapter_name="causvid_lora")
266
- pipe.set_adapters(["causvid_lora"], adapter_weights=[0.95])
267
- pipe.fuse_lora()
268
- except:
269
- logger.warning("LoRA loading skipped")
270
 
271
  # GPU๋กœ ์ด๋™
272
  pipe.to("cuda")
@@ -275,11 +296,8 @@ def generate_video(input_image, prompt, height, width,
275
  pipe.enable_vae_slicing()
276
  pipe.enable_vae_tiling()
277
 
278
- # xFormers ์‹œ๋„
279
- try:
280
- pipe.enable_xformers_memory_efficient_attention()
281
- except:
282
- pass
283
 
284
  logger.info("Model loaded successfully")
285
 
@@ -296,10 +314,12 @@ def generate_video(input_image, prompt, height, width,
296
  # ํ”„๋ ˆ์ž„ ์ˆ˜ ๊ณ„์‚ฐ (๋งค์šฐ ๋ณด์ˆ˜์ )
297
  num_frames = min(
298
  int(round(duration_seconds * config.fixed_fps)),
299
- 36 # ์ตœ๋Œ€ 36ํ”„๋ ˆ์ž„ (1.5์ดˆ)
300
  )
301
  num_frames = max(8, num_frames) # ์ตœ์†Œ 8ํ”„๋ ˆ์ž„
302
 
 
 
303
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
304
 
305
  # ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ฆˆ
@@ -308,37 +328,58 @@ def generate_video(input_image, prompt, height, width,
308
  progress(0.4, desc="๐ŸŽฌ Generating video...")
309
 
310
  # ๋น„๋””์˜ค ์ƒ์„ฑ
311
- with torch.inference_mode(), torch.amp.autocast('cuda', enabled=True):
312
  try:
313
- # ์งง์€ ํƒ€์ž„์•„์›ƒ์œผ๋กœ ์ƒ์„ฑ
 
 
 
314
  output_frames_list = pipe(
315
  image=resized_image,
316
- prompt=prompt[:200], # ํ”„๋กฌํ”„ํŠธ ๊ธธ์ด ์ œํ•œ
317
- negative_prompt=negative_prompt[:100], # ๋„ค๊ฑฐํ‹ฐ๋ธŒ ํ”„๋กฌํ”„ํŠธ๋„ ์ œํ•œ
318
  height=target_h,
319
  width=target_w,
320
  num_frames=num_frames,
321
  guidance_scale=float(guidance_scale),
322
  num_inference_steps=int(steps),
323
  generator=torch.Generator(device="cuda").manual_seed(current_seed),
324
- return_dict=True
 
 
325
  ).frames[0]
326
 
 
 
327
  except torch.cuda.OutOfMemoryError:
 
328
  clear_gpu_memory()
329
- raise gr.Error("๐Ÿ’พ GPU out of memory. Try smaller dimensions.")
 
 
 
 
 
 
 
 
330
  except Exception as e:
331
- logger.error(f"Generation error: {e}")
332
- raise gr.Error(f"โŒ Generation failed: {str(e)[:100]}")
333
 
334
  progress(0.9, desc="๐Ÿ’พ Saving video...")
335
 
336
  # ๋น„๋””์˜ค ์ €์žฅ
337
- filename = video_generator.generate_unique_filename(current_seed)
338
- with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
339
- video_path = tmpfile.name
340
-
341
- export_to_video(output_frames_list, video_path, fps=config.fixed_fps)
 
 
 
 
 
342
 
343
  progress(1.0, desc="โœจ Complete!")
344
  logger.info(f"Video generated: {num_frames} frames, {target_h}x{target_w}")
@@ -346,15 +387,16 @@ def generate_video(input_image, prompt, height, width,
346
  # ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ
347
  del output_frames_list
348
  del resized_image
349
- clear_gpu_memory()
 
350
 
351
  return video_path, current_seed
352
 
353
  except gr.Error:
354
  raise
355
  except Exception as e:
356
- logger.error(f"Unexpected error: {e}")
357
- raise gr.Error(f"โŒ Error: {str(e)[:100]}")
358
 
359
  finally:
360
  generation_lock.release()
@@ -425,12 +467,13 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
425
  # ๊ฒฝ๊ณ 
426
  gr.HTML("""
427
  <div class="warning-box">
428
- <strong>โšก Zero GPU Limitations:</strong>
429
  <ul style="margin: 5px 0; padding-left: 20px;">
430
- <li>Max resolution: 384ร—384 (recommended)</li>
431
- <li>Max duration: 1.5 seconds</li>
432
- <li>Max steps: 6 (3-4 recommended)</li>
433
- <li>Processing time: ~40-60 seconds</li>
 
434
  </ul>
435
  </div>
436
  """)
@@ -452,9 +495,9 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
452
 
453
  duration_input = gr.Slider(
454
  minimum=0.3,
455
- maximum=1.5,
456
  step=0.1,
457
- value=1.0,
458
  label="โฑ๏ธ Duration (seconds)"
459
  )
460
 
@@ -468,25 +511,25 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
468
  with gr.Row():
469
  height_slider = gr.Slider(
470
  minimum=128,
471
- maximum=640,
472
  step=32,
473
- value=384,
474
  label="Height"
475
  )
476
  width_slider = gr.Slider(
477
  minimum=128,
478
- maximum=640,
479
  step=32,
480
- value=384,
481
  label="Width"
482
  )
483
 
484
  steps_slider = gr.Slider(
485
  minimum=1,
486
- maximum=6,
487
  step=1,
488
- value=3,
489
- label="Steps (3-4 recommended)"
490
  )
491
 
492
  with gr.Row():
@@ -524,11 +567,18 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
524
  )
525
 
526
  gr.Markdown("""
527
- ### ๐Ÿ’ก Tips:
528
- - Use 384ร—384 for best results
529
- - Keep prompts simple and clear
530
- - 3-4 steps is optimal
531
- - Wait for completion before next generation
 
 
 
 
 
 
 
532
  """)
533
 
534
  # Event handlers
 
33
  lora_repo_id: str = "Kijai/WanVideo_comfy"
34
  lora_filename: str = "Wan21_CausVid_14B_T2V_lora_rank32.safetensors"
35
  mod_value: int = 32
36
+ # Zero GPU๋ฅผ ์œ„ํ•œ ๋งค์šฐ ๋ณด์ˆ˜์ ์ธ ๊ธฐ๋ณธ๊ฐ’
37
+ default_height: int = 320
38
+ default_width: int = 320
39
+ max_area: float = 320.0 * 320.0 # Zero GPU์— ์ตœ์ ํ™”
40
  slider_min_h: int = 128
41
+ slider_max_h: int = 512 # ๋” ๋‚ฎ์€ ์ตœ๋Œ€๊ฐ’
42
  slider_min_w: int = 128
43
+ slider_max_w: int = 512 # ๋” ๋‚ฎ์€ ์ตœ๋Œ€๊ฐ’
44
  fixed_fps: int = 24
45
  min_frames: int = 8
46
+ max_frames: int = 30 # ๋” ๋‚ฎ์€ ์ตœ๋Œ€ ํ”„๋ ˆ์ž„ (1.25์ดˆ)
47
+ default_prompt: str = "make this image move, smooth motion"
48
+ default_negative_prompt: str = "static, blur"
49
  # GPU ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™” ์„ค์ •
50
  enable_model_cpu_offload: bool = True
51
  enable_vae_slicing: bool = True
 
101
 
102
  aspect_ratio = orig_h / orig_w
103
 
104
+ # Zero GPU์— ์ตœ์ ํ™”๋œ ๋งค์šฐ ์ž‘์€ ํ•ด์ƒ๋„
105
+ max_area = 320.0 * 320.0 # 102,400 ํ”ฝ์…€
106
+
107
+ # ์ข…ํšก๋น„๊ฐ€ ๋„ˆ๋ฌด ๊ทน๋‹จ์ ์ธ ๊ฒฝ์šฐ ์กฐ์ •
108
+ if aspect_ratio > 2.0:
109
+ aspect_ratio = 2.0
110
+ elif aspect_ratio < 0.5:
111
+ aspect_ratio = 0.5
112
 
113
  calc_h = round(np.sqrt(max_area * aspect_ratio))
114
  calc_w = round(np.sqrt(max_area / aspect_ratio))
115
 
116
+ # mod_value์— ๋งž์ถค
117
  calc_h = max(self.config.mod_value, (calc_h // self.config.mod_value) * self.config.mod_value)
118
  calc_w = max(self.config.mod_value, (calc_w // self.config.mod_value) * self.config.mod_value)
119
 
120
+ # ์ตœ๋Œ€ 512๋กœ ์ œํ•œ
121
+ new_h = int(np.clip(calc_h, self.config.slider_min_h, 512))
122
+ new_w = int(np.clip(calc_w, self.config.slider_min_w, 512))
123
 
124
  # mod_value์— ๋งž์ถค
125
  new_h = (new_h // self.config.mod_value) * self.config.mod_value
126
  new_w = (new_w // self.config.mod_value) * self.config.mod_value
127
 
128
+ # ์ตœ์ข… ํ”ฝ์…€ ์ˆ˜ ํ™•์ธ
129
+ if new_h * new_w > 102400: # 320x320
130
+ # ๋น„์œจ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์ถ•์†Œ
131
+ scale = np.sqrt(102400 / (new_h * new_w))
132
+ new_h = int((new_h * scale) // self.config.mod_value) * self.config.mod_value
133
+ new_w = int((new_w * scale) // self.config.mod_value) * self.config.mod_value
134
+
135
  return new_h, new_w
136
 
137
  def validate_inputs(self, image: Image.Image, prompt: str, height: int,
 
142
  if not prompt or len(prompt.strip()) == 0:
143
  return False, "โœ๏ธ Please provide a prompt"
144
 
145
+ if len(prompt) > 200: # ๋” ์งง์€ ํ”„๋กฌํ”„ํŠธ ์ œํ•œ
146
+ return False, "โš ๏ธ Prompt is too long (max 200 characters)"
147
 
148
  # Zero GPU์— ์ตœ์ ํ™”๋œ ์ œํ•œ
149
  if duration < 0.3:
150
  return False, "โฑ๏ธ Duration too short (min 0.3s)"
151
 
152
+ if duration > 1.2: # ๋” ์งง์€ ์ตœ๋Œ€ duration
153
+ return False, "โฑ๏ธ Duration too long (max 1.2s for stability)"
154
 
155
+ # ํ”ฝ์…€ ์ˆ˜ ์ œํ•œ (๋” ๋ณด์ˆ˜์ ์œผ๋กœ)
156
+ max_pixels = 320 * 320 # 102,400 ํ”ฝ์…€
157
  if height * width > max_pixels:
158
+ return False, f"๐Ÿ“ Total pixels limited to {max_pixels:,} (e.g., 320ร—320, 256ร—384)"
159
+
160
+ if height > 512 or width > 512: # ๋” ๋‚ฎ์€ ์ตœ๋Œ€๊ฐ’
161
+ return False, "๐Ÿ“ Maximum dimension is 512 pixels"
162
 
163
+ # ์ข…ํšก๋น„ ์ฒดํฌ
164
+ aspect_ratio = max(height/width, width/height)
165
+ if aspect_ratio > 2.0:
166
+ return False, "๐Ÿ“ Aspect ratio too extreme (max 2:1 or 1:2)"
167
 
168
+ if steps > 5: # ๋” ๋‚ฎ์€ ์ตœ๋Œ€ ์Šคํ…
169
+ return False, "๐Ÿ”ง Maximum 5 steps in Zero GPU environment"
170
 
171
  return True, None
172
 
 
198
  def get_duration(input_image, prompt, height, width, negative_prompt,
199
  duration_seconds, guidance_scale, steps, seed, randomize_seed, progress):
200
  # Zero GPU ํ™˜๊ฒฝ์—์„œ ๋งค์šฐ ๋ณด์ˆ˜์ ์ธ ์‹œ๊ฐ„ ํ• ๋‹น
201
+ base_duration = 50 # ๊ธฐ๋ณธ 50์ดˆ๋กœ ์ฆ๊ฐ€
202
 
203
  # ํ”ฝ์…€ ์ˆ˜์— ๋”ฐ๋ฅธ ์ถ”๊ฐ€ ์‹œ๊ฐ„
204
  pixels = height * width
205
+ if pixels > 147456: # 384x384 ์ด์ƒ
206
  base_duration += 20
207
+ elif pixels > 100000: # ~316x316 ์ด์ƒ
208
  base_duration += 10
209
 
210
  # ์Šคํ… ์ˆ˜์— ๋”ฐ๋ฅธ ์ถ”๊ฐ€ ์‹œ๊ฐ„
211
  if steps > 4:
212
+ base_duration += 15
213
+ elif steps > 2:
214
  base_duration += 10
215
 
216
+ # ์ข…ํšก๋น„๊ฐ€ ๊ทน๋‹จ์ ์ธ ๊ฒฝ์šฐ ์ถ”๊ฐ€ ์‹œ๊ฐ„
217
+ aspect_ratio = max(height/width, width/height)
218
+ if aspect_ratio > 1.5: # 3:2 ์ด์ƒ์˜ ๋น„์œจ
219
+ base_duration += 10
220
+
221
+ # ์ตœ๋Œ€ 90์ดˆ๋กœ ์ œํ•œ
222
+ return min(base_duration, 90)
223
 
224
  @spaces.GPU(duration=get_duration)
225
  @measure_time
226
  def generate_video(input_image, prompt, height, width,
227
  negative_prompt=config.default_negative_prompt,
228
+ duration_seconds=0.8, guidance_scale=1, steps=3,
229
  seed=42, randomize_seed=False,
230
  progress=gr.Progress(track_tqdm=True)):
231
 
 
244
  input_image, prompt, height, width, duration_seconds, steps
245
  )
246
  if not is_valid:
247
+ logger.warning(f"Validation failed: {error_msg}")
248
  raise gr.Error(error_msg)
249
 
250
  # ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ
 
255
  # ๋ชจ๋ธ ๋กœ๋”ฉ (GPU ํ•จ์ˆ˜ ๋‚ด์—์„œ)
256
  if pipe is None:
257
  try:
258
+ logger.info("Loading model components...")
259
+
260
  # ์ปดํฌ๋„ŒํŠธ ๋กœ๋“œ
261
  image_encoder = CLIPVisionModel.from_pretrained(
262
  config.model_id,
 
286
  pipe.scheduler.config, flow_shift=8.0
287
  )
288
 
289
+ # LoRA ๋กœ๋“œ ๊ฑด๋„ˆ๋›ฐ๊ธฐ (์•ˆ์ •์„ฑ์„ ์œ„ํ•ด)
290
+ logger.info("Skipping LoRA for stability")
 
 
 
 
 
 
 
 
291
 
292
  # GPU๋กœ ์ด๋™
293
  pipe.to("cuda")
 
296
  pipe.enable_vae_slicing()
297
  pipe.enable_vae_tiling()
298
 
299
+ # ๋ชจ๋ธ CPU ์˜คํ”„๋กœ๋“œ ํ™œ์„ฑํ™” (๋ฉ”๋ชจ๋ฆฌ ์ ˆ์•ฝ)
300
+ pipe.enable_model_cpu_offload()
 
 
 
301
 
302
  logger.info("Model loaded successfully")
303
 
 
314
  # ํ”„๋ ˆ์ž„ ์ˆ˜ ๊ณ„์‚ฐ (๋งค์šฐ ๋ณด์ˆ˜์ )
315
  num_frames = min(
316
  int(round(duration_seconds * config.fixed_fps)),
317
+ 24 # ์ตœ๋Œ€ 24ํ”„๋ ˆ์ž„ (1์ดˆ)
318
  )
319
  num_frames = max(8, num_frames) # ์ตœ์†Œ 8ํ”„๋ ˆ์ž„
320
 
321
+ logger.info(f"Generating {num_frames} frames at {target_h}x{target_w}")
322
+
323
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
324
 
325
  # ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ฆˆ
 
328
  progress(0.4, desc="๐ŸŽฌ Generating video...")
329
 
330
  # ๋น„๋””์˜ค ์ƒ์„ฑ
331
+ with torch.inference_mode(), torch.amp.autocast('cuda', enabled=True, dtype=torch.float16):
332
  try:
333
+ # ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ ์œ„ํ•œ ์„ค์ •
334
+ torch.cuda.empty_cache()
335
+
336
+ # ์ƒ์„ฑ ํŒŒ๋ผ๋ฏธํ„ฐ ์ตœ์ ํ™”
337
  output_frames_list = pipe(
338
  image=resized_image,
339
+ prompt=prompt[:150], # ํ”„๋กฌํ”„ํŠธ ๊ธธ์ด ์ œํ•œ
340
+ negative_prompt=negative_prompt[:50] if negative_prompt else "",
341
  height=target_h,
342
  width=target_w,
343
  num_frames=num_frames,
344
  guidance_scale=float(guidance_scale),
345
  num_inference_steps=int(steps),
346
  generator=torch.Generator(device="cuda").manual_seed(current_seed),
347
+ return_dict=True,
348
+ # ์ถ”๊ฐ€ ์ตœ์ ํ™” ํŒŒ๋ผ๋ฏธํ„ฐ
349
+ output_type="pil"
350
  ).frames[0]
351
 
352
+ logger.info("Video generation completed successfully")
353
+
354
  except torch.cuda.OutOfMemoryError:
355
+ logger.error("GPU OOM error")
356
  clear_gpu_memory()
357
+ raise gr.Error("๐Ÿ’พ GPU out of memory. Try smaller dimensions (256x256 recommended).")
358
+ except RuntimeError as e:
359
+ if "out of memory" in str(e).lower():
360
+ logger.error("Runtime OOM error")
361
+ clear_gpu_memory()
362
+ raise gr.Error("๐Ÿ’พ GPU memory error. Please try again with smaller settings.")
363
+ else:
364
+ logger.error(f"Runtime error: {e}")
365
+ raise gr.Error(f"โŒ Generation failed: {str(e)[:50]}")
366
  except Exception as e:
367
+ logger.error(f"Generation error: {type(e).__name__}: {e}")
368
+ raise gr.Error(f"โŒ Generation failed. Try reducing resolution or steps.")
369
 
370
  progress(0.9, desc="๐Ÿ’พ Saving video...")
371
 
372
  # ๋น„๋””์˜ค ์ €์žฅ
373
+ try:
374
+ filename = video_generator.generate_unique_filename(current_seed)
375
+ with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
376
+ video_path = tmpfile.name
377
+
378
+ export_to_video(output_frames_list, video_path, fps=config.fixed_fps)
379
+ logger.info(f"Video saved: {video_path}")
380
+ except Exception as e:
381
+ logger.error(f"Save error: {e}")
382
+ raise gr.Error("Failed to save video")
383
 
384
  progress(1.0, desc="โœจ Complete!")
385
  logger.info(f"Video generated: {num_frames} frames, {target_h}x{target_w}")
 
387
  # ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ
388
  del output_frames_list
389
  del resized_image
390
+ torch.cuda.empty_cache()
391
+ gc.collect()
392
 
393
  return video_path, current_seed
394
 
395
  except gr.Error:
396
  raise
397
  except Exception as e:
398
+ logger.error(f"Unexpected error: {type(e).__name__}: {e}")
399
+ raise gr.Error(f"โŒ Unexpected error. Please try again with smaller settings.")
400
 
401
  finally:
402
  generation_lock.release()
 
467
  # ๊ฒฝ๊ณ 
468
  gr.HTML("""
469
  <div class="warning-box">
470
+ <strong>โšก Zero GPU Strict Limitations:</strong>
471
  <ul style="margin: 5px 0; padding-left: 20px;">
472
+ <li>Max resolution: 320ร—320 (recommended 256ร—256)</li>
473
+ <li>Max duration: 1.2 seconds</li>
474
+ <li>Max steps: 5 (2-3 recommended)</li>
475
+ <li>Processing time: ~50-80 seconds</li>
476
+ <li>Please wait for completion before next generation</li>
477
  </ul>
478
  </div>
479
  """)
 
495
 
496
  duration_input = gr.Slider(
497
  minimum=0.3,
498
+ maximum=1.2,
499
  step=0.1,
500
+ value=0.8,
501
  label="โฑ๏ธ Duration (seconds)"
502
  )
503
 
 
511
  with gr.Row():
512
  height_slider = gr.Slider(
513
  minimum=128,
514
+ maximum=512,
515
  step=32,
516
+ value=256,
517
  label="Height"
518
  )
519
  width_slider = gr.Slider(
520
  minimum=128,
521
+ maximum=512,
522
  step=32,
523
+ value=256,
524
  label="Width"
525
  )
526
 
527
  steps_slider = gr.Slider(
528
  minimum=1,
529
+ maximum=5,
530
  step=1,
531
+ value=2,
532
+ label="Steps (2-3 recommended)"
533
  )
534
 
535
  with gr.Row():
 
567
  )
568
 
569
  gr.Markdown("""
570
+ ### ๐Ÿ’ก Tips for Zero GPU:
571
+ - **Best**: 256ร—256 resolution
572
+ - **Safe**: 2-3 steps only
573
+ - **Duration**: 0.8s is optimal
574
+ - **Prompts**: Keep short and simple
575
+ - **Important**: Wait for completion!
576
+
577
+ ### โš ๏ธ If GPU stops:
578
+ - Reduce resolution to 256ร—256
579
+ - Use only 2 steps
580
+ - Keep duration under 1 second
581
+ - Avoid extreme aspect ratios
582
  """)
583
 
584
  # Event handlers