alexander00001 commited on
Commit
29f70b5
·
verified ·
1 Parent(s): 5806f25

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +842 -130
app.py CHANGED
@@ -1,154 +1,866 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
 
 
 
2
  import numpy as np
3
- import random
 
 
 
 
4
 
5
- # import spaces #[uncomment to use ZeroGPU]
6
- from diffusers import DiffusionPipeline
7
- import torch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- device = "cuda" if torch.cuda.is_available() else "cpu"
10
- model_repo_id = "stabilityai/sdxl-turbo" # Replace to the model you would like to use
11
-
12
- if torch.cuda.is_available():
13
- torch_dtype = torch.float16
14
- else:
15
- torch_dtype = torch.float32
16
-
17
- pipe = DiffusionPipeline.from_pretrained(model_repo_id, torch_dtype=torch_dtype)
18
- pipe = pipe.to(device)
19
-
20
- MAX_SEED = np.iinfo(np.int32).max
21
- MAX_IMAGE_SIZE = 1024
22
-
23
-
24
- # @spaces.GPU #[uncomment to use ZeroGPU]
25
- def infer(
26
- prompt,
27
- negative_prompt,
28
- seed,
29
- randomize_seed,
30
- width,
31
- height,
32
- guidance_scale,
33
- num_inference_steps,
34
- progress=gr.Progress(track_tqdm=True),
35
- ):
36
- if randomize_seed:
37
- seed = random.randint(0, MAX_SEED)
38
-
39
- generator = torch.Generator().manual_seed(seed)
40
-
41
- image = pipe(
42
- prompt=prompt,
43
- negative_prompt=negative_prompt,
44
- guidance_scale=guidance_scale,
45
- num_inference_steps=num_inference_steps,
46
- width=width,
47
- height=height,
48
- generator=generator,
49
- ).images[0]
50
-
51
- return image, seed
52
-
53
-
54
- examples = [
55
- "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
56
- "An astronaut riding a green horse",
57
- "A delicious ceviche cheesecake slice",
58
  ]
59
 
60
- css = """
61
- #col-container {
62
- margin: 0 auto;
63
- max-width: 640px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  }
65
- """
66
 
67
- with gr.Blocks(css=css) as demo:
68
- with gr.Column(elem_id="col-container"):
69
- gr.Markdown(" # Text-to-Image Gradio Template")
70
-
71
- with gr.Row():
72
- prompt = gr.Text(
73
- label="Prompt",
74
- show_label=False,
75
- max_lines=1,
76
- placeholder="Enter your prompt",
77
- container=False,
78
- )
79
 
80
- run_button = gr.Button("Run", scale=0, variant="primary")
 
81
 
82
- result = gr.Image(label="Result", show_label=False)
 
 
 
 
83
 
