seawolf2357 commited on
Commit
358279a
ยท
verified ยท
1 Parent(s): 61c8fcd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +646 -245
app.py CHANGED
@@ -1,315 +1,716 @@
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
-
209
-
210
- gr.HTML("""
211
- <style>
212
- .gradio-container {
213
- background: linear-gradient(135deg, #fef9f3 0%, #f0e6fa 50%, #e6f0fa 100%) !important;
214
- }
215
- footer {display: none !important;}
216
- </style>
217
- <div style="text-align: center; margin-bottom: 20px;">
218
- <h1 style="color: #6b5b7a; font-size: 2.2rem; font-weight: 700; margin-bottom: 0.3rem;">
219
- ๐ŸŽฌ NSFW Uncensored "Image to Video"
220
- </h1>
221
- <p style="color: #8b7b9b; font-size: 1rem;">Powered by Wan 2.2 Model</p>
222
- <div style="margin-top: 10px;">
223
- <a href="https://huggingface.co/spaces/Heartsync/FREE-NSFW-HUB" target="_blank"><img src="https://img.shields.io/static/v1?label=FREE&message=NSFW%20HUB&color=%230000ff&labelColor=%23800080&logo=huggingface&logoColor=white&style=for-the-badge" alt="badge"></a>
224
- </div>
225
- </div>
226
- """)
227
-
228
-
229
- with gr.Row():
230
- with gr.Column(scale=1):
231
- input_image_component = gr.Image(
232
- type="pil",
233
- label="๐Ÿ“ท Upload Image",
234
- height=350
235
- )
236
  prompt_input = gr.Textbox(
237
- label="โœ๏ธ Prompt",
238
- value=default_prompt_i2v,
239
- placeholder="Describe the motion you want...",
240
- lines=3
241
  )
242
- duration_seconds_input = gr.Slider(
243
- minimum=MIN_DURATION,
244
- maximum=MAX_DURATION,
245
- step=0.5,
246
- value=3.5,
247
- label="โฑ๏ธ Duration (seconds)"
248
  )
