HAL1993 commited on
Commit
3c9c1d9
·
verified ·
1 Parent(s): eba10f4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +435 -404
app.py CHANGED
@@ -1,13 +1,20 @@
1
  # ------------------------------------------------------------
2
- # IMPORTS
3
  # ------------------------------------------------------------
 
 
 
 
 
 
4
  import spaces
5
  import torch
6
- import requests
7
  import random
8
  import gc
9
  import tempfile
10
- import numpy as np
 
11
  from PIL import Image
12
 
13
  import gradio as gr
@@ -15,58 +22,81 @@ from diffusers.pipelines.wan.pipeline_wan_i2v import WanImageToVideoPipeline
15
  from diffusers.models.transformers.transformer_wan import WanTransformer3DModel
16
  from diffusers.utils.export_utils import export_to_video
17
 
18
- from torchao.quantization import quantize_
19
- from torchao.quantization import Float8DynamicActivationFloat8WeightConfig
20
- from torchao.quantization import Int8WeightOnlyConfig
21
-
22
  import aoti
23
 
 
 
 
24
  # ------------------------------------------------------------
25
  # CONFIG
26
  # ------------------------------------------------------------
27
- MODEL_ID = "Wan-AI/Wan2.2-I2V-A14B-Diffusers"
28
-
29
  MAX_DIM = 832
30
  MIN_DIM = 480
31
  SQUARE_DIM = 640
32
  MULTIPLE_OF = 16
33
-
34
  MAX_SEED = np.iinfo(np.int32).max
35
 
36
  FIXED_FPS = 16
37
  MIN_FRAMES_MODEL = 8
38
  MAX_FRAMES_MODEL = 80
39
 
40
- MIN_DURATION = round(MIN_FRAMES_MODEL / FIXED_FPS, 1)
41
- MAX_DURATION = round(MAX_FRAMES_MODEL / FIXED_FPS, 1)
42
-
43
  default_prompt_i2v = "make this image come alive, cinematic motion, smooth animation"
44
  default_negative_prompt = (
45
- "色调艳丽, 过曝, 静态, 细节模糊不清, 字幕, 风格, 作品, 画作, 画面, 静止, 整体发灰, 最差质量, "
46
- "低质量, JPEG压缩残留, 丑陋的, 残缺的, 多余的手指, 画得不好的手部, 画得不好的脸部, 畸形的, 毁容的, "
47
- "形态畸形的肢体, 手指融合, 静止不动的画面, 杂乱的背景, 三条腿, 背景人很多, 倒着走"
48
  )
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  # ------------------------------------------------------------
51
  # MODEL LOADING
52
  # ------------------------------------------------------------