84
- with gr.Accordion("Advanced Settings", open=False):
85
- negative_prompt = gr.Text(
86
- label="Negative prompt",
87
- max_lines=1,
88
- placeholder="Enter a negative prompt",
89
- visible=False,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
- seed = gr.Slider(
93
- label="Seed",
94
- minimum=0,
95
- maximum=MAX_SEED,
96
- step=1,
97
- value=0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  with gr.Row():
103
- width = gr.Slider(
104
- label="Width",
105
- minimum=256,
106
- maximum=MAX_IMAGE_SIZE,
107
- step=32,
108
- value=1024, # Replace with defaults that work for your model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  )
110
-
111
- height = gr.Slider(
112
- label="Height",
113
- minimum=256,
114
- maximum=MAX_IMAGE_SIZE,
115
- step=32,
116
- value=1024, # Replace with defaults that work for your model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  )
118
-
 
119
  with gr.Row():
120
- guidance_scale = gr.Slider(
121
- label="Guidance scale",
122
- minimum=0.0,
123
- maximum=10.0,
124
- step=0.1,
125
- value=0.0, # Replace with defaults that work for your model
 
 
126
  )
127
-
128
- num_inference_steps = gr.Slider(
129
- label="Number of inference steps",
130
- minimum=1,
131
- maximum=50,
132
- step=1,
133
- value=2, # Replace with defaults that work for your model
 
 
 
 
 
 
 
 
 
134
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
- gr.Examples(examples=examples, inputs=[prompt])
137
- gr.on(
138
- triggers=[run_button.click, prompt.submit],
139
- fn=infer,
140
- inputs=[
141
- prompt,
142
- negative_prompt,
143
- seed,
144
- randomize_seed,
145
- width,
146
- height,
147
- guidance_scale,
148
- num_inference_steps,
149
- ],
150
- outputs=[result, seed],
151
- )
152
-
153
  if __name__ == "__main__":
154
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ===== 必须首先导入spaces =====
2
+ try:
3
+ import spaces
4
+ SPACES_AVAILABLE = True
5
+ print("✅ Spaces available - ZeroGPU mode")
6
+ except ImportError:
7
+ SPACES_AVAILABLE = False
8
+ print("⚠️ Spaces not available - running in regular mode")
9
+
10
+ # ===== 其他导入 =====
11
+ import os
12
+ import uuid
13
+ from datetime import datetime
14
+ import random
15
+ import torch
16
  import gradio as gr
17
+ from diffusers import DiffusionPipeline
18
+ from PIL import Image
19
+ import traceback
20
  import numpy as np
21
+ import cv2
22
+ import imageio
23
+ from pathlib import Path
24
+ import tempfile
25
+ import shutil
26
 
27
+ # ===== 长提示词处理 =====
28
+ try:
29
+ from compel import Compel, ReturnedEmbeddingsType
30
+ COMPEL_AVAILABLE = True
31
+ print("✅ Compel available for long prompt processing")
32
+ except ImportError:
33
+ COMPEL_AVAILABLE = False
34
+ print("⚠️ Compel not available - using standard prompt processing")
35
+
36
+ # ===== 修复后的配置 =====
37
+ STYLE_PRESETS = {
38
+ "None": "",
39
+ "Cinematic": "cinematic lighting, dramatic composition, film grain, professional cinematography, movie scene, high production value",
40
+ "Anime": "anime style, detailed animation, high quality anime, cel animation, vibrant anime colors, smooth animation",
41
+ "Realistic": "photorealistic, ultra-detailed, natural lighting, realistic motion, lifelike animation, high fidelity",
42
+ "Fantasy": "fantasy style, magical atmosphere, ethereal lighting, mystical effects, enchanted scene",
43
+ "Artistic": "artistic style, creative composition, unique visual style, expressive animation, stylized rendering"
44
+ }
45
+
46
+ # 固定模型配置 - 使用官方Diffusers兼容版本
47
+ FIXED_MODEL = "Wan-AI/Wan2.2-T2V-A14B-Diffusers" # 最新版本
48
+ # 备用选择: "Wan-AI/Wan2.1-T2V-14B-Diffusers"
49
 
50
+ # 质量增强提示词 - 适配视频
51
+ QUALITY_ENHANCERS = [
52
+ "high quality video", "(masterpiece:1.3)", "(best quality:1.2)", "smooth animation",
53
+ "detailed motion", "fluid movement", "professional video quality", "high resolution",
54
+ "consistent lighting", "stable composition", "(perfect anatomy:1.1)", "natural motion",
55
+ "cinematic quality", "detailed textures", "smooth transitions"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  ]
57
 
58
+ # 修复后的风格专用增强词 - 视频优化
59
+ STYLE_ENHANCERS = {
60
+ "Cinematic": [
61
+ "cinematic shot", "(cinematic quality:1.3)", "movie scene", "film lighting",
62
+ "dramatic composition", "professional cinematography", "cinematic motion", "film grain",
63
+ "dynamic camera work", "cinematic storytelling"
64
+ ],
65
+ "Anime": [
66
+ "anime animation", "(high quality anime:1.3)", "detailed anime", "smooth anime motion",
67
+ "cel animation style", "(anime video:1.2)", "vibrant anime colors", "anime cinematics",
68
+ "japanese animation", "fluid anime movement"
69
+ ],
70
+ "Realistic": [
71
+ "realistic video", "(photorealistic animation:1.3)", "natural motion", "lifelike movement",
72
+ "realistic lighting", "(realistic video:1.2)", "natural dynamics", "authentic motion",
73
+ "real-world physics", "documentary style"
74
+ ],
75
+ "Fantasy": [
76
+ "fantasy animation", "(magical video:1.3)", "mystical motion", "ethereal effects",
77
+ "enchanted scene", "magical atmosphere", "fantasy cinematics", "surreal animation",
78
+ "otherworldly movement", "magical realism"
79
+ ],
80
+ "Artistic": [
81
+ "artistic video", "(creative animation:1.3)", "stylized motion", "artistic composition",
82
+ "unique visual style", "expressive animation", "creative cinematics", "artistic movement",
83
+ "stylized rendering", "avant-garde video"
84
+ ]
85
  }
 
86
 
87
+ # 视频参数配置
88
+ VIDEO_CONFIG = {
89
+ "default_duration": 4.0, #
90
+ "max_duration": 8.0,
91
+ "default_fps": 24,
92
+ "max_fps": 30,
93
+ "default_width": 512,
94
+ "default_height": 512,
95
+ "max_resolution": 768
96
+ }
 
 
97
 
98
+ SAVE_DIR = "generated_videos"
99
+ os.makedirs(SAVE_DIR, exist_ok=True)
100
 
101
+ # ===== 模型相关变量 =====
102
+ pipeline = None
103
+ compel_processor = None
104
+ device = None
105
+ model_loaded = False
106
 
107
+ def initialize_model():
108
+ """优化的模型初始化函数"""
109
+ global pipeline, compel_processor, device, model_loaded
110
+
111
+ if model_loaded and pipeline is not None:
112
+ return True
113
+
114
+ try:
115
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
116
+ print(f"🖥️ Using device: {device}")
117
+
118
+ print(f"📦 Loading Official Wan T2V model: {FIXED_MODEL}")
119
+
120
+ # 基础模型加载 - 使用官方Wan Pipeline
121
+ try:
122
+ from diffusers import WanPipeline
123
+ pipeline = WanPipeline.from_pretrained(
124
+ FIXED_MODEL,
125
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
126
+ variant="fp16" if torch.cuda.is_available() else None,
127
+ use_safetensors=True,
128
+ safety_checker=None,
129
+ requires_safety_checker=False
130
+ )
131
+ except ImportError:
132
+ # 如果WanPipeline不可用,使用通用DiffusionPipeline
133
+ print("⚠️ WanPipeline not found, using DiffusionPipeline")
134
+ pipeline = DiffusionPipeline.from_pretrained(
135
+ FIXED_MODEL,
136
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
137
+ variant="fp16" if torch.cuda.is_available() else None,
138
+ use_safetensors=True,
139
+ safety_checker=None,
140
+ requires_safety_checker=False,
141
+ trust_remote_code=True
142
  )
143
+
144
+ pipeline = pipeline.to(device)
145
+
146
+ # GPU优化
147
+ if torch.cuda.is_available():
148
+ try:
149
+ pipeline.enable_vae_slicing()
150
+ pipeline.enable_attention_slicing()
151
+ try:
152
+ pipeline.enable_xformers_memory_efficient_attention()
153
+ except:
154
+ pass
155
+ # 视频生成特有的内存优化
156
+ if hasattr(pipeline, 'enable_sequential_cpu_offload'):
157
+ pipeline.enable_sequential_cpu_offload()
158
+ except Exception as mem_error:
159
+ print(f"⚠️ Memory optimization warning: {mem_error}")
160
+
161
+ # 初始化Compel
162
+ if COMPEL_AVAILABLE and hasattr(pipeline, 'tokenizer'):
163
+ try:
164
+ compel_processor = Compel(
165
+ tokenizer=pipeline.tokenizer,
166
+ text_encoder=pipeline.text_encoder,
167
+ returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED,
168
+ truncate_long_prompts=False
169
+ )
170
+ print("✅ Long prompt processor (Compel) initialized successfully")
171
+ except Exception as compel_error:
172
+ print(f"⚠️ Compel initialization failed: {compel_error}")
173
+ compel_processor = None
174
+
175
+ model_loaded = True
176
+ print("✅ T2V Model initialization complete")
177
+ return True
178
+
179
+ except Exception as e:
180
+ print(f"❌ Critical model loading error: {e}")
181
+ print(traceback.format_exc())
182
+ model_loaded = False
183
+ return False
184
+
185
+ def enhance_prompt(prompt: str, style: str) -> str:
186
+ """修复后的增强提示词函数 - 视频优化"""
187
+ if not prompt or prompt.strip() == "":
188
+ return ""
189
+
190
+ enhanced_parts = [prompt.strip()]
191
+
192
+ # 添加风格前缀
193
+ style_prefix = STYLE_PRESETS.get(style, "")
194
+ if style_prefix and style != "None":
195
+ enhanced_parts.insert(0, style_prefix)
196
+
197
+ # 添加风格特定增强词
198
+ if style in STYLE_ENHANCERS and style != "None":
199
+ style_terms = ", ".join(STYLE_ENHANCERS[style])
200
+ enhanced_parts.append(style_terms)
201
+
202
+ # 添加质量增强词
203
+ quality_terms = ", ".join(QUALITY_ENHANCERS)
204
+ enhanced_parts.append(quality_terms)
205
+
206
+ enhanced_prompt = ", ".join(filter(None, enhanced_parts))
207
+
208
+ print(f"🎨 Style: {style}")
209
+ print(f"📝 Original prompt: {prompt[:100]}...")
210
+ print(f"✨ Enhanced prompt: {enhanced_prompt[:150]}...")
211
+
212
+ return enhanced_prompt
213
+
214
+ def process_long_prompt(prompt, negative_prompt=""):
215
+ """处理长提示词"""
216
+ if not compel_processor:
217
+ return None, None
218
+
219
+ try:
220
+ conditioning = compel_processor([prompt, negative_prompt])
221
+ return conditioning, None
222
+ except Exception as e:
223
+ print(f"Long prompt processing failed: {e}")
224
+ return None, None
225
 
226
+ def apply_spaces_decorator(func):
227
+ """应用spaces装饰器 - 增加超时时间"""
228
+ if SPACES_AVAILABLE:
229
+ return spaces.GPU(duration=120)(func) # 2分钟超时
230
+ return func
231
+
232
+ def create_metadata_content(prompt, enhanced_prompt, seed, steps, cfg_scale, width, height, duration, fps, style):
233
+ """创建元数据内容 - 视频版本"""
234
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
235
+ return f"""Generated Video Metadata
236
+ ======================
237
+ Timestamp: {timestamp}
238
+ Original Prompt: {prompt}
239
+ Enhanced Prompt: {enhanced_prompt}
240
+ Seed: {seed}
241
+ Steps: {steps}
242
+ CFG Scale: {cfg_scale}
243
+ Dimensions: {width}×{height}
244
+ Duration: {duration}s
245
+ FPS: {fps}
246
+ Style: {style}
247
+ Model: NSFW_Wan_14b
248
+ Total Frames: {int(duration * fps)}
249
+ """
250
+
251
+ def frames_to_video(frames, output_path, fps=24, format="mp4"):
252
+ """将帧序列转换为视频文件"""
253
+ try:
254
+ if format.lower() == "gif":
255
+ # 生成GIF
256
+ imageio.mimsave(output_path, frames, fps=fps, loop=0)
257
+ else:
258
+ # 生成MP4
259
+ writer = imageio.get_writer(output_path, fps=fps, codec='libx264', quality=8)
260
+ for frame in frames:
261
+ if isinstance(frame, Image.Image):
262
+ frame = np.array(frame)
263
+ writer.append_data(frame)
264
+ writer.close()
265
+
266
+ return True
267
+ except Exception as e:
268
+ print(f"Video creation error: {e}")
269
+ return False
270
+
271
+ @apply_spaces_decorator
272
+ def generate_video(prompt: str, style: str, negative_prompt: str = "", steps: int = 20, cfg_scale: float = 7.0,
273
+ seed: int = -1, width: int = 512, height: int = 512, duration: float = 4.0, fps: int = 24,
274
+ progress=gr.Progress()):
275
+ """视频生成函数"""
276
+ if not prompt or prompt.strip() == "":
277
+ return None, None, "", ""
278
+
279
+ # 初始化模型
280
+ progress(0.1, desc="Loading T2V model...")
281
+ if not initialize_model():
282
+ return None, None, "", "❌ Failed to load T2V model"
283
+
284
+ progress(0.2, desc="Processing prompt...")
285
+
286
+ try:
287
+ # 处理seed
288
+ if seed == -1:
289
+ seed = random.randint(0, np.iinfo(np.int32).max)
290
+
291
+ # 增强提示词
292
+ enhanced_prompt = enhance_prompt(prompt.strip(), style)
293
+
294
+ # 增强负面提示词
295
+ if not negative_prompt.strip():
296
+ negative_prompt = "(low quality, worst quality:1.4), (bad anatomy:1.2), blurry, watermark, signature, text, error, distorted motion, choppy animation, inconsistent lighting, frame drops, stuttering"
297
+
298
+ # 计算帧数
299
+ num_frames = int(duration * fps)
300
+ print(f"🎬 Generating {num_frames} frames at {fps} FPS for {duration}s video")
301
+
302
+ # 生成参数
303
+ generator = torch.Generator(device).manual_seed(seed)
304
+
305
+ progress(0.3, desc=f"Generating {duration}s video...")
306
+
307
+ # 长提示词处理
308
+ use_long_prompt = len(enhanced_prompt.split()) > 60 or len(enhanced_prompt) > 300
309
+
310
+ generation_kwargs = {
311
+ "num_inference_steps": steps,
312
+ "guidance_scale": cfg_scale,
313
+ "width": width,
314
+ "height": height,
315
+ "num_frames": num_frames,
316
+ "generator": generator
317
+ }
318
+
319
+ if use_long_prompt and compel_processor:
320
+ conditioning, _ = process_long_prompt(enhanced_prompt, negative_prompt)
321
+ if conditioning is not None:
322
+ result = pipeline(
323
+ prompt_embeds=conditioning[0:1],
324
+ negative_prompt_embeds=conditioning[1:2],
325
+ **generation_kwargs
326
+ )
327
+ else:
328
+ result = pipeline(
329
+ prompt=enhanced_prompt,
330
+ negative_prompt=negative_prompt,
331
+ **generation_kwargs
332
+ )
333
+ else:
334
+ result = pipeline(
335
+ prompt=enhanced_prompt,
336
+ negative_prompt=negative_prompt,
337
+ **generation_kwargs
338
  )
339
+
340
+ progress(0.8, desc="Processing video output...")
341
+
342
+ # 获取视��帧
343
+ if hasattr(result, 'frames') and result.frames is not None:
344
+ frames = result.frames[0] # 取第一个批次
345
+ elif hasattr(result, 'images') and result.images is not None:
346
+ frames = result.images
347
+ else:
348
+ return None, None, "", "❌ No frames generated from model"
349
+
350
+ print(f"📹 Generated {len(frames)} frames")
351
+
352
+ # 创建临时文件
353
+ with tempfile.TemporaryDirectory() as temp_dir:
354
+ # 生成MP4
355
+ mp4_path = os.path.join(temp_dir, f"video_{seed}.mp4")
356
+ mp4_success = frames_to_video(frames, mp4_path, fps=fps, format="mp4")
357
+
358
+ # 生成GIF
359
+ gif_path = os.path.join(temp_dir, f"video_{seed}.gif")
360
+ gif_success = frames_to_video(frames, gif_path, fps=fps, format="gif")
361
+
362
+ if not mp4_success and not gif_success:
363
+ return None, None, "", "❌ Failed to create video files"
364
+
365
+ # 复制到永久目录
366
+ final_mp4_path = None
367
+ final_gif_path = None
368
+
369
+ if mp4_success:
370
+ final_mp4_path = os.path.join(SAVE_DIR, f"video_{seed}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
371
+ shutil.copy2(mp4_path, final_mp4_path)
372
+
373
+ if gif_success:
374
+ final_gif_path = os.path.join(SAVE_DIR, f"video_{seed}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.gif")
375
+ shutil.copy2(gif_path, final_gif_path)
376
+
377
+ progress(0.95, desc="Creating metadata...")
378
+
379
+ # 创建元数据内容
380
+ metadata_content = create_metadata_content(
381
+ prompt, enhanced_prompt, seed, steps, cfg_scale, width, height, duration, fps, style
382
+ )
383
+
384
+ progress(1.0, desc="Complete!")
385
+
386
+ # 生成信息显示
387
+ generation_info = f"Style: {style} | Seed: {seed} | Size: {width}×{height} | Duration: {duration}s | FPS: {fps} | Frames: {num_frames}"
388
+
389
+ # 返回MP4路径用于显示,同时提供两种格式的下载
390
+ return final_mp4_path, final_gif_path, generation_info, metadata_content
391
+
392
+ except Exception as e:
393
+ error_msg = str(e)
394
+ print(f"Generation error: {error_msg}")
395
+ print(traceback.format_exc())
396
+ return None, None, "", f"❌ Generation failed: {error_msg}"
397
+
398
+ # ===== CSS样式 - 视频版本 =====
399
+ css = """
400
+ /* 全局容器 */
401
+ .gradio-container {
402
+ max-width: 100% !important;
403
+ margin: 0 !important;
404
+ padding: 0 !important;
405
+ background: linear-gradient(135deg, #e6a4f2 0%, #1197e4 100%) !important;
406
+ min-height: 100vh !important;
407
+ font-family: 'Segoe UI', Arial, sans-serif !important;
408
+ }
409
+
410
+ /* 主要内容区域 */
411
+ .main-content {
412
+ background: rgba(255, 255, 255, 0.9) !important;
413
+ border-radius: 20px !important;
414
+ padding: 20px !important;
415
+ margin: 15px !important;
416
+ box-shadow: 0 10px 25px rgba(255, 255, 255, 0.2) !important;
417
+ min-height: calc(100vh - 30px) !important;
418
+ color: #3e3e3e !important;
419
+ backdrop-filter: blur(10px) !important;
420
+ }
421
+
422
+ /* 简化标题 */
423
+ .title {
424
+ text-align: center !important;
425
+ background: linear-gradient(45deg, #bb6ded, #08676b) !important;
426
+ -webkit-background-clip: text !important;
427
+ -webkit-text-fill-color: transparent !important;
428
+ background-clip: text !important;
429
+ font-size: 2rem !important;
430
+ margin-bottom: 15px !important;
431
+ font-weight: bold !important;
432
+ }
433
+
434
+ /* 简化警告信息 */
435
+ .warning-box {
436
+ background: linear-gradient(45deg, #bb6ded, #08676b) !important;
437
+ color: white !important;
438
+ padding: 8px !important;
439
+ border-radius: 8px !important;
440
+ margin-bottom: 15px !important;
441
+ text-align: center !important;
442
+ font-weight: bold !important;
443
+ font-size: 14px !important;
444
+ }
445
+
446
+ /* 输入框样式 - 修复背景色 */
447
+ .prompt-box textarea, .prompt-box input {
448
+ border-radius: 10px !important;
449
+ border: 2px solid #bb6ded !important;
450
+ padding: 15px !important;
451
+ font-size: 18px !important;
452
+ background: linear-gradient(135deg, rgba(245, 243, 255, 0.9), rgba(237, 233, 254, 0.9)) !important;
453
+ color: #2d2d2d !important;
454
+ }
455
+
456
+ .prompt-box textarea:focus, .prompt-box input:focus {
457
+ border-color: #08676b !important;
458
+ box-shadow: 0 0 15px rgba(77, 8, 161, 0.3) !important;
459
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(248, 249, 250, 0.95)) !important;
460
+ }
461
+
462
+ /* 右侧控制区域 - 修复背景色 */
463
+ .controls-section {
464
+ background: linear-gradient(135deg, rgba(224, 218, 255, 0.8), rgba(196, 181, 253, 0.8)) !important;
465
+ border-radius: 12px !important;
466
+ padding: 15px !important;
467
+ margin-bottom: 8px !important;
468
+ border: 2px solid rgba(187, 109, 237, 0.3) !important;
469
+ backdrop-filter: blur(5px) !important;
470
+ }
471
 
472
+ .controls-section label {
473
+ font-weight: 600 !important;
474
+ color: #2d2d2d !important;
475
+ margin-bottom: 8px !important;
476
+ }
477
+
478
+ /* 修复单选按钮和输入框背景 */
479
+ .controls-section input[type="radio"] {
480
+ accent-color: #bb6ded !important;
481
+ }
482
+
483
+ .controls-section input[type="number"],
484
+ .controls-section input[type="range"] {
485
+ background: rgba(255, 255, 255, 0.9) !important;
486
+ border: 1px solid #bb6ded !important;
487
+ border-radius: 6px !important;
488
+ padding: 8px !important;
489
+ color: #2d2d2d !important;
490
+ }
491
+
492
+ .controls-section select {
493
+ background: rgba(255, 255, 255, 0.9) !important;
494
+ border: 1px solid #bb6ded !important;
495
+ border-radius: 6px !important;
496
+ padding: 8px !important;
497
+ color: #2d2d2d !important;
498
+ }
499
+
500
+ /* 生成按钮 */
501
+ .generate-btn {
502
+ background: linear-gradient(45deg, #bb6ded, #08676b) !important;
503
+ color: white !important;
504
+ border: none !important;
505
+ padding: 15px 25px !important;
506
+ border-radius: 25px !important;
507
+ font-size: 16px !important;
508
+ font-weight: bold !important;
509
+ width: 100% !important;
510
+ cursor: pointer !important;
511
+ transition: all 0.3s ease !important;
512
+ text-transform: uppercase !important;
513
+ letter-spacing: 1px !important;
514
+ }
515
+
516
+ .generate-btn:hover {
517
+ transform: translateY(-2px) !important;
518
+ box-shadow: 0 8px 25px rgba(187, 109, 237, 0.5) !important;
519
+ }
520
+
521
+ /* 视频输出区域 */
522
+ .video-output {
523
+ border-radius: 15px !important;
524
+ overflow: hidden !important;
525
+ max-width: 100% !important;
526
+ max-height: 70vh !important;
527
+ border: 3px solid #08676b !important;
528
+ box-shadow: 0 8px 20px rgba(0,0,0,0.15) !important;
529
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(248, 249, 250, 0.9)) !important;
530
+ }
531
+
532
+ /* 下载按钮样式 */
533
+ .download-btn {
534
+ background: linear-gradient(45deg, #28a745, #20c997) !important;
535
+ color: white !important;
536
+ border: none !important;
537
+ padding: 10px 20px !important;
538
+ border-radius: 20px !important;
539
+ font-size: 14px !important;
540
+ font-weight: bold !important;
541
+ margin: 5px !important;
542
+ cursor: pointer !important;
543
+ transition: all 0.3s ease !important;
544
+ }
545
 
546
+ .download-btn:hover {
547
+ transform: translateY(-1px) !important;
548
+ box-shadow: 0 5px 15px rgba(40, 167, 69, 0.4) !important;
549
+ }
550
+
551
+ /* 视频信息区域 */
552
+ .video-info {
553
+ background: linear-gradient(135deg, rgba(248, 249, 250, 0.2), rgba(233, 236, 239, 0.9)) !important;
554
+ border-radius: 8px !important;
555
+ padding: 12px !important;
556
+ margin-top: 10px !important;
557
+ font-size: 12px !important;
558
+ color: #495057 !important;
559
+ border: 2px solid rgba(187, 109, 237, 0.2) !important;
560
+ backdrop-filter: blur(5px) !important;
561
+ }
562
+
563
+ /* 元数据区域样式 */
564
+ .metadata-box {
565
+ background: linear-gradient(135deg, rgba(248, 249, 250, 0.2), rgba(233, 236, 239, 0.9)) !important;
566
+ border-radius: 8px !important;
567
+ padding: 15px !important;
568
+ margin-top: 15px !important;
569
+ font-family: 'Courier New', monospace !important;
570
+ font-size: 12px !important;
571
+ color: #495057 !important;
572
+ border: 2px solid rgba(187, 109, 237, 0.2) !important;
573
+ backdrop-filter: blur(5px) !important;
574
+ white-space: pre-wrap !important;
575
+ overflow-y: auto !important;
576
+ max-height: 300px !important;
577
+ }
578
+
579
+ /* 滑块样式 */
580
+ .slider-container input[type="range"] {
581
+ accent-color: #bb6ded !important;
582
+ }
583
+
584
+ /* 响应式设计 */
585
+ @media (max-width: 768px) {
586
+ .main-content {
587
+ margin: 10px !important;
588
+ padding: 15px !important;
589
+ }
590
+
591
+ .title {
592
+ font-size: 1.5rem !important;
593
+ }
594
+ }
595
+
596
+ /* 强制覆盖Gradio默认样式 */
597
+ .gradio-container .gr-textbox,
598
+ .gradio-container .gr-radio-group,
599
+ .gradio-container .gr-slider,
600
+ .gradio-container .gr-number {
601
+ background: rgba(255, 255, 255, 0.95) !important;
602
+ border: 1px solid rgba(187, 109, 237, 0.5) !important;
603
+ border-radius: 8px !important;
604
+ }
605
+
606
+ .gradio-container .gr-radio-group label {
607
+ color: #2d2d2d !important;
608
+ background: transparent !important;
609
+ }
610
+ """
611
+
612
+ # ===== 创建UI =====
613
+ def create_interface():
614
+ with gr.Blocks(css=css, title="Adult NSFW AI Video Generator") as interface:
615
+ with gr.Column(elem_classes=["main-content"]):
616
+ # 简化标题
617
+ gr.HTML('<div class="title">🎬 Adult NSFW AI Video Generator</div>')
618
+
619
+ # 简化警告信息
620
+ gr.HTML('''
621
+ <div class="warning-box">
622
+ ⚠️ 18+ CONTENT WARNING ⚠️ | T2V Model: NSFW_Wan_14b
623
+ </div>
624
+ ''')
625
+
626
+ # 主要输入区域
627
  with gr.Row():
628
+ # 左侧:提示词输入
629
+ with gr.Column(scale=2):
630
+ prompt_input = gr.Textbox(
631
+ label="Detailed Video Prompt",
632
+ placeholder="Describe the video scene you want to generate...",
633
+ lines=12,
634
+ elem_classes=["prompt-box"]
635
+ )
636
+
637
+ negative_prompt_input = gr.Textbox(
638
+ label="Negative Prompt (Optional)",
639
+ placeholder="Things you don't want in the video...",
640
+ lines=4,
641
+ elem_classes=["prompt-box"]
642
+ )
643
+
644
+ # 右侧:控制选项
645
+ with gr.Column(scale=1):
646
+ # Style选项
647
+ with gr.Group(elem_classes=["controls-section"]):
648
+ style_input = gr.Radio(
649
+ label="Style Preset",
650
+ choices=list(STYLE_PRESETS.keys()),
651
+ value="Cinematic"
652
+ )
653
+
654
+ # 视频参数
655
+ with gr.Group(elem_classes=["controls-section"]):
656
+ gr.HTML("<b>📹 Video Settings</b>")
657
+ duration_input = gr.Slider(
658
+ label=f"Duration (seconds)",
659
+ minimum=1.0,
660
+ maximum=VIDEO_CONFIG["max_duration"],
661
+ value=VIDEO_CONFIG["default_duration"],
662
+ step=0.5
663
+ )
664
+
665
+ fps_input = gr.Slider(
666
+ label="FPS (Frames per Second)",
667
+ minimum=12,
668
+ maximum=VIDEO_CONFIG["max_fps"],
669
+ value=VIDEO_CONFIG["default_fps"],
670
+ step=6
671
+ )
672
+
673
+ # 分辨率设置
674
+ with gr.Group(elem_classes=["controls-section"]):
675
+ gr.HTML("<b>📐 Resolution</b>")
676
+ width_input = gr.Slider(
677
+ label="Width",
678
+ minimum=256,
679
+ maximum=VIDEO_CONFIG["max_resolution"],
680
+ value=VIDEO_CONFIG["default_width"],
681
+ step=64
682
+ )
683
+
684
+ height_input = gr.Slider(
685
+ label="Height",
686
+ minimum=256,
687
+ maximum=VIDEO_CONFIG["max_resolution"],
688
+ value=VIDEO_CONFIG["default_height"],
689
+ step=64
690
+ )
691
+
692
+ # Seed选项
693
+ with gr.Group(elem_classes=["controls-section"]):
694
+ seed_input = gr.Number(
695
+ label="Seed (-1 for random)",
696
+ value=-1,
697
+ precision=0
698
+ )
699
+
700
+ # 高级参数
701
+ with gr.Group(elem_classes=["controls-section"]):
702
+ gr.HTML("<b>⚙️ Advanced</b>")
703
+ steps_input = gr.Slider(
704
+ label="Steps",
705
+ minimum=10,
706
+ maximum=30,
707
+ value=20,
708
+ step=1
709
+ )
710
+
711
+ cfg_input = gr.Slider(
712
+ label="CFG Scale",
713
+ minimum=1.0,
714
+ maximum=15.0,
715
+ value=7.0,
716
+ step=0.1
717
+ )
718
+
719
+ # 生成按钮
720
+ generate_button = gr.Button(
721
+ "🎬 GENERATE VIDEO",
722
+ elem_classes=["generate-btn"],
723
+ variant="primary"
724
+ )
725
+
726
+ # 视频输出区域
727
+ with gr.Row():
728
+ video_output = gr.Video(
729
+ label="Generated Video (MP4)",
730
+ elem_classes=["video-output"],
731
+ show_label=True,
732
+ height=400
733
  )
734
+
735
+ # 下载区域和信息显示
736
+ with gr.Row():
737
+ with gr.Column(scale=1):
738
+ mp4_download = gr.File(
739
+ label="📱 Download MP4",
740
+ visible=False
741
+ )
742
+ with gr.Column(scale=1):
743
+ gif_download = gr.File(
744
+ label="🎞️ Download GIF",
745
+ visible=False
746
+ )
747
+
748
+ # 视频信息显示
749
+ with gr.Row():
750
+ generation_info = gr.Textbox(
751
+ label="Generation Info",
752
+ interactive=False,
753
+ elem_classes=["video-info"],
754
+ show_label=True,
755
+ visible=False
756
  )
757
+
758
+ # 元数据显示区域
759
  with gr.Row():
760
+ metadata_display = gr.Textbox(
761
+ label="Video Metadata (Copy to save)",
762
+ interactive=True,
763
+ elem_classes=["metadata-box"],
764
+ show_label=True,
765
+ lines=15,
766
+ visible=False,
767
+ placeholder="Generated video metadata will appear here..."
768
  )
769
+
770
+ # 生成视频的主要函数
771
+ def on_generate(prompt, style, neg_prompt, steps, cfg, seed, width, height, duration, fps):
772
+ mp4_path, gif_path, info, metadata = generate_video(
773
+ prompt, style, neg_prompt, steps, cfg, seed, width, height, duration, fps
774
+ )
775
+
776
+ if mp4_path is not None:
777
+ return (
778
+ mp4_path, # 视频输出
779
+ mp4_path if mp4_path and os.path.exists(mp4_path) else None, # MP4下载
780
+ gif_path if gif_path and os.path.exists(gif_path) else None, # GIF下载
781
+ gr.update(visible=True, value=info), # 显示生成信息
782
+ gr.update(visible=True, value=metadata), # 显示元数据
783
+ gr.update(visible=True), # 显示MP4下载
784
+ gr.update(visible=True) # 显示GIF下载
785
  )
786
+ else:
787
+ return (
788
+ None,
789
+ None,
790
+ None,
791
+ gr.update(visible=True, value=info if info else "Generation failed"),
792
+ gr.update(visible=False),
793
+ gr.update(visible=False),
794
+ gr.update(visible=False)
795
+ )
796
+
797
+ # 绑定生成事件
798
+ generate_button.click(
799
+ fn=on_generate,
800
+ inputs=[
801
+ prompt_input, style_input, negative_prompt_input,
802
+ steps_input, cfg_input, seed_input, width_input, height_input,
803
+ duration_input, fps_input
804
+ ],
805
+ outputs=[
806
+ video_output, mp4_download, gif_download,
807
+ generation_info, metadata_display,
808
+ mp4_download, gif_download
809
+ ],
810
+ show_progress=True
811
+ )
812
+
813
+ # 支持Enter键触发
814
+ prompt_input.submit(
815
+ fn=on_generate,
816
+ inputs=[
817
+ prompt_input, style_input, negative_prompt_input,
818
+ steps_input, cfg_input, seed_input, width_input, height_input,
819
+ duration_input, fps_input
820
+ ],
821
+ outputs=[
822
+ video_output, mp4_download, gif_download,
823
+ generation_info, metadata_display,
824
+ mp4_download, gif_download
825
+ ],
826
+ show_progress=True
827
+ )
828
+
829
+ # 启动时显示欢迎信息
830
+ interface.load(
831
+ fn=lambda: (
832
+ None, None, None,
833
+ gr.update(visible=False),
834
+ gr.update(visible=False),
835
+ gr.update(visible=False),
836
+ gr.update(visible=False)
837
+ ),
838
+ outputs=[
839
+ video_output, mp4_download, gif_download,
840
+ generation_info, metadata_display,
841
+ mp4_download, gif_download
842
+ ]
843
+ )
844
+
845
+ return interface
846
 
847
+ # ===== 启动应用 =====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
848
  if __name__ == "__main__":
849
+ print("🎬 Starting NSFW Video Generator...")
850
+ print(f"🔧 Official Wan T2V Model: {FIXED_MODEL}")
851
+ print("🔧 Using official Wan-AI Diffusers-compatible model")
852
+ print(f"🔧 Default Duration: {VIDEO_CONFIG['default_duration']}s")
853
+ print(f"🔧 Default Resolution: {VIDEO_CONFIG['default_width']}×{VIDEO_CONFIG['default_height']}")
854
+ print(f"🔧 Spaces GPU: {'✅ Available' if SPACES_AVAILABLE else '❌ Not Available'}")
855
+ print(f"🔧 Compel Library: {'✅ Available' if COMPEL_AVAILABLE else '❌ Not Available'}")
856
+ print(f"🔧 CUDA: {'✅ Available' if torch.cuda.is_available() else '❌ Not Available'}")
857
+
858
+ app = create_interface()
859
+ app.queue(max_size=5, default_concurrency_limit=1) # 降低并发以节省GPU
860
+
861
+ app.launch(
862
+ server_name="0.0.0.0",
863
+ server_port=7860,
864
+ show_error=True,
865
+ share=False
866
+ )