seawolf2357 commited on
Commit
a4773f5
·
verified ·
1 Parent(s): ba35d35

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +306 -32
app.py CHANGED
@@ -1,35 +1,309 @@
 
 
1
  import os
2
- import sys
3
- import streamlit as st
4
- from tempfile import NamedTemporaryFile
5
-
6
- def main():
7
- try:
8
- # Get the code from secrets
9
- code = os.environ.get("MAIN_CODE")
10
-
11
- if not code:
12
- st.error("⚠️ The application code wasn't found in secrets. Please add the MAIN_CODE secret.")
13
- return
14
-
15
- # Create a temporary Python file
16
- with NamedTemporaryFile(suffix='.py', delete=False, mode='w') as tmp:
17
- tmp.write(code)
18
- tmp_path = tmp.name
19
-
20
- # Execute the code
21
- exec(compile(code, tmp_path, 'exec'), globals())
22
-
23
- # Clean up the temporary file
24
- try:
25
- os.unlink(tmp_path)
26
- except:
27
- pass
28
-
29
- except Exception as e:
30
- st.error(f"⚠️ Error loading or executing the application: {str(e)}")
31
- import traceback
32
- st.code(traceback.format_exc())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  if __name__ == "__main__":
35
- main()
 
1
+ #최대 7720프레임 = 321.6초 x 24fps
2
+
3
  import os