53
  pipe = WanImageToVideoPipeline.from_pretrained(
54
- MODEL_ID,
55
- transformer=WanTransformer3DModel.from_pretrained(
56
- "cbensimon/Wan2.2-I2V-A14B-bf16-Diffusers",
57
- subfolder="transformer",
58
- torch_dtype=torch.bfloat16,
59
- device_map="cuda",
60
- ),
61
- transformer_2=WanTransformer3DModel.from_pretrained(
62
- "cbensimon/Wan2.2-I2V-A14B-bf16-Diffusers",
63
- subfolder="transformer_2",
64
- torch_dtype=torch.bfloat16,
65
- device_map="cuda",
66
- ),
67
  torch_dtype=torch.bfloat16,
 
68
  ).to("cuda")
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  # ---- LoRA -------------------------------------------------
71
  pipe.load_lora_weights(
72
  "Kijai/WanVideo_comfy",
@@ -74,12 +104,11 @@ pipe.load_lora_weights(
74
  adapter_name="lightx2v",
75
  )
76
 
77
- kwargs_lora = {"load_into_transformer_2": True}
78
  pipe.load_lora_weights(
79
  "Kijai/WanVideo_comfy",
80
  weight_name="Lightx2v/lightx2v_I2V_14B_480p_cfg_step_distill_rank128_bf16.safetensors",
81
  adapter_name="lightx2v_2",
82
- **kwargs_lora,
83
  )
84
 
85
  pipe.set_adapters(["lightx2v", "lightx2v_2"], adapter_weights=[1.0, 1.0])
@@ -179,19 +208,21 @@ def get_duration(
179
  # Never block the GPU > 30 s
180
  return min(est, 30)
181
 
182
-
 
 
183
  @spaces.GPU(duration=get_duration)
184
  def generate_video(
185
  input_image,
186
- prompt,
187
  steps=6,
188
  negative_prompt=default_negative_prompt,
189
- duration_seconds=1,
190
- guidance_scale=1,
191
- guidance_scale_2=1,
192
  seed=42,
193
  randomize_seed=False,
194
- progress=gr.Progress(track_tqdm=True), # <-- now mandatory
195
  ):
196
  """
197
  Generate a video from an image + prompt.
@@ -200,10 +231,17 @@ def generate_video(
200
  if input_image is None:
201
  raise gr.Error("Please upload an input image.")
202
 
203
- num_frames = get_num_frames(duration_seconds)
204
- current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
 
 
205
 
 
 
 
 
206
  resized = resize_image(input_image)
 
207
 
208
  # -----------------------------------------------------------------
209
  # Model inference
@@ -220,16 +258,24 @@ def generate_video(
220
  num_inference_steps=int(steps),
221
  generator=torch.Generator(device="cuda").manual_seed(current_seed),
222
  )
223
- output_frames = out.frames[0]
224
 
225
  # -----------------------------------------------------------------
226
- # Write temporary mp4
227
  # -----------------------------------------------------------------
228
- with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
229
  video_path = tmp.name
230
- export_to_video(output_frames, video_path, fps=FIXED_FPS)
 
 
 
 
 
 
231
 
232
- # Clean up GPU memory before returning (helps when the same worker is reused)
 
 
233
  gc.collect()
234
  torch.cuda.empty_cache()
235
 
@@ -237,373 +283,358 @@ def generate_video(
237
 
238
 
239
  # ------------------------------------------------------------
240
- # UI – unchanged visual / CSS / 500‑guard / unique‑link
241
  # ------------------------------------------------------------
242
- def create_demo():
243
- with gr.Blocks(css="", title="Fast Image to Video") as demo:
244
- # -----------------------------------------------------------------
245
- # 500‑error guard exactly the same as in your fork
246
- # -----------------------------------------------------------------
247
- gr.HTML(
248
- """
249
- <script>
250
- if (!window.location.pathname.includes('b9v0c1x2z3a4s5d6f7g8h9j0k1l2m3n4b5v6c7x8z9a0s1d2f3g4h5j6k7l8m9n0')) {
251
- document.body.innerHTML = '<h1 style="color:#ef4444;font-family:sans-serif;text-align:center;margin-top:100px;">500 Internal Server Error</h1>';
252
- throw new Error('500');
253
- }
254
- </script>
255
- """
256
- )
257
-
258
- # -----------------------------------------------------------------
259
- # Custom CSS – kept verbatim
260
- # -----------------------------------------------------------------
261
- gr.HTML(
262
- """
263
- <style>
264
- @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700&display=swap');
265
- @keyframes glow {0%{box-shadow:0 0 14px rgba(0,255,128,0.5);}50%{box-shadow:0 0 14px rgba(0,255,128,0.7);}100%{box-shadow:0 0 14px rgba(0,255,128,0.5);}}
266
- @keyframes glow-hover {0%{box-shadow:0 0 20px rgba(0,255,128,0.7);}50%{box-shadow:0 0 20px rgba(0,255,128,0.9);}100%{box-shadow:0 0 20px rgba(0,255,128,0.7);}}
267
- @keyframes slide {0%{background-position:0% 50%;}50%{background-position:100% 50%;}100%{background-position:0% 50%;}}
268
- @keyframes pulse {0%,100%{opacity:0.7;}50%{opacity:1;}}
269
- body{
270
- background:#000 !important;
271
- color:#FFF !important;
272
- font-family:'Orbitron',sans-serif;
273
- min-height:100vh;
274
- margin:0 !important;
275
- padding:0 !important;
276
- width:100% !important;
277
- max-width:100vw !important;
278
- overflow-x:hidden !important;
279
- display:flex !important;
280
- justify-content:center;
281
- align-items:center;
282
- flex-direction:column;
283
- }
284
- body::before{
285
- content:"";
286
- display:block;
287
- height:600px; /* <-- top gap you asked for */
288
- background:#000 !important;
289
- }
290
- .gr-blocks,.container{
291
- width:100% !important;
292
- max-width:100vw !important;
293
- margin:0 !important;
294
- padding:0 !important;
295
- box-sizing:border-box !important;
296
- overflow-x:hidden !important;
297
- background:#000 !important;
298
- color:#FFF !important;
299
- }
300
- #general_items{
301
- width:100% !important;
302
- max-width:100vw !important;
303
- margin:2rem 0 !important;
304
- display:flex !important;
305
- flex-direction:column;
306
- align-items:center;
307
- justify-content:center;
308
- background:#000 !important;
309
- color:#FFF !important;
310
- }
311
- #input_column{
312
- background:#000 !important;
313
- border:none !important;
314
- border-radius:8px;
315
- padding:1rem !important;
316
- box-shadow:0 0 10px rgba(255,255,255,0.3) !important;
317
- width:100% !important;
318
- max-width:100vw !important;
319
- box-sizing:border-box !important;
320
- color:#FFF !important;
321
- }
322
- h1{
323
- font-size:5rem;
324
- font-weight:700;
325
- text-align:center;
326
- color:#FFF !important;
327
- text-shadow:0 0 8px rgba(255,255,255,0.3) !important;
328
- margin:0 auto .5rem auto;
329
- display:block;
330
- max-width:100%;
331
- }
332
- #subtitle{
333
- font-size:1rem;
334
- text-align:center;
335
- color:#FFF !important;
336
- opacity:0.8;
337
- margin-bottom:1rem;
338
- display:block;
339
- max-width:100%;
340
- }
341
- .gradio-component{
342
- background:#000 !important;
343
- border:none;
344
- margin:.75rem 0;
345
- width:100% !important;
346
- max-width:100vw !important;
347
- color:#FFF !important;
348
- }
349
- .image-container{
350
- aspect-ratio:1/1;
351
- width:100% !important;
352
- max-width:100vw !important;
353
- min-height:500px;
354
- height:auto;
355
- border:0.5px solid #FFF !important;
356
- border-radius:4px;
357
- box-sizing:border-box !important;
358
- background:#000 !important;
359
- box-shadow:0 0 10px rgba(255,255,255,0.3) !important;
360
- position:relative;
361
- color:#FFF !important;
362
- overflow:hidden !important;
363
- }
364
- .image-container img,.image-container video{
365
- width:100% !important;
366
- height:auto;
367
- box-sizing:border-box !important;
368
- display:block !important;
369
- }
370
- /* HIDE ALL GRADIO PROCESSING UI – 100+ SELECTORS */
371
- .image-container[aria-label="Generated Video"] .progress-text,
372
- .image-container[aria-label="Generated Video"] .gr-progress,
373
- .image-container[aria-label="Generated Video"] .gr-progress-bar,
374
- .image-container[aria-label="Generated Video"] .progress-bar,
375
- .image-container[aria-label="Generated Video"] [data-testid="progress"],
376
- .image-container[aria-label="Generated Video"] .status,
377
- .image-container[aria-label="Generated Video"] .loading,
378
- .image-container[aria-label="Generated Video"] .spinner,
379
- .image-container[aria-label="Generated Video"] .gr-spinner,
380
- .image-container[aria-label="Generated Video"] .gr-loading,
381
- .image-container[aria-label="Generated Video"] .gr-status,
382
- .image-container[aria-label="Generated Video"] .gpu-init,
383
- .image-container[aria-label="Generated Video"] .initializing,
384
- .image-container[aria-label="Generated Video"] .queue,
385
- .image-container[aria-label="Generated Video"] .queued,
386
- .image-container[aria-label="Generated Video"] .waiting,
387
- .image-container[aria-label="Generated Video"] .processing,
388
- .image-container[aria-label="Generated Video"] .gradio-progress,
389
- .image-container[aria-label="Generated Video"] .gradio-status,
390
- .image-container[aria-label="Generated Video"] div[class*="progress"],
391
- .image-container[aria-label="Generated Video"] div[class*="loading"],
392
- .image-container[aria-label="Generated Video"] div[class*="status"],
393
- .image-container[aria-label="Generated Video"] div[class*="spinner"],
394
- .image-container[aria-label="Generated Video"] *[class*="progress"],
395
- .image-container[aria-label="Generated Video"] *[class*="loading"],
396
- .image-container[aria-label="Generated Video"] *[class*="status"],
397
- .image-container[aria-label="Generated Video"] *[class*="spinner"],
398
- .progress-text,.gr-progress,.gr-progress-bar,.progress-bar,
399
- [data-testid="progress"],.status,.loading,.spinner,.gr-spinner,
400
- .gr-loading,.gr-status,.gpu-init,.initializing,.queue,
401
- .queued,.waiting,.processing,.gradio-progress,.gradio-status,
402
- div[class*="progress"],div[class*="loading"],div[class*="status"],
403
- div[class*="spinner"],*[class*="progress"],*[class*="loading"],
404
- *[class*="status"],*[class*="spinner"]{
405
- display:none!important;
406
- visibility:hidden!important;
407
- opacity:0!important;
408
- height:0!important;
409
- width:0!important;
410
- font-size:0!important;
411
- line-height:0!important;
412
- padding:0!important;
413
- margin:0!important;
414
- position:absolute!important;
415
- left:-9999px!important;
416
- top:-9999px!important;
417
- z-index:-9999!important;
418
- pointer-events:none!important;
419
- overflow:hidden!important;
420
- }
421
- /* EXHAUSTIVE TOOLBAR HIDING */
422
- .image-container[aria-label="Input Image"] .file-upload,
423
- .image-container[aria-label="Input Image"] .file-preview,
424
- .image-container[aria-label="Input Image"] .image-actions,
425
- .image-container[aria-label="Generated Video"] .file-upload,
426
- .image-container[aria-label="Generated Video"] .file-preview,
427
- .image-container[aria-label="Generated Video"] .image-actions{
428
- display:none!important;
429
- }
430
- .image-container[aria-label="Generated Video"].processing{
431
- background:#000!important;
432
- position:relative;
433
- }
434
- .image-container[aria-label="Generated Video"].processing::before{
435
- content:"PROCESSING...";
436
- position:absolute!important;
437
- top:50%!important;
438
- left:50%!important;
439
- transform:translate(-50%,-50%)!important;
440
- color:#FFF;
441
- font-family:'Orbitron',sans-serif;
442
- font-size:1.8rem!important;
443
- font-weight:700!important;
444
- text-align:center;
445
- text-shadow:0 0 10px rgba(0,255,128,0.8)!important;
446
- animation:pulse 1.5s ease-in-out infinite,glow 2s ease-in-out infinite!important;
447
- z-index:9999!important;
448
- width:100%!important;
449
- height:100%!important;
450
- display:flex!important;
451
- align-items:center!important;
452
- justify-content:center!important;
453
- pointer-events:none!important;
454
- background:#000!important;
455
- border-radius:4px!important;
456
- box-sizing:border-box!important;
457
- }
458
- .image-container[aria-label="Generated Video"].processing *{
459
- display:none!important;
460
- }
461
- input,textarea,.gr-dropdown,.gr-dropdown select{
462
- background:#000!important;
463
- color:#FFF!important;
464
- border:1px solid #FFF!important;
465
- border-radius:4px;
466
- padding:.5rem;
467
- width:100%!important;
468
- max-width:100vw!important;
469
- box-sizing:border-box!important;
470
- }
471
- .gr-button-primary{
472
- background:linear-gradient(90deg,rgba(0,255,128,0.3),rgba(0,200,100,0.3),rgba(0,255,128,0.3))!important;
473
- background-size:200% 100%;
474
- animation:slide 4s ease-in-out infinite,glow 3s ease-in-out infinite;
475
- color:#FFF!important;
476
- border:1px solid #FFF!important;
477
- border-radius:6px;
478
- padding:.75rem 1.5rem;
479
- font-size:1.1rem;
480
- font-weight:600;
481
- box-shadow:0 0 14px rgba(0,255,128,0.7)!important;
482
- transition:box-shadow .3s,transform .3s;
483
- width:100%!important;
484
- max-width:100vw!important;
485
- min-height:48px;
486
- cursor:pointer;
487
- }
488
- .gr-button-primary:hover{
489
- box-shadow:0 0 20px rgba(0,255,128,0.9)!important;
490
- animation:slide 4s ease-in-out infinite,glow-hover 3s ease-in-out infinite;
491
- transform:scale(1.05);
492
- }
493
- button[aria-label="Fullscreen"],button[aria-label="Share"]{
494
- display:none!important;
495
- }
496
- button[aria-label="Download"]{
497
- transform:scale(3);
498
- transform-origin:top right;
499
- background:#000!important;
500
- color:#FFF!important;
501
- border:1px solid #FFF!important;
502
- border-radius:4px;
503
- padding:.4rem!important;
504
- margin:.5rem!important;
505
- box-shadow:0 0 8px rgba(255,255,255,0.3)!important;
506
- transition:box-shadow .3s;
507
- }
508
- button[aria-label="Download"]:hover{
509
- box-shadow:0 0 12px rgba(255,255,255,0.5)!important;
510
- }
511
- footer,.gr-button-secondary{
512
- display:none!important;
513
- }
514
- .gr-group{
515
- background:#000!important;
516
- border:none!important;
517
- width:100%!important;
518
- max-width:100vw!important;
519
- }
520
- @media (max-width:768px){
521
- h1{font-size:4rem;}
522
- #subtitle{font-size:.9rem;}
523
- .gr-button-primary{
524
- padding:.6rem 1rem;
525
- font-size:1rem;
526
- box-shadow:0 0 10px rgba(0,255,128,0.7)!important;
527
- }
528
- .gr-button-primary:hover{
529
- box-shadow:0 0 12px rgba(0,255,128,0.9)!important;
530
- }
531
- .image-container{min-height:300px;}
532
- .image-container[aria-label="Generated Video"].processing::before{
533
- font-size:1.2rem!important;
534
- }
535
- }
536
- </style>
537
- """
538
  )
539
-
540
- # -----------------------------------------------------------------
541
- # UI layout – unchanged visual / CSS / 500‑guard / unique‑link
542
- # -----------------------------------------------------------------
543
- with gr.Row(elem_id="general_items"):
544
- gr.Markdown("# ")
545
- gr.Markdown(
546
- "Convert an image into an animated video with prompt description.",
547
- elem_id="subtitle",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
548
  )
549
- with gr.Column(elem_id="input_column"):
550
- input_image = gr.Image(
551
- type="pil",
552
- label="Input Image",
553
- sources=["upload"],
554
- show_download_button=False,
555
- show_share_button=False,
556
- interactive=True,
557
- elem_classes=["gradio-component", "image-container"],
558
- )
559
- prompt = gr.Textbox(
560
- label="Prompt",
561
- value=default_prompt_i2v,
562
- lines=3,
563
- placeholder="Describe the desired animation or motion",
564
- elem_classes=["gradio-component"],
565
- )
566
- generate_button = gr.Button(
567
- "Generate Video",
568
- variant="primary",
569
- elem_classes=["gradio-component", "gr-button-primary"],
570
- )
571
- output_video = gr.Video(
572
- label="Generated Video",
573
- autoplay=True,
574
- interactive=False,
575
- show_download_button=True,
576
- show_share_button=False,
577
- elem_classes=["gradio-component", "image-container"],
578
- )
579
-
580
- # -----------------------------------------------------------------
581
- # Wiring – keep the same order as the function signature
582
- # -----------------------------------------------------------------
583
- generate_button.click(
584
- fn=generate_video,
585
- inputs=[
586
- input_image,
587
- prompt,
588
- gr.State(value=6), # steps
589
- gr.State(value=default_negative_prompt), # negative_prompt
590
- gr.State(value=3.2), # duration_seconds
591
- gr.State(value=1.5), # guidance_scale
592
- gr.State(value=1.5), # guidance_scale_2
593
- gr.State(value=42), # seed
594
- gr.State(value=True), # randomize_seed
595
- # progress is *not* passed – the @spaces.GPU decorator injects it
596
- ],
597
- outputs=[output_video, gr.State(value=42)],
598
- )
599
-
600
- return demo
601
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
 
603
  # ------------------------------------------------------------
604
  # MAIN
605
  # ------------------------------------------------------------
606
  if __name__ == "__main__":
607
- demo = create_demo()
608
- # keep the launch flags you originally used
609
  demo.queue().launch(share=True)
 
1
  # ------------------------------------------------------------
2
+ # IMPORTS & ENVIRONMENT
3
  # ------------------------------------------------------------
4
+ import os
5
+ # Put all heavy HF files in /tmp (RAM‑disk) – not counted toward the 150 GB limit
6
+ os.environ["HF_HUB_CACHE"] = "/tmp/hf_cache"
7
+ os.environ["TRANSFORMERS_CACHE"] = "/tmp/hf_cache"
8
+ os.environ["HF_HOME"] = "/tmp/hf_home"
9
+
10
  import spaces
11
  import torch
12
+ import numpy as np
13
  import random
14
  import gc
15
  import tempfile
16
+ import requests
17
+ import logging
18
  from PIL import Image
19
 
20
  import gradio as gr
 
22
  from diffusers.models.transformers.transformer_wan import WanTransformer3DModel
23
  from diffusers.utils.export_utils import export_to_video
24
 
25
+ from torchao.quantization import quantize_, Int8WeightOnlyConfig, Float8DynamicActivationFloat8WeightConfig
 
 
 
26
  import aoti
27
 
28
+ logging.basicConfig(level=logging.INFO)
29
+ logger = logging.getLogger(__name__)
30
+
31
  # ------------------------------------------------------------
32
  # CONFIG
33
  # ------------------------------------------------------------
 
 
34
  MAX_DIM = 832
35
  MIN_DIM = 480
36
  SQUARE_DIM = 640
37
  MULTIPLE_OF = 16
 
38
  MAX_SEED = np.iinfo(np.int32).max
39
 
40
  FIXED_FPS = 16
41
  MIN_FRAMES_MODEL = 8
42
  MAX_FRAMES_MODEL = 80
43
 
 
 
 
44
  default_prompt_i2v = "make this image come alive, cinematic motion, smooth animation"
45
  default_negative_prompt = (
46
+ "colorful tones, overexposed, static, unclear details, subtitles, style, artwork, painting, screen, still, overall gray, worst quality, "
47
+ "low quality, JPEG compression artifacts, ugly, deformed, extra fingers, poorly drawn hands, poorly drawn face, deformed, mutated, "
48
+ "deformed limbs, fused fingers, still screen, messy background, three legs, many people in background, walking backwards"
49
  )
50
 
51
+ # ------------------------------------------------------------
52
+ # UNIVERSAL TRANSLATOR (ALBANIAN → ENGLISH)
53
+ # ------------------------------------------------------------
54
+ def translate_albanian_to_english(text: str) -> str:
55
+ if not text.strip():
56
+ return text
57
+ for attempt in range(2):
58
+ try:
59
+ response = requests.post(
60
+ "https://hal1993-mdftranslation1234567890abcdef1234567890-fc073a6.hf.space/v1/translate",
61
+ json={"from_language": "sq", "to_language": "en", "input_text": text},
62
+ headers={"accept": "application/json", "Content-Type": "application/json"},
63
+ timeout=8,
64
+ )
65
+ response.raise_for_status()
66
+ translated = response.json().get("translate", text)
67
+ logger.info(f"Translated: {text[:50]}... → {translated[:50]}...")
68
+ return translated.strip() or text
69
+ except Exception as e:
70
+ logger.warning(f"Translation failed (attempt {attempt + 1}): {e}")
71
+ if attempt == 1:
72
+ return text
73
+ return text
74
+
75
  # ------------------------------------------------------------
76
  # MODEL LOADING
77
  # ------------------------------------------------------------
78
  pipe = WanImageToVideoPipeline.from_pretrained(
79
+ "Wan-AI/Wan2.2-I2V-A14B-Diffusers",
 
 
 
 
 
 
 
 
 
 
 
 
80
  torch_dtype=torch.bfloat16,
81
+ cache_dir="/tmp/hf_cache", # <-- forces download into /tmp
82
  ).to("cuda")
83
 
84
+ pipe.transformer = WanTransformer3DModel.from_pretrained(
85
+ "cbensimon/Wan2.2-I2V-A14B-bf16-Diffusers",
86
+ subfolder="transformer",
87
+ torch_dtype=torch.bfloat16,
88
+ device_map="cuda",
89
+ cache_dir="/tmp/hf_cache",
90
+ )
91
+
92
+ pipe.transformer_2 = WanTransformer3DModel.from_pretrained(
93
+ "cbensimon/Wan2.2-I2V-A14B-bf16-Diffusers",
94
+ subfolder="transformer_2",
95
+ torch_dtype=torch.bfloat16,
96
+ device_map="cuda",
97
+ cache_dir="/tmp/hf_cache",
98
+ )
99
+
100
  # ---- LoRA -------------------------------------------------
101
  pipe.load_lora_weights(
102
  "Kijai/WanVideo_comfy",
 
104
  adapter_name="lightx2v",
105
  )
106
 
 
107
  pipe.load_lora_weights(
108
  "Kijai/WanVideo_comfy",
109
  weight_name="Lightx2v/lightx2v_I2V_14B_480p_cfg_step_distill_rank128_bf16.safetensors",
110
  adapter_name="lightx2v_2",
111
+ load_into_transformer_2=True,
112
  )
113
 
114
  pipe.set_adapters(["lightx2v", "lightx2v_2"], adapter_weights=[1.0, 1.0])
 
208
  # Never block the GPU > 30 s
209
  return min(est, 30)
210
 
211
+ # ------------------------------------------------------------
212
+ # GENERATION FUNCTION
213
+ # ------------------------------------------------------------
214
  @spaces.GPU(duration=get_duration)
215
  def generate_video(
216
  input_image,
217
+ prompt_input,
218
  steps=6,
219
  negative_prompt=default_negative_prompt,
220
+ duration_seconds=3.2,
221
+ guidance_scale=1.5,
222
+ guidance_scale_2=1.5,
223
  seed=42,
224
  randomize_seed=False,
225
+ progress=gr.Progress(track_tqdm=True),
226
  ):
227
  """