249
-
250
- with gr.Accordion("โš™๏ธ Options", open=False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  negative_prompt_input = gr.Textbox(
252
- label="Negative Prompt",
253
- value=default_negative_prompt,
254
  lines=2
255
  )
256
- steps_slider = gr.Slider(
257
- minimum=1,
258
- maximum=30,
259
- step=1,
260
- value=6,
261
- label="Inference Steps"
262
  )
263
- guidance_scale_input = gr.Slider(
264
- minimum=0.0,
265
- maximum=10.0,
266
- step=0.5,
267
- value=1,
268
- label="Guidance Scale"
269
  )
270
- guidance_scale_2_input = gr.Slider(
271
- minimum=0.0,
272
- maximum=10.0,
273
- step=0.5,
274
- value=1,
275
- label="Guidance Scale 2"
276
  )
277
- seed_input = gr.Slider(
278
- label="Seed",
279
- minimum=0,
280
- maximum=MAX_SEED,
281
- step=1,
282
- value=42
 
 
 
 
 
 
 
283
  )
284
  randomize_seed_checkbox = gr.Checkbox(
285
- label="Randomize Seed",
286
  value=True
287
  )
288
-
289
- generate_button = gr.Button(
290
- "โœจ Generate Video",
291
- variant="primary"
292
- )
293
-
294
- with gr.Column(scale=1):
 
 
 
 
 
 
295
  video_output = gr.Video(
296
- label="๐ŸŽฅ Generated Video",
297
  autoplay=True,
298
- height=450
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  )
300
 
301
- ui_inputs = [
302
- input_image_component, prompt_input, steps_slider,
303
- negative_prompt_input, duration_seconds_input,
304
- guidance_scale_input, guidance_scale_2_input,
305
- seed_input, randomize_seed_checkbox
 
 
 
 
 
 
 
306
  ]
307
 
308
- generate_button.click(
309
- fn=generate_video,
310
- inputs=ui_inputs,
311
- outputs=[video_output, seed_input]
 
 
 
 
 
 
 
 
312
  )
313
 
 
 
314
  if __name__ == "__main__":
315
  demo.queue().launch()
 
 
 
 
 
 
 
 
 
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
  # =========================================================
18
+ # API CONFIGURATION
19
  # =========================================================
20
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
21
+ MODEL_ID = os.getenv("MODEL_ID")
22
+ HF_TOKEN = os.getenv("HF_TOKEN")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  # =========================================================
25
+ # MODEL LOADING
26
  # =========================================================
27
+ dtype = torch.bfloat16
28
+ device = "cuda" if torch.cuda.is_available() else "cpu"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ print("Loading pipeline...")
31
+ pipe = WanPipeline.from_pretrained(
32
+ MODEL_ID,
33
+ torch_dtype=dtype,
34
+ token=HF_TOKEN
35
+ ).to(device)
 
36
 
37
+ MAX_SEED = np.iinfo(np.int32).max
38
+ FIXED_FPS = 24
39
+ MIN_FRAMES = 8
40
+ MAX_FRAMES = 81
41
 
42
  # =========================================================
43
  # DEFAULT PROMPTS
44
  # =========================================================
45
+ default_prompt = "A beautiful sunset over the ocean with gentle waves."
46
  default_negative_prompt = "low quality, worst quality, blurry, distorted, deformed, ugly, bad anatomy"
47
 
48
  # =========================================================
49
+ # PROMPT ENHANCEMENT SYSTEM PROMPT
50
  # =========================================================
51
+ ENHANCE_SYSTEM_PROMPT = """You are a professional video prompt engineer. Your task is to enhance user prompts for AI video generation.
 
 
 
 
 
 
 
 
 
52
 
53
+ Rules:
54
+ 1. Add vivid visual details (lighting, colors, textures, atmosphere)
55
+ 2. Include camera movements (pan, zoom, tracking shot, etc.)
56
+ 3. Describe motion and dynamics clearly
57
+ 4. Keep the enhanced prompt concise but detailed (max 150 words)
58
+ 5. Maintain the original intent of the user's prompt
59
+ 6. Output ONLY the enhanced prompt, nothing else
 
60
 
61
+ Example:
62
+ User: "A cat playing"
63
+ Enhanced: "A fluffy orange tabby cat playfully batting at a dangling yarn ball, soft afternoon sunlight streaming through a window creating warm golden highlights on its fur, smooth tracking shot following the cat's graceful movements, shallow depth of field with bokeh background, cozy living room setting with warm ambient lighting"
64
+ """
 
 
65
 
 
 
66
 
67
+ # =========================================================
68
+ # PROMPT ENHANCEMENT FUNCTION
69
+ # =========================================================
70
+ def enhance_prompt(prompt: str) -> str:
71
+ """Enhance the user prompt using Groq LLM API."""
72
+ if not GROQ_API_KEY:
73
+ return prompt + " (API key not configured - using original prompt)"
74
+
75
+ try:
76
+ client = Groq(api_key=GROQ_API_KEY)
77
+
78
+ enhanced_text = ""
79
+ completion = client.chat.completions.create(
80
+ model="meta-llama/llama-4-scout-17b-16e-instruct",
81
+ messages=[
82
+ {
83
+ "role": "system",
84
+ "content": ENHANCE_SYSTEM_PROMPT
85
+ },
86
+ {
87
+ "role": "user",
88
+ "content": f"Enhance this video generation prompt: {prompt}"
89
+ }
90
+ ],
91
+ temperature=0.7,
92
+ max_completion_tokens=512,
93
+ top_p=1,
94
+ stream=True,
95
+ stop=None
96
+ )
97
+
98
+ for chunk in completion:
99
+ if chunk.choices[0].delta.content:
100
+ enhanced_text += chunk.choices[0].delta.content
101
+
102
+ return enhanced_text.strip() if enhanced_text.strip() else prompt
103
+
104
+ except Exception as e:
105
+ print(f"Prompt enhancement error: {e}")
106
+ return prompt
107
 
 
108
 
109
  # =========================================================
110
+ # HELPER FUNCTIONS
111
  # =========================================================
112
+ def get_num_frames(duration_seconds: float) -> int:
113
+ """Calculate number of frames based on duration."""
114
+ return max(MIN_FRAMES, min(MAX_FRAMES, int(duration_seconds * FIXED_FPS)))
115
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  # =========================================================
118
  # MAIN GENERATION FUNCTION
119
  # =========================================================
120
+ @spaces.GPU
121
  def generate_video(
122
+ prompt: str,
123
+ negative_prompt: str = default_negative_prompt,
124
+ enhance_prompt_option: bool = False,
125
+ duration_seconds: float = 3.0,
126
+ guidance_scale: float = 7.5,
127
+ num_inference_steps: int = 20,
128
+ height: int = 480,
129
+ width: int = 832,
130
+ seed: int = 0,
131
+ randomize_seed: bool = True,
132
  progress=gr.Progress(track_tqdm=True),
133
+ ) -> Tuple[str, int, str]:
134
+ """Generate video from text prompt."""
135
+
136
+ if not prompt.strip():
137
+ raise gr.Error("Please enter a prompt.")
138
+
139
+ # Enhance prompt if option is enabled
140
+ final_prompt = prompt
141
+ if enhance_prompt_option:
142
+ final_prompt = enhance_prompt(prompt)
143
+ print(f"Enhanced Prompt: {final_prompt}")
144
+
145
+ # Handle seed
146
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
147
+ generator = torch.Generator(device=device).manual_seed(current_seed)
148
+
149
+ # Calculate frames
150
+ num_frames = get_num_frames(duration_seconds)
151
+
152
+ # Generate video
153
+ output_frames = pipe(
154
+ prompt=final_prompt,
155
  negative_prompt=negative_prompt,
156
+ height=height,
157
+ width=width,
158
  num_frames=num_frames,
159
+ guidance_scale=guidance_scale,
160
+ num_inference_steps=num_inference_steps,
161
+ generator=generator,
 
162
  ).frames[0]
163
+
164
+ # Export to video file
165
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
166
  video_path = tmpfile.name
167
+ export_to_video(output_frames, video_path, fps=FIXED_FPS)
168
+
169
+ # Build info log
170
+ info_log = f"""โœ… VIDEO GENERATION COMPLETE!
171
+ {'=' * 50}
172
+ ๐ŸŽฌ Video Info:
173
+ โ€ข Duration: {duration_seconds:.1f} seconds
174
+ โ€ข Total Frames: {num_frames}
175
+ โ€ข FPS: {FIXED_FPS}
176
+ โ€ข Resolution: {width} x {height}
177
+ {'=' * 50}
178
+ โš™๏ธ Generation Settings:
179
+ โ€ข Guidance Scale: {guidance_scale}
180
+ โ€ข Inference Steps: {num_inference_steps}
181
+ โ€ข Seed: {current_seed}
182
+ โ€ข Prompt Enhanced: {'Yes' if enhance_prompt_option else 'No'}
183
+ {'=' * 50}
184
+ ๐Ÿ’พ Ready to download!"""
185
+
186
+ return video_path, current_seed, final_prompt, info_log
187
+
188
+
189
+ # ============================================
190
+ # ๐ŸŽจ Comic Classic Theme - Toon Playground
191
+ # ============================================
192
+
193
+ css = """
194
+ /* ===== ๐ŸŽจ Google Fonts Import ===== */
195
+ @import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap');
196
+
197
+ /* ===== ๐ŸŽจ Comic Classic ๋ฐฐ๊ฒฝ - ๋นˆํ‹ฐ์ง€ ํŽ˜์ดํผ + ๋„ํŠธ ํŒจํ„ด ===== */
198
+ .gradio-container {
199
+ background-color: #FEF9C3 !important;
200
+ background-image:
201
+ radial-gradient(#1F2937 1px, transparent 1px) !important;
202
+ background-size: 20px 20px !important;
203
+ min-height: 100vh !important;
204
+ font-family: 'Comic Neue', cursive, sans-serif !important;
205
+ }
206
+
207
+ /* ===== ํ—ˆ๊น…ํŽ˜์ด์Šค ์ƒ๋‹จ ์š”์†Œ ์ˆจ๊น€ ===== */
208
+ .huggingface-space-header,
209
+ #space-header,
210
+ .space-header,
211
+ [class*="space-header"],
212
+ .svelte-1ed2p3z,
213
+ .space-header-badge,
214
+ .header-badge,
215
+ [data-testid="space-header"],
216
+ .svelte-kqij2n,
217
+ .svelte-1ax1toq,
218
+ .embed-container > div:first-child {
219
+ display: none !important;
220
+ visibility: hidden !important;
221
+ height: 0 !important;
222
+ width: 0 !important;
223
+ overflow: hidden !important;
224
+ opacity: 0 !important;
225
+ pointer-events: none !important;
226
+ }
227
+
228
+ /* ===== Footer ์™„์ „ ์ˆจ๊น€ ===== */
229
+ footer,
230
+ .footer,
231
+ .gradio-container footer,
232
+ .built-with,
233
+ [class*="footer"],
234
+ .gradio-footer,
235
+ .main-footer,
236
+ div[class*="footer"],
237
+ .show-api,
238
+ .built-with-gradio,
239
+ a[href*="gradio.app"],
240
+ a[href*="huggingface.co/spaces"] {
241
+ display: none !important;
242
+ visibility: hidden !important;
243
+ height: 0 !important;
244
+ padding: 0 !important;
245
+ margin: 0 !important;
246
+ }
247
+
248
+ /* ===== ๋ฉ”์ธ ์ปจํ…Œ์ด๋„ˆ ===== */
249
+ #col-container {
250
+ max-width: 1000px;
251
+ margin: 0 auto;
252
+ }
253
+
254
+ /* ===== ๐ŸŽจ ํ—ค๋” ํƒ€์ดํ‹€ - ์ฝ”๋ฏน ์Šคํƒ€์ผ ===== */
255
+ .header-text h1 {
256
+ font-family: 'Bangers', cursive !important;
257
+ color: #1F2937 !important;
258
+ font-size: 3.5rem !important;
259
+ font-weight: 400 !important;
260
+ text-align: center !important;
261
+ margin-bottom: 0.5rem !important;
262
+ text-shadow:
263
+ 4px 4px 0px #FACC15,
264
+ 6px 6px 0px #1F2937 !important;
265
+ letter-spacing: 3px !important;
266
+ -webkit-text-stroke: 2px #1F2937 !important;
267
+ }
268
+
269
+ /* ===== ๐ŸŽจ ์„œ๋ธŒํƒ€์ดํ‹€ ===== */
270
+ .subtitle {
271
+ text-align: center !important;
272
+ font-family: 'Comic Neue', cursive !important;
273
+ font-size: 1.2rem !important;
274
+ color: #1F2937 !important;
275
+ margin-bottom: 1.5rem !important;
276
+ font-weight: 700 !important;
277
+ }
278
+
279
+ /* ===== ๐ŸŽจ ์นด๋“œ/ํŒจ๋„ - ๋งŒํ™” ํ”„๋ ˆ์ž„ ์Šคํƒ€์ผ ===== */
280
+ .gr-panel,
281
+ .gr-box,
282
+ .gr-form,
283
+ .block,
284
+ .gr-group {
285
+ background: #FFFFFF !important;
286
+ border: 3px solid #1F2937 !important;
287
+ border-radius: 8px !important;
288
+ box-shadow: 6px 6px 0px #1F2937 !important;
289
+ transition: all 0.2s ease !important;
290
+ }
291
+
292
+ .gr-panel:hover,
293
+ .block:hover {
294
+ transform: translate(-2px, -2px) !important;
295
+ box-shadow: 8px 8px 0px #1F2937 !important;
296
+ }
297
+
298
+ /* ===== ๐ŸŽจ ์ž…๋ ฅ ํ•„๋“œ (Textbox) ===== */
299
+ textarea,
300
+ input[type="text"],
301
+ input[type="number"] {
302
+ background: #FFFFFF !important;
303
+ border: 3px solid #1F2937 !important;
304
+ border-radius: 8px !important;
305
+ color: #1F2937 !important;
306
+ font-family: 'Comic Neue', cursive !important;
307
+ font-size: 1rem !important;
308
+ font-weight: 700 !important;
309
+ transition: all 0.2s ease !important;
310
+ }
311
+
312
+ textarea:focus,
313
+ input[type="text"]:focus,
314
+ input[type="number"]:focus {
315
+ border-color: #3B82F6 !important;
316
+ box-shadow: 4px 4px 0px #3B82F6 !important;
317
+ outline: none !important;
318
+ }
319
+
320
+ textarea::placeholder {
321
+ color: #9CA3AF !important;
322
+ font-weight: 400 !important;
323
+ }
324
+
325
+ /* ===== ๐ŸŽจ Primary ๋ฒ„ํŠผ - ์ฝ”๋ฏน ๋ธ”๋ฃจ ===== */
326
+ .gr-button-primary,
327
+ button.primary,
328
+ .gr-button.primary {
329
+ background: #3B82F6 !important;
330
+ border: 3px solid #1F2937 !important;
331
+ border-radius: 8px !important;
332
+ color: #FFFFFF !important;
333
+ font-family: 'Bangers', cursive !important;
334
+ font-weight: 400 !important;
335
+ font-size: 1.3rem !important;
336
+ letter-spacing: 2px !important;
337
+ padding: 14px 28px !important;
338
+ box-shadow: 5px 5px 0px #1F2937 !important;
339
+ transition: all 0.1s ease !important;
340
+ text-shadow: 1px 1px 0px #1F2937 !important;
341
+ }
342
+
343
+ .gr-button-primary:hover,
344
+ button.primary:hover,
345
+ .gr-button.primary:hover {
346
+ background: #2563EB !important;
347
+ transform: translate(-2px, -2px) !important;
348
+ box-shadow: 7px 7px 0px #1F2937 !important;
349
+ }
350
+
351
+ .gr-button-primary:active,
352
+ button.primary:active,
353
+ .gr-button.primary:active {
354
+ transform: translate(3px, 3px) !important;
355
+ box-shadow: 2px 2px 0px #1F2937 !important;
356
+ }
357
+
358
+ /* ===== ๐ŸŽจ Secondary ๋ฒ„ํŠผ - ์ฝ”๋ฏน ๋ ˆ๋“œ ===== */
359
+ .gr-button-secondary,
360
+ button.secondary,
361
+ .generate-btn {
362
+ background: #EF4444 !important;
363
+ border: 3px solid #1F2937 !important;
364
+ border-radius: 8px !important;
365
+ color: #FFFFFF !important;
366
+ font-family: 'Bangers', cursive !important;
367
+ font-weight: 400 !important;
368
+ font-size: 1.1rem !important;
369
+ letter-spacing: 1px !important;
370
+ box-shadow: 4px 4px 0px #1F2937 !important;
371
+ transition: all 0.1s ease !important;
372
+ text-shadow: 1px 1px 0px #1F2937 !important;
373
+ }
374
+
375
+ .gr-button-secondary:hover,
376
+ button.secondary:hover,
377
+ .generate-btn:hover {
378
+ background: #DC2626 !important;
379
+ transform: translate(-2px, -2px) !important;
380
+ box-shadow: 6px 6px 0px #1F2937 !important;
381
+ }
382
+
383
+ .gr-button-secondary:active,
384
+ button.secondary:active,
385
+ .generate-btn:active {
386
+ transform: translate(2px, 2px) !important;
387
+ box-shadow: 2px 2px 0px #1F2937 !important;
388
+ }
389
+
390
+ /* ===== ๐ŸŽจ ๋กœ๊ทธ ์ถœ๋ ฅ ์˜์—ญ ===== */
391
+ .info-log textarea {
392
+ background: #1F2937 !important;
393
+ color: #10B981 !important;
394
+ font-family: 'Courier New', monospace !important;
395
+ font-size: 0.9rem !important;
396
+ font-weight: 400 !important;
397
+ border: 3px solid #10B981 !important;
398
+ border-radius: 8px !important;
399
+ box-shadow: 4px 4px 0px #10B981 !important;
400
+ }
401
+
402
+ /* ===== ๐ŸŽจ ๋น„๋””์˜ค ์ถœ๋ ฅ ์˜์—ญ ===== */
403
+ .video-output video {
404
+ border: 4px solid #1F2937 !important;
405
+ border-radius: 8px !important;
406
+ box-shadow: 8px 8px 0px #1F2937 !important;
407
+ }
408
+
409
+ /* ===== ๐ŸŽจ ์•„์ฝ”๋””์–ธ - ๋งํ’์„  ์Šคํƒ€์ผ ===== */
410
+ .gr-accordion {
411
+ background: #FACC15 !important;
412
+ border: 3px solid #1F2937 !important;
413
+ border-radius: 8px !important;
414
+ box-shadow: 4px 4px 0px #1F2937 !important;
415
+ }
416
+
417
+ .gr-accordion-header {
418
+ color: #1F2937 !important;
419
+ font-family: 'Comic Neue', cursive !important;
420
+ font-weight: 700 !important;
421
+ font-size: 1.1rem !important;
422
+ }
423
+
424
+ /* ===== ๐ŸŽจ ์ด๋ฏธ์ง€ ์ถœ๋ ฅ ์˜์—ญ ===== */
425
+ .gr-image,
426
+ .image-container {
427
+ border: 4px solid #1F2937 !important;
428
+ border-radius: 8px !important;
429
+ box-shadow: 8px 8px 0px #1F2937 !important;
430
+ overflow: hidden !important;
431
+ background: #FFFFFF !important;
432
+ }
433
+
434
+ /* ===== ๐ŸŽจ ๋ผ๋ฒจ ์Šคํƒ€์ผ ===== */
435
+ label,
436
+ .gr-input-label,
437
+ .gr-block-label {
438
+ color: #1F2937 !important;
439
+ font-family: 'Comic Neue', cursive !important;
440
+ font-weight: 700 !important;
441
+ font-size: 1rem !important;
442
+ }
443
+
444
+ span.gr-label {
445
+ color: #1F2937 !important;
446
+ }
447
+
448
+ /* ===== ๐ŸŽจ ์ฒดํฌ๋ฐ•์Šค ์Šคํƒ€์ผ ===== */
449
+ input[type="checkbox"] {
450
+ accent-color: #3B82F6 !important;
451
+ width: 20px !important;
452
+ height: 20px !important;
453
+ }
454
+
455
+ /* ===== ๐ŸŽจ ์Šฌ๋ผ์ด๋” ์Šคํƒ€์ผ ===== */
456
+ input[type="range"] {
457
+ accent-color: #3B82F6 !important;
458
+ }
459
+
460
+ /* ===== ๐ŸŽจ ์ •๋ณด ํ…์ŠคํŠธ ===== */
461
+ .gr-info,
462
+ .info {
463
+ color: #6B7280 !important;
464
+ font-family: 'Comic Neue', cursive !important;
465
+ font-size: 0.9rem !important;
466
+ }
467
+
468
+ /* ===== ๐ŸŽจ ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” ===== */
469
+ .progress-bar,
470
+ .gr-progress-bar {
471
+ background: #3B82F6 !important;
472
+ border: 2px solid #1F2937 !important;
473
+ border-radius: 4px !important;
474
+ }
475
+
476
+ /* ===== ๐ŸŽจ ์Šคํฌ๋กค๋ฐ” - ์ฝ”๋ฏน ์Šคํƒ€์ผ ===== */
477
+ ::-webkit-scrollbar {
478
+ width: 12px;
479
+ height: 12px;
480
+ }
481
+
482
+ ::-webkit-scrollbar-track {
483
+ background: #FEF9C3;
484
+ border: 2px solid #1F2937;
485
+ }
486
+
487
+ ::-webkit-scrollbar-thumb {
488
+ background: #3B82F6;
489
+ border: 2px solid #1F2937;
490
+ border-radius: 0px;
491
+ }
492
+
493
+ ::-webkit-scrollbar-thumb:hover {
494
+ background: #EF4444;
495
+ }
496
+
497
+ /* ===== ๐ŸŽจ ์„ ํƒ ํ•˜์ด๋ผ์ดํŠธ ===== */
498
+ ::selection {
499
+ background: #FACC15;
500
+ color: #1F2937;
501
+ }
502
+
503
+ /* ===== ๐ŸŽจ ๋งํฌ ์Šคํƒ€์ผ ===== */
504
+ a {
505
+ color: #3B82F6 !important;
506
+ text-decoration: none !important;
507
+ font-weight: 700 !important;
508
+ }
509
+
510
+ a:hover {
511
+ color: #EF4444 !important;
512
+ }
513
+
514
+ /* ===== ๐ŸŽจ Row/Column ๊ฐ„๊ฒฉ ===== */
515
+ .gr-row {
516
+ gap: 1.5rem !important;
517
+ }
518
+
519
+ .gr-column {
520
+ gap: 1rem !important;
521
+ }
522
+
523
+ /* ===== ๋ฐ˜์‘ํ˜• ์กฐ์ • ===== */
524
+ @media (max-width: 768px) {
525
+ .header-text h1 {
526
+ font-size: 2.2rem !important;
527
+ text-shadow:
528
+ 3px 3px 0px #FACC15,
529
+ 4px 4px 0px #1F2937 !important;
530
+ }
531
+
532
+ .gr-button-primary,
533
+ button.primary {
534
+ padding: 12px 20px !important;
535
+ font-size: 1.1rem !important;
536
+ }
537
+
538
+ .gr-panel,
539
+ .block {
540
+ box-shadow: 4px 4px 0px #1F2937 !important;
541
+ }
542
+ }
543
+
544
+ /* ===== ๐ŸŽจ ๋‹คํฌ๋ชจ๋“œ ๋น„ํ™œ์„ฑํ™” (์ฝ”๋ฏน์€ ๋ฐ์•„์•ผ ํ•จ) ===== */
545
+ @media (prefers-color-scheme: dark) {
546
+ .gradio-container {
547
+ background-color: #FEF9C3 !important;
548
+ }
549
+ }
550
+ """
551
+
552
 
553
  # =========================================================
554
+ # GRADIO UI - Comic Classic Theme
555
  # =========================================================
556
  with gr.Blocks() as demo:
557
 
558
+ # CSS ์‚ฝ์ž…
559
+ gr.HTML(f"<style>{css}</style>")
560
+
561
+ # Header Title
562
+ gr.Markdown(
563
+ """
564
+ # ๐ŸŽฌ UNCENSORED TEXT TO VIDEO ๐ŸŽฅ
565
+ """,
566
+ elem_classes="header-text"
567
+ )
568
+
569
+ gr.Markdown(
570
+ """
571
+ <p class="subtitle">โœจ Transform your ideas into stunning AI-generated videos! ๐Ÿš€</p>
572
+ """,
573
+ )
574
+
575
+ with gr.Row(equal_height=False):
576
+ # Left column - Input
577
+ with gr.Column(scale=1, min_width=320):
 
 
 
 
 
 
 
 
578
  prompt_input = gr.Textbox(
579
+ label="โœ๏ธ Your Prompt",
580
+ value=default_prompt,
581
+ placeholder="Describe the video you want to create...",
582
+ lines=4
583
  )
584
+
585
+ enhance_prompt_checkbox = gr.Checkbox(
586
+ label="โœจ Enhance Prompt with AI",
587
+ value=False,
588
+ info="Use AI to automatically enhance your prompt for better results"
 
589
  )
590
+
591
+ duration_slider = gr.Slider(
592
+ label="โฑ๏ธ Duration (seconds)",
593
+ minimum=1.0,
594
+ maximum=4.0,
595
+ step=0.5,
596
+ value=3.0
597
+ )
598
+
599
+ generate_btn = gr.Button(
600
+ "๐ŸŽฌ GENERATE VIDEO! ๐Ÿš€",
601
+ variant="primary",
602
+ size="lg",
603
+ elem_classes="generate-btn"
604
+ )
605
+
606
+ with gr.Accordion("โš™๏ธ Advanced Options", open=False):
607
  negative_prompt_input = gr.Textbox(
608
+ label="Negative Prompt",
609
+ value=default_negative_prompt,
610
  lines=2
611
  )
612
+ guidance_scale_slider = gr.Slider(
613
+ label="Guidance Scale",
614
+ minimum=1.0,
615
+ maximum=15.0,
616
+ step=0.5,
617
+ value=7.5
618
  )
619
+ num_inference_steps_slider = gr.Slider(
620
+ label="Inference Steps",
621
+ minimum=10,
622
+ maximum=50,
623
+ step=1,
624
+ value=20
625
  )
626
+ height_slider = gr.Slider(
627
+ label="Height",
628
+ minimum=256,
629
+ maximum=720,
630
+ step=16,
631
+ value=480
632
  )
633
+ width_slider = gr.Slider(
634
+ label="Width",
635
+ minimum=256,
636
+ maximum=1280,
637
+ step=16,
638
+ value=832
639
+ )
640
+ seed_slider = gr.Slider(
641
+ label="Seed",
642
+ minimum=0,
643
+ maximum=MAX_SEED,
644
+ step=1,
645
+ value=0
646
  )
647
  randomize_seed_checkbox = gr.Checkbox(
648
+ label="Randomize Seed",
649
  value=True
650
  )
651
+
652
+ with gr.Accordion("๐Ÿ“œ Generation Info", open=True):
653
+ info_log = gr.Textbox(
654
+ label="",
655
+ placeholder="Generation info will appear here...",
656
+ lines=12,
657
+ max_lines=20,
658
+ interactive=False,
659
+ elem_classes="info-log"
660
+ )
661
+
662
+ # Right column - Output
663
+ with gr.Column(scale=1, min_width=320):
664
  video_output = gr.Video(
665
+ label="๐ŸŽฅ Generated Video",
666
  autoplay=True,
667
+ height=400,
668
+ elem_classes="video-output"
669
+ )
670
+
671
+ final_prompt_output = gr.Textbox(
672
+ label="๐Ÿ“ Final Prompt Used",
673
+ interactive=False,
674
+ lines=3
675
+ )
676
+
677
+ gr.Markdown(
678
+ """
679
+ <p style="text-align: center; margin-top: 10px; font-weight: 700; color: #1F2937;">
680
+ ๐Ÿ’ก Right-click on the video to save, or use the download button!
681
+ </p>
682
+ """
683
  )
684
 
685
+ # Define inputs and outputs
686
+ inputs = [
687
+ prompt_input,
688
+ negative_prompt_input,
689
+ enhance_prompt_checkbox,
690
+ duration_slider,
691
+ guidance_scale_slider,
692
+ num_inference_steps_slider,
693
+ height_slider,
694
+ width_slider,
695
+ seed_slider,
696
+ randomize_seed_checkbox,
697
  ]
698
 
699
+ outputs = [
700
+ video_output,
701
+ seed_slider,
702
+ final_prompt_output,
703
+ info_log,
704
+ ]
705
+
706
+ # Generate button click
707
+ generate_btn.click(
708
+ fn=generate_video,
709
+ inputs=inputs,
710
+ outputs=outputs,
711
  )
712
 
713
+ gr.api(generate_video, api_name="generate_video")
714
+
715
  if __name__ == "__main__":
716
  demo.queue().launch()