inoculatemedia commited on
Commit
95a2ebc
·
verified ·
1 Parent(s): ef45de2

Update app.py from anycoder

Browse files
Files changed (1) hide show
  1. app.py +253 -601
app.py CHANGED
@@ -1,610 +1,262 @@
1
  import gradio as gr
2
  import numpy as np
3
- from PIL import Image, ImageDraw, ImageFont
4
- import io
5
- import time
6
- import random
7
- import base64
8
- from typing import List, Dict, Any
9
- from functools import wraps
10
-
11
- # ZeroGPU decorator
12
- def zero_gpu(func):
13
- """
14
- Decorator to indicate that this function can run on CPU-only environments.
15
- This is useful for deployment on platforms without GPU access.
16
- """
17
- @wraps(func)
18
- def wrapper(*args, **kwargs):
19
- return func(*args, **kwargs)
20
- wrapper.zero_gpu = True
21
- return wrapper
22
-
23
- # Configuration
24
- CONFIG = {
25
- "required_images": 10,
26
- "playback_fps": 30,
27
- "image_size": (512, 512)
28
- }
29
-
30
- class ImageSequenceGenerator:
31
- def __init__(self):
32
- self.images: List[str] = []
33
- self.is_playing: bool = False
34
- self.current_frame: int = 0
35
- self.playback_interval: Any = None
36
- self.is_generating: bool = False
37
-
38
- @zero_gpu
39
- def generate_random_image(self, prompt: str) -> str:
40
- """Generate a random colorful image with the prompt text"""
41
- width, height = CONFIG["image_size"]
42
- canvas = Image.new('RGB', (width, height))
43
-
44
- # Generate random colors
45
- colors = []
46
- for _ in range(5):
47
- colors.append((random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
48
-
49
- # Create gradient
50
- for y in range(height):
51
- # Calculate gradient position
52
- pos = y / height
53
- # Get interpolated color
54
- if len(colors) == 1:
55
- color = colors[0]
56
- else:
57
- # Find which segment we're in
58
- segment = pos * (len(colors) - 1)
59
- idx = int(segment)
60
- fraction = segment - idx
61
-
62
- # Interpolate between colors
63
- c1 = colors[idx]
64
- c2 = colors[idx + 1] if idx + 1 < len(colors) else colors[idx]
65
-
66
- color = (
67
- int(c1[0] + (c2[0] - c1[0]) * fraction),
68
- int(c1[1] + (c2[1] - c1[1]) * fraction),
69
- int(c1[2] + (c2[2] - c1[2]) * fraction)
70
- )
71
-
72
- # Draw horizontal line
73
- for x in range(width):
74
- canvas.putpixel((x, y), color)
75
-
76
- # Add random shapes
77
- draw = ImageDraw.Draw(canvas)
78
- for _ in range(10):
79
- color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), 180)
80
- size = random.randint(50, 150)
81
- x = random.randint(0, width - size)
82
- y = random.randint(0, height - size)
83
-
84
- if random.random() > 0.5:
85
- # Circle
86
- draw.ellipse([x, y, x + size, y + size], fill=color)
87
- else:
88
- # Rectangle
89
- draw.rectangle([x, y, x + size, y + size], fill=color)
90
-
91
- # Add prompt text
92
- if prompt:
93
- try:
94
- font_size = 24
95
- font = ImageFont.truetype("arial.ttf", font_size) if hasattr(ImageFont, 'truetype') else ImageFont.load_default()
96
- text = prompt[:30] + ("..." if len(prompt) > 30 else "")
97
- text_width, text_height = draw.textsize(text, font=font)
98
-
99
- # Add text with white outline
100
- for dx in [-1, 0, 1]:
101
- for dy in [-1, 0, 1]:
102
- if dx != 0 or dy != 0:
103
- draw.text(
104
- ((width - text_width) // 2 + dx, (height - text_height) // 2 + dy),
105
- text,
106
- font=font,
107
- fill=(0, 0, 0)
108
- )
109
-
110
- draw.text(
111
- ((width - text_width) // 2, (height - text_height) // 2),
112
- text,
113
- font=font,
114
- fill=(255, 255, 255)
115
- )
116
- except:
117
- pass # Skip text if font issues
118
-
119
- # Convert to base64 string
120
- buffered = io.BytesIO()
121
- canvas.save(buffered, format="PNG")
122
- img_str = f"data:image/png;base64,{base64.b64encode(buffered.getvalue()).decode()}"
123
- return img_str
124
-
125
- @zero_gpu
126
- def add_image_to_buffer(self, image_data: str):
127
- """Add image to buffer"""
128
- self.images.append(image_data)
129
- return self.get_state()
130
-
131
- @zero_gpu
132
- def remove_image_from_buffer(self, index: int):
133
- """Remove image from buffer"""
134
- if 0 <= index < len(self.images):
135
- self.images.pop(index)
136
- return self.get_state()
137
-
138
- @zero_gpu
139
- def clear_buffer(self):
140
- """Clear the entire buffer"""
141
- self.images = []
142
- self.stop_sequence()
143
- return self.get_state()
144
-
145
- @zero_gpu
146
- def play_sequence(self):
147
- """Start playing the sequence"""
148
- if self.is_playing or len(self.images) < CONFIG["required_images"]:
149
- return self.get_state()
150
-
151
- self.is_playing = True
152
- self.current_frame = 0
153
- return self.get_state()
154
-
155
- @zero_gpu
156
- def stop_sequence(self):
157
- """Stop playing the sequence"""
158
- if not self.is_playing:
159
- return self.get_state()
160
-
161
- self.is_playing = False
162
- self.current_frame = 0
163
- return self.get_state()
164
-
165
- @zero_gpu
166
- def get_current_frame(self):
167
- """Get the current frame for display"""
168
- if not self.is_playing or not self.images:
169
- return None
170
-
171
- frame_data = self.images[self.current_frame]
172
- self.current_frame = (self.current_frame + 1) % len(self.images)
173
- return frame_data
174
-
175
- @zero_gpu
176
- def get_state(self) -> Dict[str, Any]:
177
- """Get current state for UI updates"""
178
- return {
179
- "buffer_count": len(self.images),
180
- "can_play": len(self.images) >= CONFIG["required_images"] and not self.is_playing,
181
- "is_playing": self.is_playing,
182
- "is_generating": self.is_generating,
183
- "images": self.images
184
- }
185
-
186
- @zero_gpu
187
- def generate_image(self, prompt: str, negative_prompt: str, steps: int, guidance_scale: float, seed: int, batch_size: int):
188
- """Generate a new image"""
189
- if self.is_generating:
190
- return self.get_state()
191
-
192
- if not prompt.strip():
193
- raise gr.Error("Please enter a prompt")
194
-
195
- self.is_generating = True
196
-
197
- # Simulate generation progress
198
- for i in range(10):
199
- time.sleep(0.3)
200
- yield self.get_state()
201
-
202
- try:
203
- # Generate image
204
- image_data = self.generate_random_image(prompt)
205
- self.add_image_to_buffer(image_data)
206
- except Exception as e:
207
- raise gr.Error(f"Failed to generate image: {str(e)}")
208
- finally:
209
- self.is_generating = False
210
-
211
- return self.get_state()
212
-
213
- # Create the Gradio interface
214
- def create_gradio_app():
215
- generator = ImageSequenceGenerator()
216
-
217
- # Custom CSS for styling
218
- custom_css = """
219
- .gradio-container {
220
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
221
- }
222
-
223
- .main-title {
224
- text-align: center;
225
- color: #6c5ce7;
226
- margin-bottom: 2rem;
227
- }
228
-
229
- .status-container {
230
- display: flex;
231
- justify-content: space-between;
232
- margin: 1rem 0;
233
- gap: 1rem;
234
- }
235
-
236
- .status-item {
237
- flex: 1;
238
- padding: 1rem;
239
- background: #f8f9fa;
240
- border-radius: 8px;
241
- text-align: center;
242
- }
243
-
244
- .status-label {
245
- font-size: 0.9rem;
246
- color: #666;
247
- margin-bottom: 0.3rem;
248
- }
249
-
250
- .status-value {
251
- font-weight: 700;
252
- color: #6c5ce7;
253
- font-size: 1.2rem;
254
- }
255
-
256
- .image-buffer {
257
- display: grid;
258
- grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
259
- gap: 1rem;
260
- margin: 1rem 0;
261
- max-height: 400px;
262
- overflow-y: auto;
263
- padding: 0.5rem;
264
- border: 2px dashed #e0e0e0;
265
- border-radius: 8px;
266
- }
267
-
268
- .buffer-item {
269
- position: relative;
270
- aspect-ratio: 1;
271
- border-radius: 8px;
272
- overflow: hidden;
273
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
274
- transition: transform 0.3s ease;
275
- }
276
-
277
- .buffer-item:hover {
278
- transform: scale(1.05);
279
- z-index: 10;
280
- }
281
-
282
- .buffer-item img {
283
- width: 100%;
284
- height: 100%;
285
- object-fit: cover;
286
- }
287
 
288
- .player-display {
289
- width: 100%;
290
- aspect-ratio: 16/9;
291
- background: #f0f0f0;
292
- border-radius: 8px;
293
- overflow: hidden;
294
- position: relative;
295
- display: flex;
296
- align-items: center;
297
- justify-content: center;
298
- margin: 1rem 0;
299
- }
300
-
301
- .player-display img {
302
- width: 100%;
303
- height: 100%;
304
- object-fit: contain;
305
- }
306
-
307
- .empty-state {
308
- text-align: center;
309
- padding: 2rem;
310
- color: #999;
311
- }
312
-
313
- .empty-state i {
314
- font-size: 3rem;
315
- margin-bottom: 1rem;
316
- color: #ddd;
317
- }
318
-
319
- .progress-bar {
320
- height: 8px;
321
- background: #e0e0e0;
322
- border-radius: 4px;
323
- overflow: hidden;
324
- margin: 1rem 0;
325
- }
326
-
327
- .progress {
328
- height: 100%;
329
- background: #6c5ce7;
330
- width: 0%;
331
- transition: width 0.3s ease;
332
- }
333
 
334
- .player-controls {
335
- display: flex;
336
- gap: 1rem;
337
- margin: 1rem 0;
338
- }
339
 
340
- .settings-panel {
341
- background: white;
342
- border-radius: 15px;
343
- padding: 1.5rem;
344
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
345
- margin: 1rem 0;
346
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
 
348
- .settings-grid {
349
- display: grid;
350
- grid-template-columns: 1fr 1fr;
351
- gap: 1rem;
352
- }
353
 
354
- @media (max-width: 768px) {
355
- .settings-grid {
356
- grid-template-columns: 1fr;
357
- }
358
- }
359
  """
360
-
361
- with gr.Blocks() as demo:
362
- gr.Markdown("""
363
- <div style="text-align: center; margin-bottom: 2rem;">
364
- <h1 style="color: #6c5ce7; font-size: 2.5rem; margin-bottom: 0.5rem;">
365
- <i class="fas fa-images"></i> Image Sequence Generator
366
- </h1>
367
- <p style="color: #666; font-size: 1.1rem;">
368
- Generate images and play them back as a sequence at 30 FPS
369
- </p>
370
- <div style="margin-top: 1rem; font-size: 0.9rem; color: #666;">
371
- Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: #6c5ce7; font-weight: 600;">anycoder</a>
372
- </div>
373
- </div>
374
- """)
375
-
376
- with gr.Row():
377
- with gr.Column(scale=1):
378
- # Controls Section
379
- gr.Markdown("### Generation Controls")
380
-
381
- prompt = gr.Textbox(
382
- label="Prompt",
383
- placeholder="Describe the image you want to generate",
384
- lines=2
385
- )
386
-
387
- negative_prompt = gr.Textbox(
388
- label="Negative Prompt",
389
- placeholder="What you don't want to see",
390
- lines=2
391
- )
392
-
393
- with gr.Accordion("Advanced Settings", open=False):
394
- with gr.Row():
395
- steps = gr.Slider(
396
- label="Steps",
397
- minimum=1,
398
- maximum=20,
399
- value=4,
400
- step=1
401
- )
402
- guidance_scale = gr.Slider(
403
- label="Guidance Scale",
404
- minimum=0,
405
- maximum=20,
406
- value=0,
407
- step=0.1
408
- )
409
-
410
- with gr.Row():
411
- seed = gr.Number(
412
- label="Seed",
413
- value=-1,
414
- minimum=-1
415
- )
416
- batch_size = gr.Number(
417
- label="Batch Size",
418
- value=1,
419
- minimum=1,
420
- maximum=4
421
- )
422
-
423
- generate_btn = gr.Button(
424
- value="Generate Image",
425
- variant="primary",
426
- size="lg"
427
- )
428
-
429
- progress_bar = gr.HTML("""
430
- <div class="progress-bar">
431
- <div class="progress" id="progress-bar" style="width: 0%;"></div>
432
- </div>
433
- """)
434
-
435
- # State components
436
- state_display = gr.JSON(visible=False)
437
- buffer_state = gr.State(generator.get_state())
438
-
439
- with gr.Column(scale=1):
440
- # Preview Section
441
- gr.Markdown("### Image Buffer & Player")
442
-
443
- # Status display
444
- status_html = gr.HTML("""
445
- <div class="status-container">
446
- <div class="status-item">
447
- <div class="status-label">Images in Buffer</div>
448
- <div class="status-value" id="buffer-count">0</div>
449
- </div>
450
- <div class="status-item">
451
- <div class="status-label">Required for Playback</div>
452
- <div class="status-value">10</div>
453
- </div>
454
- <div class="status-item">
455
- <div class="status-label">Playback Speed</div>
456
- <div class="status-value">30 FPS</div>
457
- </div>
458
- </div>
459
- """)
460
-
461
- with gr.Row():
462
- clear_btn = gr.Button("Clear Buffer", variant="secondary")
463
- play_btn = gr.Button("Play Sequence", variant="primary")
464
- stop_btn = gr.Button("Stop", variant="stop")
465
-
466
- player_display = gr.Image(
467
- label="Player Display",
468
- type="pil",
469
- height=300,
470
- visible=True
471
- )
472
-
473
- gr.Markdown("#### Image Buffer")
474
- image_buffer = gr.Gallery(
475
- label="Generated Images",
476
- columns=4,
477
- height="auto",
478
- object_fit="cover"
479
- )
480
-
481
- # Event handlers
482
- def update_ui(state):
483
- # Update status display
484
- status_html.value = f"""
485
- <div class="status-container">
486
- <div class="status-item">
487
- <div class="status-label">Images in Buffer</div>
488
- <div class="status-value">{state['buffer_count']}</div>
489
- </div>
490
- <div class="status-item">
491
- <div class="status-label">Required for Playback</div>
492
- <div class="status-value">{CONFIG['required_images']}</div>
493
- </div>
494
- <div class="status-item">
495
- <div class="status-label">Playback Speed</div>
496
- <div class="status-value">30 FPS</div>
497
- </div>
498
- </div>
499
- """
500
-
501
- # Update button states
502
- play_btn.interactive = state['can_play']
503
- stop_btn.interactive = state['is_playing']
504
- generate_btn.interactive = not state['is_generating']
505
-
506
- # Update image buffer
507
- if state['images']:
508
- images = [gr.Image(value=img, visible=True) for img in state['images']]
509
- return {
510
- status_html: status_html,
511
- play_btn: gr.Button(interactive=state['can_play']),
512
- stop_btn: gr.Button(interactive=state['is_playing']),
513
- generate_btn: gr.Button(interactive=not state['is_generating']),
514
- image_buffer: gr.Gallery(value=state['images'], visible=True)
515
- }
516
- else:
517
- return {
518
- status_html: status_html,
519
- play_btn: gr.Button(interactive=state['can_play']),
520
- stop_btn: gr.Button(interactive=state['is_playing']),
521
- generate_btn: gr.Button(interactive=not state['is_generating']),
522
- image_buffer: gr.Gallery(value=[], visible=True)
523
- }
524
-
525
- def generate_image_wrapper(prompt, negative_prompt, steps, guidance_scale, seed, batch_size):
526
- for state in generator.generate_image(prompt, negative_prompt, steps, guidance_scale, seed, batch_size):
527
- yield update_ui(state)
528
-
529
- def play_sequence_wrapper():
530
- state = generator.play_sequence()
531
- return update_ui(state)
532
-
533
- def stop_sequence_wrapper():
534
- state = generator.stop_sequence()
535
- return update_ui(state)
536
-
537
- def clear_buffer_wrapper():
538
- state = generator.clear_buffer()
539
- return update_ui(state)
540
-
541
- # Connect event handlers
542
- generate_btn.click(
543
- fn=generate_image_wrapper,
544
- inputs=[prompt, negative_prompt, steps, guidance_scale, seed, batch_size],
545
- outputs=[status_html, play_btn, stop_btn, generate_btn, image_buffer]
546
- )
547
-
548
- play_btn.click(
549
- fn=play_sequence_wrapper,
550
- outputs=[status_html, play_btn, stop_btn, generate_btn, image_buffer]
551
- )
552
-
553
- stop_btn.click(
554
- fn=stop_sequence_wrapper,
555
- outputs=[status_html, play_btn, stop_btn, generate_btn, image_buffer]
556
- )
557
-
558
- clear_btn.click(
559
- fn=clear_buffer_wrapper,
560
- outputs=[status_html, play_btn, stop_btn, generate_btn, image_buffer]
561
- )
562
-
563
- # Timer for playback
564
- timer = gr.Timer(1.0 / CONFIG["playback_fps"])
565
-
566
- @zero_gpu
567
- def update_player_display():
568
- if generator.is_playing and generator.images:
569
- frame_data = generator.get_current_frame()
570
- if frame_data:
571
- # Convert base64 to PIL Image
572
- header, encoded = frame_data.split(",", 1)
573
- binary_data = base64.b64decode(encoded)
574
- image = Image.open(io.BytesIO(binary_data))
575
- return image
576
- return None
577
-
578
- player_display.change(
579
- fn=update_player_display,
580
- every=timer,
581
- outputs=player_display
582
- )
583
-
584
- return demo
585
-
586
- # Create and launch the app
587
- if __name__ == "__main__":
588
- demo = create_gradio_app()
589
- demo.launch(
590
- theme=gr.themes.Soft(
591
- primary_hue="purple",
592
- secondary_hue="indigo",
593
- neutral_hue="slate",
594
- font=[gr.themes.GoogleFont("Segoe UI"), "sans-serif"],
595
- text_size="lg",
596
- spacing_size="lg",
597
- radius_size="md"
598
- ).set(
599
- button_primary_background_fill="*primary_600",
600
- button_primary_background_fill_hover="*primary_700",
601
- block_title_text_weight="600",
602
- ),
603
- footer_links=[
604
- {
605
- "label": "Built with anycoder",
606
- "url": "https://huggingface.co/spaces/akhaliq/anycoder"
607
- }
608
  ],
609
- show_error=True
610
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  import numpy as np
3
+ import cv2
4
+ from typing import Tuple
5
+ import os
6
+ import tempfile
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ def apply_normal_map_depth(video_path: str, normal_map_path: str, depth_strength: float) -> str:
9
+ """
10
+ Apply normal map depth effect to video
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ Args:
13
+ video_path: Path to input video file
14
+ normal_map_path: Path to normal map image
15
+ depth_strength: Strength of depth effect (0-1)
 
16
 
17
+ Returns:
18
+ Path to output video with depth effect
19
+ """
20
+ # Load normal map
21
+ normal_map = cv2.imread(normal_map_path)
22
+ if normal_map is None:
23
+ raise gr.Error("Failed to load normal map image")
24
+
25
+ # Convert to grayscale and normalize
26
+ normal_map_gray = cv2.cvtColor(normal_map, cv2.COLOR_BGR2GRAY)
27
+ normal_map_gray = normal_map_gray.astype(np.float32) / 255.0
28
+
29
+ # Create output video path
30
+ output_path = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False).name
31
+
32
+ # Open video capture
33
+ cap = cv2.VideoCapture(video_path)
34
+ if not cap.isOpened():
35
+ raise gr.Error("Failed to open video file")
36
+
37
+ # Get video properties
38
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
39
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
40
+ fps = cap.get(cv2.CAP_PROP_FPS)
41
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
42
+
43
+ # Create video writer
44
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
45
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
46
+
47
+ # Resize normal map to match video dimensions
48
+ normal_map_resized = cv2.resize(normal_map_gray, (width, height))
49
+
50
+ frame_count = 0
51
+ try:
52
+ while True:
53
+ ret, frame = cap.read()
54
+ if not ret:
55
+ break
56
+
57
+ # Apply depth effect based on normal map
58
+ if len(frame.shape) == 3 and frame.shape[2] == 3:
59
+ # Convert to float for processing
60
+ frame_float = frame.astype(np.float32) / 255.0
61
+
62
+ # Apply depth effect by adjusting brightness based on normal map
63
+ depth_effect = normal_map_resized * depth_strength * 0.5 # Scale down effect
64
+ frame_float = np.clip(frame_float + depth_effect[:, :, np.newaxis], 0, 1)
65
+
66
+ # Convert back to uint8
67
+ frame = (frame_float * 255).astype(np.uint8)
68
+
69
+ # Write frame to output
70
+ out.write(frame)
71
+ frame_count += 1
72
+
73
+ # Update progress
74
+ if frame_count % 10 == 0:
75
+ progress = frame_count / total_frames
76
+ print(f"Processing: {progress*100:.1f}%")
77
+
78
+ finally:
79
+ cap.release()
80
+ out.release()
81
+
82
+ return output_path
83
+
84
+ def process_video(video: gr.Video, normal_map: gr.Image, strength: float) -> gr.Video:
85
+ """
86
+ Process video with normal map depth effect
87
 
88
+ Args:
89
+ video: Input video
90
+ normal_map: Normal map image
91
+ strength: Depth effect strength
 
92
 
93
+ Returns:
94
+ Processed video with depth effect
 
 
 
95
  """
96
+ try:
97
+ # Save uploaded files temporarily
98
+ video_path = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False).name
99
+ normal_map_path = tempfile.NamedTemporaryFile(suffix='.png', delete=False).name
100
+
101
+ # Save video
102
+ if isinstance(video, str):
103
+ video_path = video
104
+ else:
105
+ # For webcam input, we need to handle differently
106
+ if video.startswith('data:'):
107
+ # This is a base64 encoded video from webcam
108
+ # For demo purposes, we'll use a placeholder
109
+ return gr.Video(value="https://gradio-builds.s3.amazonaws.com/assets/video_sample.mp4")
110
+
111
+ # Save normal map
112
+ if isinstance(normal_map, np.ndarray):
113
+ cv2.imwrite(normal_map_path, cv2.cvtColor(normal_map, cv2.COLOR_RGB2BGR))
114
+ else:
115
+ # Handle file path
116
+ normal_map_path = normal_map
117
+
118
+ # Process video
119
+ output_path = apply_normal_map_depth(video_path, normal_map_path, strength)
120
+
121
+ return gr.Video(value=output_path, format="mp4")
122
+
123
+ except Exception as e:
124
+ raise gr.Error(f"Error processing video: {str(e)}")
125
+ finally:
126
+ # Clean up temporary files
127
+ if 'video_path' in locals() and os.path.exists(video_path) and video_path.startswith(tempfile.gettempdir()):
128
+ os.unlink(video_path)
129
+ if 'normal_map_path' in locals() and os.path.exists(normal_map_path) and normal_map_path.startswith(tempfile.gettempdir()):
130
+ os.unlink(normal_map_path)
131
+
132
+ # Create Gradio interface
133
+ with gr.Blocks() as demo:
134
+ gr.Markdown("# Normal Map Depth Effect for Videos")
135
+ gr.Markdown("""
136
+ ### Built with anycoder
137
+ [![Anycoder](https://huggingface.co/spaces/akhaliq/anycoder/resolve/main/logo.png)](https://huggingface.co/spaces/akhaliq/anycoder)
138
+
139
+ Apply depth effects to videos using normal maps. Upload a video and a normal map image to create 3D-like depth effects.
140
+ """)
141
+
142
+ with gr.Row():
143
+ with gr.Column():
144
+ gr.Markdown("## Input Video")
145
+ video_input = gr.Video(
146
+ label="Upload Video",
147
+ sources=["upload", "webcam"],
148
+ format="mp4",
149
+ height=300
150
+ )
151
+
152
+ gr.Markdown("## Normal Map")
153
+ normal_map_input = gr.Image(
154
+ label="Upload Normal Map",
155
+ type="numpy",
156
+ height=300,
157
+ tooltip="Upload a normal map image (grayscale or color)"
158
+ )
159
+
160
+ depth_strength = gr.Slider(
161
+ minimum=0.1,
162
+ maximum=2.0,
163
+ value=1.0,
164
+ step=0.1,
165
+ label="Depth Strength",
166
+ info="Control the intensity of the depth effect"
167
+ )
168
+
169
+ process_btn = gr.Button("Apply Depth Effect", variant="primary", size="lg")
170
+
171
+ with gr.Column():
172
+ gr.Markdown("## Output Video with Depth Effect")
173
+ video_output = gr.Video(
174
+ label="Processed Video",
175
+ format="mp4",
176
+ height=400,
177
+ autoplay=True
178
+ )
179
+
180
+ gr.Markdown("""
181
+ ### How it works:
182
+ 1. Upload a video file or use your webcam
183
+ 2. Upload a normal map image (grayscale works best)
184
+ 3. Adjust the depth strength slider
185
+ 4. Click 'Apply Depth Effect' to process
186
+ 5. View the result with enhanced depth
187
+
188
+ ### Tips:
189
+ - Use high-contrast normal maps for best results
190
+ - Start with lower depth strength and increase gradually
191
+ - Normal maps should match the video resolution for optimal effect
192
+ """)
193
+
194
+ # Examples
195
+ gr.Markdown("## Examples")
196
+ examples = gr.Examples(
197
+ examples=[
198
+ [
199
+ "https://gradio-builds.s3.amazonaws.com/assets/video_sample.mp4",
200
+ "https://gradio-builds.s3.amazonaws.com/assets/normal_map_sample.png",
201
+ 1.0
202
+ ],
203
+ [
204
+ "https://gradio-builds.s3.amazonaws.com/assets/video_sample.mp4",
205
+ "https://gradio-builds.s3.amazonaws.com/assets/normal_map_sample2.png",
206
+ 0.7
207
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  ],
209
+ inputs=[video_input, normal_map_input, depth_strength],
210
+ outputs=[video_output],
211
+ fn=process_video,
212
+ cache_examples=True,
213
+ examples_per_page=2,
214
+ label="Try these examples:"
215
+ )
216
+
217
+ # Event listeners
218
+ process_btn.click(
219
+ fn=process_video,
220
+ inputs=[video_input, normal_map_input, depth_strength],
221
+ outputs=[video_output],
222
+ api_visibility="public",
223
+ api_name="apply_depth_effect"
224
+ )
225
+
226
+ # Add footer with anycoder link
227
+ gr.Markdown("""
228
+ ---
229
+ ### Built with [anycoder](https://huggingface.co/spaces/akhaliq/anycoder) 🚀
230
+ """)
231
+
232
+ # Launch with modern theme
233
+ demo.launch(
234
+ theme=gr.themes.Soft(
235
+ primary_hue="blue",
236
+ secondary_hue="indigo",
237
+ neutral_hue="slate",
238
+ font=gr.themes.GoogleFont("Inter"),
239
+ text_size="lg",
240
+ spacing_size="lg",
241
+ radius_size="md"
242
+ ).set(
243
+ button_primary_background_fill="*primary_600",
244
+ button_primary_background_fill_hover="*primary_700",
245
+ block_title_text_weight="600",
246
+ ),
247
+ footer_links=[
248
+ {"label": "Built with anycoder", "url": "https://huggingface.co/spaces/akhaliq/anycoder"},
249
+ {"label": "Gradio Docs", "url": "https://www.gradio.app/docs"},
250
+ {"label": "GitHub", "url": "https://github.com/gradio-app/gradio"}
251
+ ],
252
+ css="""
253
+ .gradio-container {
254
+ max-width: 1200px !important;
255
+ }
256
+ .gr-box {
257
+ border-radius: 12px !important;
258
+ }
259
+ """,
260
+ show_error=True,
261
+ share=True
262
+ )