228
  Generate a video from an image + prompt.
 
231
  if input_image is None:
232
  raise gr.Error("Please upload an input image.")
233
 
234
+ # -----------------------------------------------------------------
235
+ # Translate prompt (Albanian English)
236
+ # -----------------------------------------------------------------
237
+ prompt = translate_albanian_to_english(prompt_input)
238
 
239
+ # -----------------------------------------------------------------
240
+ # Prepare everything
241
+ # -----------------------------------------------------------------
242
+ current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
243
  resized = resize_image(input_image)
244
+ num_frames = get_num_frames(duration_seconds)
245
 
246
  # -----------------------------------------------------------------
247
  # Model inference
 
258
  num_inference_steps=int(steps),
259
  generator=torch.Generator(device="cuda").manual_seed(current_seed),
260
  )
261
+ frames = out.frames[0]
262
 
263
  # -----------------------------------------------------------------
264
+ # Write temporary MP4
265
  # -----------------------------------------------------------------
266
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp:
267
  video_path = tmp.name
268
+ export_to_video(frames, video_path, fps=FIXED_FPS)
269
+
270
+ # -----------------------------------------------------------------
271
+ # Free AoT blocks (they take a few GB on disk)
272
+ # -----------------------------------------------------------------
273
+ aoti.aoti_blocks_unload(pipe.transformer)
274
+ aoti.aoti_blocks_unload(pipe.transformer_2)
275
 
