Spaces:
Running
on
Zero
Running
on
Zero
Update app.py
Browse files
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 =
|
38 |
-
default_width: int =
|
39 |
-
max_area: float =
|
40 |
slider_min_h: int = 128
|
41 |
-
slider_max_h: int =
|
42 |
slider_min_w: int = 128
|
43 |
-
slider_max_w: int =
|
44 |
fixed_fps: int = 24
|
45 |
min_frames: int = 8
|
46 |
-
max_frames: int =
|
47 |
-
default_prompt: str = "make this image
|
48 |
-
default_negative_prompt: str = "static,
|
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 =
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
# ์ต๋
|
114 |
-
new_h = int(np.clip(calc_h, self.config.slider_min_h,
|
115 |
-
new_w = int(np.clip(calc_w, self.config.slider_min_w,
|
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) >
|
132 |
-
return False, "โ ๏ธ Prompt is too long (max
|
133 |
|
134 |
# Zero GPU์ ์ต์ ํ๋ ์ ํ
|
135 |
if duration < 0.3:
|
136 |
return False, "โฑ๏ธ Duration too short (min 0.3s)"
|
137 |
|
138 |
-
if duration > 1.
|
139 |
-
return False, "โฑ๏ธ Duration too long (max 1.
|
140 |
|
141 |
-
# ํฝ์
์ ์ ํ (
|
142 |
-
max_pixels =
|
143 |
if height * width > max_pixels:
|
144 |
-
return False, f"๐ Total pixels limited to {max_pixels:,} (e.g.,
|
|
|
|
|
|
|
145 |
|
146 |
-
|
147 |
-
|
|
|
|
|
148 |
|
149 |
-
if steps >
|
150 |
-
return False, "๐ง Maximum
|
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 =
|
183 |
|
184 |
# ํฝ์
์์ ๋ฐ๋ฅธ ์ถ๊ฐ ์๊ฐ
|
185 |
pixels = height * width
|
186 |
-
if pixels >
|
187 |
base_duration += 20
|
188 |
-
elif pixels >
|
189 |
base_duration += 10
|
190 |
|
191 |
# ์คํ
์์ ๋ฐ๋ฅธ ์ถ๊ฐ ์๊ฐ
|
192 |
if steps > 4:
|
|
|
|
|
193 |
base_duration += 10
|
194 |
|
195 |
-
#
|
196 |
-
|
|
|
|
|
|
|
|
|
|
|
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=
|
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 |
-
|
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 |
-
#
|
279 |
-
|
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 |
-
|
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[:
|
317 |
-
negative_prompt=negative_prompt[:
|
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
|
333 |
|
334 |
progress(0.9, desc="๐พ Saving video...")
|
335 |
|
336 |
# ๋น๋์ค ์ ์ฅ
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
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"โ
|
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:
|
431 |
-
<li>Max duration: 1.
|
432 |
-
<li>Max steps:
|
433 |
-
<li>Processing time: ~
|
|
|
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.
|
456 |
step=0.1,
|
457 |
-
value=
|
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=
|
472 |
step=32,
|
473 |
-
value=
|
474 |
label="Height"
|
475 |
)
|
476 |
width_slider = gr.Slider(
|
477 |
minimum=128,
|
478 |
-
maximum=
|
479 |
step=32,
|
480 |
-
value=
|
481 |
label="Width"
|
482 |
)
|
483 |
|
484 |
steps_slider = gr.Slider(
|
485 |
minimum=1,
|
486 |
-
maximum=
|
487 |
step=1,
|
488 |
-
value=
|
489 |
-
label="Steps (3
|
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 |
-
-
|
529 |
-
-
|
530 |
-
-
|
531 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|