4
+ import spaces
5
+ import torch
6
+ from diffusers.pipelines.wan.pipeline_wan_i2v import WanImageToVideoPipeline
7
+ from diffusers.models.transformers.transformer_wan import WanTransformer3DModel
8
+ from diffusers.utils.export_utils import export_to_video
9
+ import gradio as gr
10
+ import tempfile
11
+ import numpy as np
12
+ from PIL import Image
13
+ import random
14
+ import gc
15
+
16
+ from torchao.quantization import quantize_
17
+ from torchao.quantization import Float8DynamicActivationFloat8WeightConfig, Int8WeightOnlyConfig
18
+ import aoti
19
+
20
+ # =========================================================
21
+ # MODEL CONFIGURATION
22
+ # =========================================================
23
+ MODEL_ID = os.getenv("MODEL_ID")
24
+ HF_TOKEN = os.environ.get("HF_TOKEN")
25
+
26
+ MAX_DIM = 832
27
+ MIN_DIM = 480
28
+ SQUARE_DIM = 640
29
+ MULTIPLE_OF = 16
30
+
31
+ MAX_SEED = np.iinfo(np.int32).max
32
+
33
+ FIXED_FPS = 24
34
+ MIN_FRAMES_MODEL = 8
35
+ MAX_FRAMES_MODEL = 7720
36
+
37
+ MIN_DURATION = 0.5
38
+ MAX_DURATION = 10.0
39
+
40
+ # =========================================================
41
+ # LOAD PIPELINE
42
+ # =========================================================
43
+ print("Loading pipeline...")
44
+ pipe = WanImageToVideoPipeline.from_pretrained(
45
+ MODEL_ID,
46
+ transformer=WanTransformer3DModel.from_pretrained(
47
+ MODEL_ID,
48
+ subfolder="transformer",
49
+ torch_dtype=torch.bfloat16,
50
+ device_map="cuda",
51
+ token=HF_TOKEN
52
+ ),
53
+ transformer_2=WanTransformer3DModel.from_pretrained(
54
+ MODEL_ID,
55
+ subfolder="transformer_2",
56
+ torch_dtype=torch.bfloat16,
57
+ device_map="cuda",
58
+ token=HF_TOKEN
59
+ ),
60
+ torch_dtype=torch.bfloat16,
61
+ ).to("cuda")
62
+
63
+ # =========================================================
64
+ # LOAD LORA ADAPTERS
65
+ # =========================================================
66
+ print("Loading LoRA adapters...")
67
+ pipe.load_lora_weights(
68
+ "Kijai/WanVideo_comfy",
69
+ weight_name="Lightx2v/lightx2v_I2V_14B_480p_cfg_step_distill_rank128_bf16.safetensors",
70
+ adapter_name="lightx2v"
71
+ )
72
+ pipe.load_lora_weights(
73
+ "Kijai/WanVideo_comfy",
74
+ weight_name="Lightx2v/lightx2v_I2V_14B_480p_cfg_step_distill_rank128_bf16.safetensors",
75
+ adapter_name="lightx2v_2",
76
+ load_into_transformer_2=True
77
+ )
78
+
79
+ pipe.set_adapters(["lightx2v", "lightx2v_2"], adapter_weights=[1., 1.])
80
+ pipe.fuse_lora(adapter_names=["lightx2v"], lora_scale=3., components=["transformer"])
81
+ pipe.fuse_lora(adapter_names=["lightx2v_2"], lora_scale=1., components=["transformer_2"])
82
+ pipe.unload_lora_weights()
83
+
84
+ # =========================================================
85
+ # QUANTIZATION & AOT OPTIMIZATION
86
+ # =========================================================
87
+ print("Applying quantization...")
88
+ quantize_(pipe.text_encoder, Int8WeightOnlyConfig())
89
+ quantize_(pipe.transformer, Float8DynamicActivationFloat8WeightConfig())
90
+ quantize_(pipe.transformer_2, Float8DynamicActivationFloat8WeightConfig())
91
+
92
+ print("Loading AOTI blocks...")
93
+ aoti.aoti_blocks_load(pipe.transformer, 'zerogpu-aoti/Wan2', variant='fp8da')
94
+ aoti.aoti_blocks_load(pipe.transformer_2, 'zerogpu-aoti/Wan2', variant='fp8da')
95
+
96
+ # =========================================================
97
+ # DEFAULT PROMPTS
98
+ # =========================================================
99
+ default_prompt_i2v = "Generate a video with smooth and natural movement. Objects should have visible motion while maintaining fluid transitions."
100
+ default_negative_prompt = "low quality, worst quality, blurry, distorted, deformed, ugly, bad anatomy"
101
+
102
+ # =========================================================
103
+ # IMAGE RESIZING LOGIC
104
+ # =========================================================
105
+ def resize_image(image: Image.Image) -> Image.Image:
106
+ width, height = image.size
107
+ if width == height:
108
+ return image.resize((SQUARE_DIM, SQUARE_DIM), Image.LANCZOS)
109
+
110
+ aspect_ratio = width / height
111
+ MAX_ASPECT_RATIO = MAX_DIM / MIN_DIM
112
+ MIN_ASPECT_RATIO = MIN_DIM / MAX_DIM
113
+
114
+ image_to_resize = image
115
+
116
+ if aspect_ratio > MAX_ASPECT_RATIO:
117
+ crop_width = int(round(height * MAX_ASPECT_RATIO))
118
+ left = (width - crop_width) // 2
119
+ image_to_resize = image.crop((left, 0, left + crop_width, height))
120
+ elif aspect_ratio < MIN_ASPECT_RATIO:
121
+ crop_height = int(round(width / MIN_ASPECT_RATIO))
122
+ top = (height - crop_height) // 2
123
+ image_to_resize = image.crop((0, top, width, top + crop_height))
124
+
125
+ if width > height:
126
+ target_w = MAX_DIM
127
+ target_h = int(round(target_w / aspect_ratio))
128
+ else:
129
+ target_h = MAX_DIM
130
+ target_w = int(round(target_h * aspect_ratio))
131
+
132
+ final_w = round(target_w / MULTIPLE_OF) * MULTIPLE_OF
133
+ final_h = round(target_h / MULTIPLE_OF) * MULTIPLE_OF
134
+
135
+ final_w = max(MIN_DIM, min(MAX_DIM, final_w))
136
+ final_h = max(MIN_DIM, min(MAX_DIM, final_h))
137
+
138
+ return image_to_resize.resize((final_w, final_h), Image.LANCZOS)
139
+
140
+ # =========================================================
141
+ # UTILITY FUNCTIONS
142
+ # =========================================================
143
+ def get_num_frames(duration_seconds: float):
144
+ return 1 + int(np.clip(int(round(duration_seconds * FIXED_FPS)), MIN_FRAMES_MODEL, MAX_FRAMES_MODEL))
145
+
146
+ def get_duration(
147
+ input_image, prompt, steps, negative_prompt,
148
+ duration_seconds, guidance_scale, guidance_scale_2,
149
+ seed, randomize_seed, progress,
150
+ ):
151
+ if input_image is None:
152
+ return 120
153
+
154
+ BASE_FRAMES_HEIGHT_WIDTH = 81 * 832 * 624
155
+ BASE_STEP_DURATION = 15
156
+ width, height = resize_image(input_image).size
157
+ frames = get_num_frames(duration_seconds)
158
+ factor = frames * width * height / BASE_FRAMES_HEIGHT_WIDTH
159
+ step_duration = BASE_STEP_DURATION * factor ** 1.5
160
+ return 10 + int(steps) * step_duration
161
+
162
+ # =========================================================
163
+ # MAIN GENERATION FUNCTION
164
+ # =========================================================
165
+ @spaces.GPU(duration=get_duration)
166
+ def generate_video(
167
+ input_image,
168
+ prompt,
169
+ steps=4,
170
+ negative_prompt=default_negative_prompt,
171
+ duration_seconds=3.5,
172
+ guidance_scale=1,
173
+ guidance_scale_2=1,
174
+ seed=42,
175
+ randomize_seed=False,
176
+ progress=gr.Progress(track_tqdm=True),
177
+ ):
178
+ if input_image is None:
179
+ raise gr.Error("Please upload an image.")
180
+
181
+ num_frames = get_num_frames(duration_seconds)
182
+ current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
183
+ resized_image = resize_image(input_image)
184
+
185
+ output_frames_list = pipe(
186
+ image=resized_image,
187
+ prompt=prompt,
188
+ negative_prompt=negative_prompt,
189
+ height=resized_image.height,
190
+ width=resized_image.width,
191
+ num_frames=num_frames,
192
+ guidance_scale=float(guidance_scale),
193
+ guidance_scale_2=float(guidance_scale_2),
194
+ num_inference_steps=int(steps),
195
+ generator=torch.Generator(device="cuda").manual_seed(current_seed),
196
+ ).frames[0]
197
+
198
+ with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
199
+ video_path = tmpfile.name
200
+ export_to_video(output_frames_list, video_path, fps=FIXED_FPS)
201
+ return video_path, current_seed
202
+
203
+ # =========================================================
204
+ # GRADIO UI
205
+ # =========================================================
206
+ with gr.Blocks() as demo:
207
+
208
+ gr.HTML("""
209
+ <style>
210
+ .gradio-container {
211
+ background: linear-gradient(135deg, #fef9f3 0%, #f0e6fa 50%, #e6f0fa 100%) !important;
212
+ }
213
+ footer {display: none !important;}
214
+ </style>
215
+ <div style="text-align: center; margin-bottom: 20px;">
216
+ <h1 style="color: #6b5b7a; font-size: 2.2rem; font-weight: 700; margin-bottom: 0.3rem;">
217
+ 🎬 NSFW Uncensored "Image to Video"
218
+ </h1>
219
+ <p style="color: #8b7b9b; font-size: 1rem;">Powered by Wan 2.2 Model</p>
220
+ </div>
221
+ """)
222
+
223
+ with gr.Row():
224
+ with gr.Column(scale=1):
225
+ input_image_component = gr.Image(
226
+ type="pil",
227
+ label="📷 Upload Image",
228
+ height=350
229
+ )
230
+ prompt_input = gr.Textbox(
231
+ label="✏️ Prompt",
232
+ value=default_prompt_i2v,
233
+ placeholder="Describe the motion you want...",
234
+ lines=3
235
+ )
236
+ duration_seconds_input = gr.Slider(
237
+ minimum=MIN_DURATION,
238
+ maximum=MAX_DURATION,
239
+ step=0.5,
240
+ value=3.5,
241
+ label="⏱️ Duration (seconds)"
242
+ )
243
+
244
+ with gr.Accordion("⚙️ Options", open=False):
245
+ negative_prompt_input = gr.Textbox(
246
+ label="Negative Prompt",
247
+ value=default_negative_prompt,
248
+ lines=2
249
+ )
250
+ steps_slider = gr.Slider(
251
+ minimum=1,
252
+ maximum=30,
253
+ step=1,
254
+ value=6,
255
+ label="Inference Steps"
256
+ )
257
+ guidance_scale_input = gr.Slider(
258
+ minimum=0.0,
259
+ maximum=10.0,
260
+ step=0.5,
261
+ value=1,
262
+ label="Guidance Scale"
263
+ )
264
+ guidance_scale_2_input = gr.Slider(
265
+ minimum=0.0,
266
+ maximum=10.0,
267
+ step=0.5,
268
+ value=1,
269
+ label="Guidance Scale 2"
270
+ )
271
+ seed_input = gr.Slider(
272
+ label="Seed",
273
+ minimum=0,
274
+ maximum=MAX_SEED,
275
+ step=1,
276
+ value=42
277
+ )
278
+ randomize_seed_checkbox = gr.Checkbox(
279
+ label="Randomize Seed",
280
+ value=True
281
+ )
282
+
283
+ generate_button = gr.Button(
284
+ "✨ Generate Video",
285
+ variant="primary"
286
+ )
287
+
288
+ with gr.Column(scale=1):
289
+ video_output = gr.Video(
290
+ label="🎥 Generated Video",
291
+ autoplay=True,
292
+ height=450
293
+ )
294
+
295
+ ui_inputs = [
296
+ input_image_component, prompt_input, steps_slider,
297
+ negative_prompt_input, duration_seconds_input,
298
+ guidance_scale_input, guidance_scale_2_input,
299
+ seed_input, randomize_seed_checkbox
300
+ ]
301
+
302
+ generate_button.click(
303
+ fn=generate_video,
304
+ inputs=ui_inputs,
305
+ outputs=[video_output, seed_input]
306
+ )
307
 
308
  if __name__ == "__main__":
309
+ demo.queue().launch()