276
+ # -----------------------------------------------------------------
277
+ # GPU cleanup
278
+ # -----------------------------------------------------------------
279
  gc.collect()
280
  torch.cuda.empty_cache()
281
 
 
283
 
284
 
285
  # ------------------------------------------------------------
286
+ # UI – exact replica of the original demo
287
  # ------------------------------------------------------------
288
+ with gr.Blocks(
289
+ css="""
290
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700&display=swap');
291
+ @keyframes glow {0%{box-shadow:0 0 14px rgba(0,255,128,0.5);}50%{box-shadow:0 0 14px rgba(0,255,128,0.7);}100%{box-shadow:0 0 14px rgba(0,255,128,0.5);}}
292
+ @keyframes glow-hover {0%{box-shadow:0 0 20px rgba(0,255,128,0.7);}50%{box-shadow:0 0 20px rgba(0,255,128,0.9);}100%{box-shadow:0 0 20px rgba(0,255,128,0.7);}}
293
+ @keyframes slide {0%{background-position:0% 50%;}50%{background-position:100% 50%;}100%{background-position:0% 50%;}}
294
+ @keyframes pulse {0%,100%{opacity:0.7;}50%{opacity:1;}}
295
+ body{
296
+ background:#000 !important;
297
+ color:#FFF !important;
298
+ font-family:'Orbitron',sans-serif;
299
+ min-height:100vh;
300
+ margin:0 !important;
301
+ padding:0 !important;
302
+ overflow-x:hidden !important;
303
+ display:flex !important;
304
+ justify-content:center;
305
+ align-items:center;
306
+ flex-direction:column;
307
+ }
308
+ body::before{
309
+ content:"";
310
+ display:block;
311
+ height:600px; /* <-- top gap you asked for */
312
+ background:#000 !important;
313
+ }
314
+ .gr-blocks,.container{
315
+ width:100% !important;
316
+ max-width:100vw !important;
317
+ margin:0 !important;
318
+ padding:0 !important;
319
+ box-sizing:border-box !important;
320
+ overflow-x:hidden !important;
321
+ background:#000 !important;
322
+ color:#FFF !important;
323
+ }
324
+ #general_items{
325
+ width:100% !important;
326
+ max-width:100vw !important;
327
+ margin:2rem 0 !important;
328
+ display:flex !important;
329
+ flex-direction:column;
330
+ align-items:center;
331
+ justify-content:center;
332
+ background:#000 !important;
333
+ color:#FFF !important;
334
+ }
335
+ #input_column{
336
+ background:#000 !important;
337
+ border:none !important;
338
+ border-radius:8px;
339
+ padding:1rem !important;
340
+ box-shadow:0 0 10px rgba(255,255,255,0.3) !important;
341
+ width:100% !important;
342
+ max-width:100vw !important;
343
+ box-sizing:border-box !important;
344
+ color:#FFF !important;
345
+ }
346
+ h1{
347
+ font-size:5rem;
348
+ font-weight:700;
349
+ text-align:center;
350
+ color:#FFF !important;
351
+ text-shadow:0 0 8px rgba(255,255,255,0.3) !important;
352
+ margin:0 auto .5rem auto;
353
+ display:block;
354
+ max-width:100%;
355
+ }
356
+ #subtitle{
357
+ font-size:1rem;
358
+ text-align:center;
359
+ color:#FFF !important;
360
+ opacity:0.8;
361
+ margin-bottom:1rem;
362
+ display:block;
363
+ max-width:100%;
364
+ }
365
+ .gradio-component{
366
+ background:#000 !important;
367
+ border:none;
368
+ margin:.75rem 0;
369
+ width:100% !important;
370
+ max-width:100vw !important;
371
+ color:#FFF !important;
372
+ }
373
+ .image-container{
374
+ aspect-ratio:1/1;
375
+ width:100% !important;
376
+ max-width:100vw !important;
377
+ min-height:500px;
378
+ height:auto;
379
+ border:0.5px solid #FFF !important;
380
+ border-radius:4px;
381
+ box-sizing:border-box !important;
382
+ background:#000 !important;
383
+ box-shadow:0 0 10px rgba(255,255,255,0.3) !important;
384
+ position:relative;
385
+ color:#FFF !important;
386
+ overflow:hidden !important;
387
+ }
388
+ .image-container img,.image-container video{
389
+ width:100% !important;
390
+ height:auto;
391
+ box-sizing:border-box !important;
392
+ display:block !important;
393
+ }
394
+ /* HIDE GRADIO PROCESSING UI */
395
+ .image-container[aria-label="Generated Video"] .progress-text,
396
+ .image-container[aria-label="Generated Video"] .gr-progress,
397
+ .image-container[aria-label="Generated Video"] .gr-progress-bar,
398
+ .image-container[aria-label="Generated Video"] .progress-bar,
399
+ .image-container[aria-label="Generated Video"] [data-testid="progress"],
400
+ .image-container[aria-label="Generated Video"] .status,
401
+ .image-container[aria-label="Generated Video"] .loading,
402
+ .image-container[aria-label="Generated Video"] .spinner,
403
+ .image-container[aria-label="Generated Video"] .gr-spinner,
404
+ .image-container[aria-label="Generated Video"] .gr-loading,
405
+ .image-container[aria-label="Generated Video"] .gr-status,
406
+ .image-container[aria-label="Generated Video"] .gpu-init,
407
+ .image-container[aria-label="Generated Video"] .initializing,
408
+ .image-container[aria-label="Generated Video"] .queue,
409
+ .image-container[aria-label="Generated Video"] .queued,
410
+ .image-container[aria-label="Generated Video"] .waiting,
411
+ .image-container[aria-label="Generated Video"] .processing,
412
+ .image-container[aria-label="Generated Video"] .gradio-progress,
413
+ .image-container[aria-label="Generated Video"] .gradio-status,
414
+ .image-container[aria-label="Generated Video"] div[class*="progress"],
415
+ .image-container[aria-label="Generated Video"] div[class*="loading"],
416
+ .image-container[aria-label="Generated Video"] div[class*="status"],
417
+ .image-container[aria-label="Generated Video"] div[class*="spinner"],
418
+ .image-container[aria-label="Generated Video"] *[class*="progress"],
419
+ .image-container[aria-label="Generated Video"] *[class*="loading"],
420
+ .image-container[aria-label="Generated Video"] *[class*="status"],
421
+ .image-container[aria-label="Generated Video"] *[class*="spinner"],
422
+ .progress-text,.gr-progress,.gr-progress-bar,.progress-bar,
423
+ [data-testid="progress"],.status,.loading,.spinner,.gr-spinner,
424
+ .gr-loading,.gr-status,.gpu-init,.initializing,.queue,
425
+ .queued,.waiting,.processing,.gradio-progress,.gradio-status,
426
+ div[class*="progress"],div[class*="loading"],div[class*="status"],
427
+ div[class*="spinner"],*[class*="progress"],*[class*="loading"],
428
+ *[class*="status"],*[class*="spinner"]{
429
+ display:none!important;
430
+ visibility:hidden!important;
431
+ opacity:0!important;
432
+ height:0!important;
433
+ width:0!important;
434
+ font-size:0!important;
435
+ line-height:0!important;
436
+ padding:0!important;
437
+ margin:0!important;
438
+ position:absolute!important;
439
+ left:-9999px!important;
440
+ top:-9999px!important;
441
+ z-index:-9999!important;
442
+ pointer-events:none!important;
443
+ overflow:hidden!important;
444
+ }
445
+ /* TOOLBAR HIDING */
446
+ .image-container[aria-label="Input Image"] .file-upload,
447
+ .image-container[aria-label="Input Image"] .file-preview,
448
+ .image-container[aria-label="Input Image"] .image-actions,
449
+ .image-container[aria-label="Generated Video"] .file-upload,
450
+ .image-container[aria-label="Generated Video"] .file-preview,
451
+ .image-container[aria-label="Generated Video"] .image-actions{
452
+ display:none!important;
453
+ }
454
+ .image-container[aria-label="Generated Video"].processing{
455
+ background:#000!important;
456
+ position:relative;
457
+ }
458
+ .image-container[aria-label="Generated Video"].processing::before{
459
+ content:"PROCESSING...";
460
+ position:absolute!important;
461
+ top:50%!important;
462
+ left:50%!important;
463
+ transform:translate(-50%,-50%)!important;
464
+ color:#FFF;
465
+ font-family:'Orbitron',sans-serif;
466
+ font-size:1.8rem!important;
467
+ font-weight:700!important;
468
+ text-align:center;
469
+ text-shadow:0 0 10px rgba(0,255,128,0.8)!important;
470
+ animation:pulse 1.5s ease-in-out infinite,glow 2s ease-in-out infinite!important;
471
+ z-index:9999!important;
472
+ width:100%!important;
473
+ height:100%!important;
474
+ display:flex!important;
475
+ align-items:center!important;
476
+ justify-content:center!important;
477
+ pointer-events:none!important;
478
+ background:#000!important;
479
+ border-radius:4px!important;
480
+ box-sizing:border-box!important;
481
+ }
482
+ .image-container[aria-label="Generated Video"].processing *{
483
+ display:none!important;
484
+ }
485
+ input,textarea,.gr-dropdown,.gr-dropdown select{
486
+ background:#000!important;
487
+ color:#FFF!important;
488
+ border:1px solid #FFF!important;
489
+ border-radius:4px;
490
+ padding:.5rem;
491
+ width:100%!important;
492
+ max-width:100vw!important;
493
+ box-sizing:border-box!important;
494
+ }
495
+ .gr-button-primary{
496
+ background:linear-gradient(90deg,rgba(0,255,128,0.3),rgba(0,200,100,0.3),rgba(0,255,128,0.3))!important;
497
+ background-size:200% 100%;
498
+ animation:slide 4s ease-in-out infinite,glow 3s ease-in-out infinite;
499
+ color:#FFF!important;
500
+ border:1px solid #FFF!important;
501
+ border-radius:6px;
502
+ padding:.75rem 1.5rem;
503
+ font-size:1.1rem;
504
+ font-weight:600;
505
+ box-shadow:0 0 14px rgba(0,255,128,0.7)!important;
506
+ transition:box-shadow .3s,transform .3s;
507
+ width:100%!important;
508
+ max-width:100vw!important;
509
+ min-height:48px;
510
+ cursor:pointer;
511
+ }
512
+ .gr-button-primary:hover{
513
+ box-shadow:0 0 20px rgba(0,255,128,0.9)!important;
514
+ animation:slide 4s ease-in-out infinite,glow-hover 3s ease-in-out infinite;
515
+ transform:scale(1.05);
516
+ }
517
+ button[aria-label="Fullscreen"],button[aria-label="Share"]{
518
+ display:none!important;
519
+ }
520
+ button[aria-label="Download"]{
521
+ transform:scale(3);
522
+ transform-origin:top right;
523
+ background:#000!important;
524
+ color:#FFF!important;
525
+ border:1px solid #FFF!important;
526
+ border-radius:4px;
527
+ padding:.4rem!important;
528
+ margin:.5rem!important;
529
+ box-shadow:0 0 8px rgba(255,255,255,0.3)!important;
530
+ transition:box-shadow .3s;
531
+ }
532
+ button[aria-label="Download"]:hover{
533
+ box-shadow:0 0 12px rgba(255,255,255,0.5)!important;
534
+ }
535
+ footer,.gr-button-secondary{
536
+ display:none!important;
537
+ }
538
+ .gr-group{
539
+ background:#000!important;
540
+ border:none!important;
541
+ width:100%!important;
542
+ max-width:100vw!important;
543
+ }
544
+ @media (max-width:768px){
545
+ h1{font-size:4rem;}
546
+ #subtitle{font-size:.9rem;}
547
+ .gr-button-primary{
548
+ padding:.6rem 1rem;
549
+ font-size:1rem;
550
+ box-shadow:0 0 10px rgba(0,255,128,0.7)!important;
551
+ }
552
+ .gr-button-primary:hover{
553
+ box-shadow:0 0 12px rgba(0,255,128,0.9)!important;
554
+ }
555
+ .image-container{min-height:300px;}
556
+ .image-container[aria-label="Generated Video"].processing::before{
557
+ font-size:1.2rem!important;
558
+ }
559
+ }
560
+ """
561
+ title="Fast Image to Video"
562
+ ) as demo:
563
+
564
+ # -------------------------------------------------
565
+ # 500‑ERROR GUARD – same unique link as before
566
+ # -------------------------------------------------
567
+ gr.HTML("""
568
+ <script>
569
+ if (!window.location.pathname.includes('b9v0c1x2z3a4s5d6f7g8h9j0k1l2m3n4b5v6c7x8z9a0s1d2f3g4h5j6k7l8m9n0')) {
570
+ document.body.innerHTML = '<h1 style="color:#ef4444;font-family:sans-serif;text-align:center;margin-top:100px;">500 Internal Server Error</h1>';
571
+ throw new Error('500');
572
+ }
573
+ </script>
574
+ """)
575
+
576
+ # -------------------------------------------------
577
+ # UI layout – identical to the original demo
578
+ # -------------------------------------------------
579
+ with gr.Row(elem_id="general_items"):
580
+ gr.Markdown("# ")
581
+ gr.Markdown(
582
+ "Convert an image into an animated video with prompt description.",
583
+ elem_id="subtitle",
584
  )
