seawolf2357 commited on
Commit
f00f427
·
verified ·
1 Parent(s): 16a9443

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +194 -159
app.py CHANGED
@@ -1,57 +1,91 @@
1
- import gradio as gr
2
- import numpy as np
3
- import random
4
- import torch
5
- import spaces
6
  import os
 
7
 
8
- from PIL import Image
9
- from diffusers.pipelines.wan.pipeline_wan import WanPipeline
 
 
 
10
  from diffusers.utils.export_utils import export_to_video
11
-
12
  import tempfile
13
- from typing import Optional, Tuple, Any
14
- from groq import Groq
 
 
 
15
 
16
- # =========================================================
17
- # CUDA BACKEND FIX
18
- # =========================================================
19
- # Fix for cusolver error
20
- torch.backends.cuda.preferred_linalg_library("cusolver")
21
 
22
  # =========================================================
23
  # API CONFIGURATION
24
  # =========================================================
25
  GROQ_API_KEY = os.getenv("GROQ_API_KEY")
26
- MODEL_ID = os.getenv("MODEL_ID")
27
- HF_TOKEN = os.getenv("HF_TOKEN")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
  # =========================================================
30
  # MODEL LOADING
31
  # =========================================================
32
- dtype = torch.bfloat16
33
- device = "cuda" if torch.cuda.is_available() else "cpu"
 
 
 
34
 
35
- print("Loading pipeline...")
36
  pipe = WanPipeline.from_pretrained(
37
  MODEL_ID,
38
- torch_dtype=dtype,
39
- token=HF_TOKEN
40
- ).to(device)
41
-
42
- MAX_SEED = np.iinfo(np.int32).max
43
- FIXED_FPS = 24
44
- MIN_FRAMES = 9 # Must be (4k + 1) format: 9, 13, 17, 21, 25, ...
45
- MAX_FRAMES = 81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  # =========================================================
48
  # DEFAULT PROMPTS
49
  # =========================================================
50
- default_prompt = "A beautiful sunset over the ocean with gentle waves."
51
- default_negative_prompt = "low quality, worst quality, blurry, distorted, deformed, ugly, bad anatomy"
52
 
53
  # =========================================================
54
- # PROMPT ENHANCEMENT SYSTEM PROMPT
55
  # =========================================================
56
  ENHANCE_SYSTEM_PROMPT = """You are a professional video prompt engineer. Your task is to enhance user prompts for AI video generation.
57
 
@@ -69,29 +103,21 @@ Enhanced: "A fluffy orange tabby cat playfully batting at a dangling yarn ball,
69
  """
70
 
71
 
72
- # =========================================================
73
- # PROMPT ENHANCEMENT FUNCTION
74
- # =========================================================
75
  def enhance_prompt(prompt: str) -> str:
76
  """Enhance the user prompt using Groq LLM API."""
77
  if not GROQ_API_KEY:
78
  return prompt
79
 
80
  try:
 
81
  client = Groq(api_key=GROQ_API_KEY)
82
 
83
  enhanced_text = ""