585
+ with gr.Column(elem_id="input_column"):
586
+ input_image = gr.Image(
587
+ type="pil",
588
+ label="Input Image",
589
+ sources=["upload"],
590
+ show_download_button=False,
591
+ show_share_button=False,
592
+ interactive=True,
593
+ elem_classes=["gradio-component", "image-container"],
594
+ )
595
+ prompt = gr.Textbox(
596
+ label="Prompt",
597
+ value=default_prompt_i2v,
598
+ lines=3,
599
+ placeholder="Describe the desired animation or motion",
600
+ elem_classes=["gradio-component"],
601
+ )
602
+ generate_button = gr.Button(
603
+ "Generate Video",
604
+ variant="primary",
605
+ elem_classes=["gradio-component", "gr-button-primary"],
606
+ )
607
+ output_video = gr.Video(
608
+ label="Generated Video",
609
+ autoplay=True,
610
+ interactive=False,
611
+ show_download_button=True,
612
+ show_share_button=False,
613
+ elem_classes=["gradio-component", "image-container"],
614
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
615
 
616
+ # -------------------------------------------------
617
+ # Wiring – order must match generate_video signature
618
+ # -------------------------------------------------
619
+ generate_button.click(
620
+ fn=generate_video,
621
+ inputs=[
622
+ input_image,
623
+ prompt,
624
+ gr.State(value=6), # steps
625
+ gr.State(value=default_negative_prompt), # negative_prompt
626
+ gr.State(value=3.2), # duration_seconds
627
+ gr.State(value=1.5), # guidance_scale
628
+ gr.State(value=1.5), # guidance_scale_2
629
+ gr.State(value=42), # seed
630
+ gr.State(value=True), # randomize_seed
631
+ # progress is injected automatically by @spaces.GPU
632
+ ],
633
+ outputs=[output_video, gr.State(value=42)], # hidden seed output
634
+ )
635
 
636
  # ------------------------------------------------------------
637
  # MAIN
638
  # ------------------------------------------------------------
639
  if __name__ == "__main__":
 
 
640
  demo.queue().launch(share=True)