84
  completion = client.chat.completions.create(
85
  model="meta-llama/llama-4-scout-17b-16e-instruct",
86
  messages=[
87
- {
88
- "role": "system",
89
- "content": ENHANCE_SYSTEM_PROMPT
90
- },
91
- {
92
- "role": "user",
93
- "content": f"Enhance this video generation prompt: {prompt}"
94
- }
95
  ],
96
  temperature=0.7,
97
  max_completion_tokens=512,
@@ -112,95 +138,80 @@ def enhance_prompt(prompt: str) -> str:
112
 
113
 
114
  # =========================================================
115
- # HELPER FUNCTIONS
116
- # =========================================================
117
- def get_num_frames(duration_seconds: float) -> int:
118
- """
119
- Calculate number of frames based on duration.
120
- num_frames - 1 must be divisible by 4, so num_frames must be 4k + 1
121
- Valid values: 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81
122
- """
123
- raw_frames = int(duration_seconds * FIXED_FPS)
124
- # Round to nearest valid frame count (4k + 1)
125
- k = round((raw_frames - 1) / 4)
126
- num_frames = 4 * k + 1
127
- # Clamp to valid range
128
- num_frames = max(MIN_FRAMES, min(MAX_FRAMES, num_frames))
129
- return num_frames
130
-
131
-
132
  # =========================================================
133
- # MAIN GENERATION FUNCTION
134
- # =========================================================
135
- @spaces.GPU
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  def generate_video(
137
- prompt: str,
138
- negative_prompt: str,
139
- enhance_prompt_option: bool,
140
- duration_seconds: float,
141
- guidance_scale: float,
142
- num_inference_steps: int,
143
- height: int,
144
- width: int,
145
- seed: int,
146
- randomize_seed: bool,
147
- progress: gr.Progress = gr.Progress(track_tqdm=True),
148
- ) -> Tuple[str, int, str, str]:
149
- """Generate video from text prompt."""
150
-
151
- if not prompt.strip():
152
- raise gr.Error("Please enter a prompt.")
153
-
154
  # Enhance prompt if option is enabled
155
  final_prompt = prompt
156
  if enhance_prompt_option:
157
  final_prompt = enhance_prompt(prompt)
158
  print(f"Enhanced Prompt: {final_prompt}")
159
 
160
- # Handle seed
161
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
162
- generator = torch.Generator(device=device).manual_seed(current_seed)
163
-
164
- # Calculate frames (must be 4k + 1)
165
- num_frames = get_num_frames(duration_seconds)
166
- actual_duration = num_frames / FIXED_FPS
167
-
168
- print(f"Generating video with {num_frames} frames ({actual_duration:.2f}s)")
169
-
170
- # Generate video
171
- output_frames = pipe(
172
  prompt=final_prompt,
173
  negative_prompt=negative_prompt,
174
- height=height,
175
- width=width,
176
  num_frames=num_frames,
177
- guidance_scale=guidance_scale,
178
- num_inference_steps=num_inference_steps,
179
- generator=generator,
 
180
  ).frames[0]
181
-
182
- # Export to video file
183
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
184
  video_path = tmpfile.name
185
- export_to_video(output_frames, video_path, fps=FIXED_FPS)
186
-
 
187
  # Build info log
 
188
  info_log = f"""✅ VIDEO GENERATION COMPLETE!
189
  {'=' * 50}
190
  🎬 Video Info:
191
  • Duration: {actual_duration:.2f} seconds
192
  • Total Frames: {num_frames}
193
  • FPS: {FIXED_FPS}
194
- • Resolution: {width} x {height}
195
  {'=' * 50}
196
  ⚙️ Generation Settings:
197
- • Guidance Scale: {guidance_scale}
198
- • Inference Steps: {num_inference_steps}
199
  • Seed: {current_seed}
200
  • Prompt Enhanced: {'Yes' if enhance_prompt_option else 'No'}
201
  {'=' * 50}
202
  💾 Ready to download!"""
203
-
204
  return video_path, current_seed, final_prompt, info_log
205
 
206
 
@@ -538,6 +549,20 @@ a:hover {
538
  gap: 1rem !important;
539
  }
540
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  /* ===== 반응형 조정 ===== */
542
  @media (max-width: 768px) {
543
  .header-text h1 {
@@ -595,7 +620,7 @@ with gr.Blocks() as demo:
595
  with gr.Column(scale=1, min_width=320):
596
  prompt_input = gr.Textbox(
597
  label="✏️ Your Prompt",
598
- value=default_prompt,
599
  placeholder="Describe the video you want to create...",
600
  lines=4
601
  )
@@ -606,15 +631,16 @@ with gr.Blocks() as demo:
606
  info="Use AI to automatically enhance your prompt for better results"
607
  )
608
 
609
- duration_slider = gr.Slider(
 
 
 
 
610
  label="⏱️ Duration (seconds)",
611
- minimum=0.5,
612
- maximum=3.5,
613
- step=0.5,
614
- value=2.0
615
  )
616
 
617
- generate_btn = gr.Button(
618
  "🎬 GENERATE VIDEO! 🚀",
619
  variant="primary",
620
  size="lg",
@@ -625,46 +651,41 @@ with gr.Blocks() as demo:
625
  negative_prompt_input = gr.Textbox(
626
  label="Negative Prompt",
627
  value=default_negative_prompt,
628
- lines=2
629
- )
630
- guidance_scale_slider = gr.Slider(
631
- label="Guidance Scale",
632
- minimum=1.0,
633
- maximum=15.0,
634
- step=0.5,
635
- value=7.5
636
- )
637
- num_inference_steps_slider = gr.Slider(
638
- label="Inference Steps",
639
- minimum=10,
640
- maximum=50,
641
- step=1,
642
- value=20
643
- )
644
- height_slider = gr.Slider(
645
- label="Height",
646
- minimum=256,
647
- maximum=720,
648
- step=16,
649
- value=480
650
- )
651
- width_slider = gr.Slider(
652
- label="Width",
653
- minimum=256,
654
- maximum=1280,
655
- step=16,
656
- value=832
657
  )
658
- seed_slider = gr.Slider(
659
  label="Seed",
660
  minimum=0,
661
  maximum=MAX_SEED,
662
  step=1,
663
- value=0
 
664
  )
665
  randomize_seed_checkbox = gr.Checkbox(
666
  label="Randomize Seed",
667
- value=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
668
  )
669
 
670
  with gr.Accordion("📜 Generation Info", open=True):
@@ -682,6 +703,7 @@ with gr.Blocks() as demo:
682
  video_output = gr.Video(
683
  label="🎥 Generated Video",
684
  autoplay=True,
 
685
  height=400,
686
  elem_classes="video-output"
687
  )
@@ -699,33 +721,46 @@ with gr.Blocks() as demo:
699
  </p>
700
  """
701
  )
702
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
  # Define inputs and outputs
704
- inputs = [
705
  prompt_input,
706
  negative_prompt_input,
707
  enhance_prompt_checkbox,
708
- duration_slider,
709
- guidance_scale_slider,
710
- num_inference_steps_slider,
711
- height_slider,
712
- width_slider,
713
- seed_slider,
714
- randomize_seed_checkbox,
715
  ]
716
 
717
- outputs = [
718
  video_output,
719
- seed_slider,
720
  final_prompt_output,
721
- info_log,
722
  ]
723
-
724
- # Generate button click
725
- generate_btn.click(
726
  fn=generate_video,
727
- inputs=inputs,
728
- outputs=outputs,
729
  )
730
 
731
  if __name__ == "__main__":
 
1
+ # PyTorch 2.8 (temporary hack)
 
 
 
 
2
  import os
3
+ os.system('pip install --upgrade --pre --extra-index-url https://download.pytorch.org/whl/nightly/cu126 "torch<2.9" spaces')
4
 
5
+ # Actual demo code
6
+ import spaces
7
+ import torch
8
+ from diffusers import WanPipeline, AutoencoderKLWan
9
+ from diffusers.models.transformers.transformer_wan import WanTransformer3DModel
10
  from diffusers.utils.export_utils import export_to_video
11
+ import gradio as gr
12
  import tempfile
13
+ import numpy as np
14
+ from PIL import Image
15
+ import random
16
+ import gc
17
+ from optimization import optimize_pipeline_
18
 
 
 
 
 
 
19
 
20
  # =========================================================
21
  # API CONFIGURATION
22
  # =========================================================
23
  GROQ_API_KEY = os.getenv("GROQ_API_KEY")
24
+
25
+ # =========================================================
26
+ # MODEL CONFIGURATION
27
+ # =========================================================
28
+ MODEL_ID = "Wan-AI/Wan2.2-T2V-A14B-Diffusers"
29
+
30
+ LANDSCAPE_WIDTH = 832
31
+ LANDSCAPE_HEIGHT = 480
32
+ MAX_SEED = np.iinfo(np.int32).max
33
+
34
+ FIXED_FPS = 16
35
+ MIN_FRAMES_MODEL = 8
36
+ MAX_FRAMES_MODEL = 81
37
+
38
+ MIN_DURATION = round(MIN_FRAMES_MODEL/FIXED_FPS, 1)
39
+ MAX_DURATION = round(MAX_FRAMES_MODEL/FIXED_FPS, 1)
40
 
41
  # =========================================================
42
  # MODEL LOADING
43
  # =========================================================
44
+ vae = AutoencoderKLWan.from_pretrained(
45
+ "Wan-AI/Wan2.2-T2V-A14B-Diffusers",
46
+ subfolder="vae",
47
+ torch_dtype=torch.float32
48
+ )
49
 
 
50
  pipe = WanPipeline.from_pretrained(
51
  MODEL_ID,
52
+ transformer=WanTransformer3DModel.from_pretrained(
53
+ 'linoyts/Wan2.2-T2V-A14B-Diffusers-BF16',
54
+ subfolder='transformer',
55
+ torch_dtype=torch.bfloat16,
56
+ device_map='cuda',
57
+ ),
58
+ transformer_2=WanTransformer3DModel.from_pretrained(
59
+ 'linoyts/Wan2.2-T2V-A14B-Diffusers-BF16',
60
+ subfolder='transformer_2',
61
+ torch_dtype=torch.bfloat16,
62
+ device_map='cuda',
63
+ ),
64
+ vae=vae,
65
+ torch_dtype=torch.bfloat16,
66
+ ).to('cuda')
67
+
68
+ for i in range(3):
69
+ gc.collect()
70
+ torch.cuda.synchronize()
71
+ torch.cuda.empty_cache()
72
+
73
+ optimize_pipeline_(
74
+ pipe,
75
+ prompt='prompt',
76
+ height=LANDSCAPE_HEIGHT,
77
+ width=LANDSCAPE_WIDTH,
78
+ num_frames=MAX_FRAMES_MODEL,
79
+ )
80
 
81
  # =========================================================
82
  # DEFAULT PROMPTS
83
  # =========================================================
84
+ default_prompt_t2v = "Two anthropomorphic cats in comfy boxing gear and bright gloves fight intensely on a spotlighted stage."
85
+ default_negative_prompt = "色调艳丽, 过曝, 静态, 细节模糊不清, 字幕, 风格, 作品, 画作, 画面, 静止, 整体发灰, 最差质量, 低质量, JPEG压缩残留, 丑陋的, 残缺的, 多余的手指, 画得不好的手部, 画得不好的脸部, 畸形的, 毁容的, 形态畸形的肢体, 手指融合, 静止不动的画面, 杂乱的背景, 三条腿, 背景人很多, 倒着走"
86
 
87
  # =========================================================
88
+ # PROMPT ENHANCEMENT
89
  # =========================================================
90
  ENHANCE_SYSTEM_PROMPT = """You are a professional video prompt engineer. Your task is to enhance user prompts for AI video generation.
91
 
 
103
  """
104
 
105
 
 
 
 
106
  def enhance_prompt(prompt: str) -> str:
107
  """Enhance the user prompt using Groq LLM API."""
108
  if not GROQ_API_KEY:
109
  return prompt
110
 
111
  try:
112
+ from groq import Groq
113
  client = Groq(api_key=GROQ_API_KEY)
114
 
115
  enhanced_text = ""
116
  completion = client.chat.completions.create(
117
  model="meta-llama/llama-4-scout-17b-16e-instruct",
118
  messages=[
119
+ {"role": "system", "content": ENHANCE_SYSTEM_PROMPT},
120
+ {"role": "user", "content": f"Enhance this video generation prompt: {prompt}"}
 
 
 
 
 
 
121
  ],
122
  temperature=0.7,
123
  max_completion_tokens=512,
 
138
 
139
 
140
  # =========================================================
141
+ # GENERATION FUNCTIONS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  # =========================================================
143
+ def get_duration(
144
+ prompt,
145
+ negative_prompt,
146
+ enhance_prompt_option,
147
+ duration_seconds,
148
+ guidance_scale,
149
+ guidance_scale_2,
150
+ steps,
151
+ seed,
152
+ randomize_seed,
153
+ progress,
154
+ ):
155
+ return steps * 15
156
+
157
+
158
+ @spaces.GPU(duration=get_duration)
159
  def generate_video(
160
+ prompt,
161
+ negative_prompt=default_negative_prompt,
162
+ enhance_prompt_option=False,
163
+ duration_seconds=MAX_DURATION,
164
+ guidance_scale=1,
165
+ guidance_scale_2=3,
166
+ steps=4,
167
+ seed=42,
168
+ randomize_seed=False,
169
+ progress=gr.Progress(track_tqdm=True),
170
+ ):
 
 
 
 
 
 
171
  # Enhance prompt if option is enabled
172
  final_prompt = prompt
173
  if enhance_prompt_option:
174
  final_prompt = enhance_prompt(prompt)
175
  print(f"Enhanced Prompt: {final_prompt}")
176
 
177
+ num_frames = np.clip(int(round(duration_seconds * FIXED_FPS)), MIN_FRAMES_MODEL, MAX_FRAMES_MODEL)
178
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
179
+
180
+ output_frames_list = pipe(
 
 
 
 
 
 
 
 
181
  prompt=final_prompt,
182
  negative_prompt=negative_prompt,
183
+ height=480,
184
+ width=832,
185
  num_frames=num_frames,
186
+ guidance_scale=float(guidance_scale),
187
+ guidance_scale_2=float(guidance_scale_2),
188
+ num_inference_steps=int(steps),
189
+ generator=torch.Generator(device="cuda").manual_seed(current_seed),
190
  ).frames[0]
191
+
 
192
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
193
  video_path = tmpfile.name
194
+
195
+ export_to_video(output_frames_list, video_path, fps=FIXED_FPS)
196
+
197
  # Build info log
198
+ actual_duration = num_frames / FIXED_FPS
199
  info_log = f"""✅ VIDEO GENERATION COMPLETE!
200
  {'=' * 50}
201
  🎬 Video Info:
202
  • Duration: {actual_duration:.2f} seconds
203
  • Total Frames: {num_frames}
204
  • FPS: {FIXED_FPS}
205
+ • Resolution: 832 x 480
206
  {'=' * 50}
207
  ⚙️ Generation Settings:
208
+ • Guidance Scale: {guidance_scale} / {guidance_scale_2}
209
+ • Inference Steps: {steps}
210
  • Seed: {current_seed}
211
  • Prompt Enhanced: {'Yes' if enhance_prompt_option else 'No'}
212
  {'=' * 50}
213
  💾 Ready to download!"""
214
+
215
  return video_path, current_seed, final_prompt, info_log
216
 
217
 
 
549
  gap: 1rem !important;
550
  }
551
 
552
+ /* ===== 🎨 Examples 섹션 ===== */
553
+ #examples .gr-sample {
554
+ border: 3px solid #1F2937 !important;
555
+ border-radius: 8px !important;
556
+ box-shadow: 4px 4px 0px #1F2937 !important;
557
+ background: #FFFFFF !important;
558
+ transition: all 0.2s ease !important;
559
+ }
560
+
561
+ #examples .gr-sample:hover {
562
+ transform: translate(-2px, -2px) !important;
563
+ box-shadow: 6px 6px 0px #1F2937 !important;
564
+ }
565
+
566
  /* ===== 반응형 조정 ===== */
567
  @media (max-width: 768px) {
568
  .header-text h1 {
 
620
  with gr.Column(scale=1, min_width=320):
621
  prompt_input = gr.Textbox(
622
  label="✏️ Your Prompt",
623
+ value=default_prompt_t2v,
624
  placeholder="Describe the video you want to create...",
625
  lines=4
626
  )
 
631
  info="Use AI to automatically enhance your prompt for better results"
632
  )
633
 
634
+ duration_seconds_input = gr.Slider(
635
+ minimum=MIN_DURATION,
636
+ maximum=MAX_DURATION,
637
+ step=0.1,
638
+ value=MAX_DURATION,
639
  label="⏱️ Duration (seconds)",
640
+ info=f"Range: {MIN_DURATION}s - {MAX_DURATION}s at {FIXED_FPS}fps"
 
 
 
641
  )
642
 
643
+ generate_button = gr.Button(
644
  "🎬 GENERATE VIDEO! 🚀",
645
  variant="primary",
646
  size="lg",
 
651
  negative_prompt_input = gr.Textbox(
652
  label="Negative Prompt",
653
  value=default_negative_prompt,
654
+ lines=3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
655
  )
656
+ seed_input = gr.Slider(
657
  label="Seed",
658
  minimum=0,
659
  maximum=MAX_SEED,
660
  step=1,
661
+ value=42,
662
+ interactive=True
663
  )
664
  randomize_seed_checkbox = gr.Checkbox(
665
  label="Randomize Seed",
666
+ value=True,
667
+ interactive=True
668
+ )
669
+ steps_slider = gr.Slider(
670
+ minimum=1,
671
+ maximum=30,
672
+ step=1,
673
+ value=4,
674
+ label="Inference Steps"
675
+ )
676
+ guidance_scale_input = gr.Slider(
677
+ minimum=0.0,
678
+ maximum=10.0,
679
+ step=0.5,
680
+ value=1,
681
+ label="Guidance Scale (High Noise)"
682
+ )
683
+ guidance_scale_2_input = gr.Slider(
684
+ minimum=0.0,
685
+ maximum=10.0,
686
+ step=0.5,
687
+ value=3,
688
+ label="Guidance Scale 2 (Low Noise)"
689
  )
690
 
691
  with gr.Accordion("📜 Generation Info", open=True):
 
703
  video_output = gr.Video(
704
  label="🎥 Generated Video",
705
  autoplay=True,
706
+ interactive=False,
707
  height=400,
708
  elem_classes="video-output"
709
  )
 
721
  </p>
722
  """
723
  )
724
+
725
+ # Examples section
726
+ gr.Examples(
727
+ examples=[
728
+ ["POV selfie video, white cat with sunglasses standing on surfboard, relaxed smile, tropical beach behind. Surfboard tips, cat falls into ocean, camera plunges underwater with bubbles."],
729
+ ["Two anthropomorphic cats in comfy boxing gear and bright gloves fight intensely on a spotlighted stage."],
730
+ ["A cinematic shot of a boat sailing on a calm sea at sunset."],
731
+ ["Drone footage flying over a futuristic city with flying cars."],
732
+ ],
733
+ inputs=[prompt_input],
734
+ outputs=[video_output, seed_input, final_prompt_output, info_log],
735
+ fn=generate_video,
736
+ cache_examples="lazy",
737
+ elem_id="examples"
738
+ )
739
+
740
  # Define inputs and outputs
741
+ ui_inputs = [
742
  prompt_input,
743
  negative_prompt_input,
744
  enhance_prompt_checkbox,
745
+ duration_seconds_input,
746
+ guidance_scale_input,
747
+ guidance_scale_2_input,
748
+ steps_slider,
749
+ seed_input,
750
+ randomize_seed_checkbox
 
751
  ]
752
 
753
+ ui_outputs = [
754
  video_output,
755
+ seed_input,
756
  final_prompt_output,
757
+ info_log
758
  ]
759
+
760
+ generate_button.click(
 
761
  fn=generate_video,
762
+ inputs=ui_inputs,
763
+ outputs=ui_outputs
764
  )
765
 
766
  if __name__ == "__main__":