| import gradio as gr |
| import numpy as np |
| import cv2 |
| from typing import Tuple |
| import os |
| import tempfile |
|
|
| def apply_normal_map_depth(video_path: str, normal_map_path: str, depth_strength: float) -> str: |
| """ |
| Apply normal map depth effect to video |
| |
| Args: |
| video_path: Path to input video file |
| normal_map_path: Path to normal map image |
| depth_strength: Strength of depth effect (0-1) |
| |
| Returns: |
| Path to output video with depth effect |
| """ |
| |
| normal_map = cv2.imread(normal_map_path) |
| if normal_map is None: |
| raise gr.Error("Failed to load normal map image") |
|
|
| |
| normal_map_gray = cv2.cvtColor(normal_map, cv2.COLOR_BGR2GRAY) |
| normal_map_gray = normal_map_gray.astype(np.float32) / 255.0 |
|
|
| |
| output_path = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False).name |
|
|
| |
| cap = cv2.VideoCapture(video_path) |
| if not cap.isOpened(): |
| raise gr.Error("Failed to open video file") |
|
|
| |
| width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) |
| height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) |
| fps = cap.get(cv2.CAP_PROP_FPS) |
| total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) |
|
|
| |
| fourcc = cv2.VideoWriter_fourcc(*'mp4v') |
| out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) |
|
|
| |
| normal_map_resized = cv2.resize(normal_map_gray, (width, height)) |
|
|
| frame_count = 0 |
| try: |
| while True: |
| ret, frame = cap.read() |
| if not ret: |
| break |
|
|
| |
| if len(frame.shape) == 3 and frame.shape[2] == 3: |
| |
| frame_float = frame.astype(np.float32) / 255.0 |
|
|
| |
| depth_effect = normal_map_resized * depth_strength * 0.5 |
| frame_float = np.clip(frame_float + depth_effect[:, :, np.newaxis], 0, 1) |
|
|
| |
| frame = (frame_float * 255).astype(np.uint8) |
|
|
| |
| out.write(frame) |
| frame_count += 1 |
|
|
| |
| if frame_count % 10 == 0: |
| progress = frame_count / total_frames |
| print(f"Processing: {progress*100:.1f}%") |
|
|
| finally: |
| cap.release() |
| out.release() |
|
|
| return output_path |
|
|
| def process_video(video: gr.Video, normal_map: gr.Image, strength: float) -> gr.Video: |
| """ |
| Process video with normal map depth effect |
| |
| Args: |
| video: Input video |
| normal_map: Normal map image |
| strength: Depth effect strength |
| |
| Returns: |
| Processed video with depth effect |
| """ |
| try: |
| |
| video_path = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False).name |
| normal_map_path = tempfile.NamedTemporaryFile(suffix='.png', delete=False).name |
|
|
| |
| if isinstance(video, str): |
| video_path = video |
| else: |
| |
| if video.startswith('data:'): |
| |
| |
| return gr.Video(value="https://gradio-builds.s3.amazonaws.com/assets/video_sample.mp4") |
|
|
| |
| if isinstance(normal_map, np.ndarray): |
| cv2.imwrite(normal_map_path, cv2.cvtColor(normal_map, cv2.COLOR_RGB2BGR)) |
| else: |
| |
| normal_map_path = normal_map |
|
|
| |
| output_path = apply_normal_map_depth(video_path, normal_map_path, strength) |
|
|
| return gr.Video(value=output_path, format="mp4") |
|
|
| except Exception as e: |
| raise gr.Error(f"Error processing video: {str(e)}") |
| finally: |
| |
| if 'video_path' in locals() and os.path.exists(video_path) and video_path.startswith(tempfile.gettempdir()): |
| os.unlink(video_path) |
| if 'normal_map_path' in locals() and os.path.exists(normal_map_path) and normal_map_path.startswith(tempfile.gettempdir()): |
| os.unlink(normal_map_path) |
|
|
| |
| with gr.Blocks() as demo: |
| gr.Markdown("# Normal Map Depth Effect for Videos") |
| gr.Markdown(""" |
| ### Built with anycoder |
| [](https://huggingface.co/spaces/akhaliq/anycoder) |
| |
| Apply depth effects to videos using normal maps. Upload a video and a normal map image to create 3D-like depth effects. |
| """) |
|
|
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("## Input Video") |
| video_input = gr.Video( |
| label="Upload Video", |
| sources=["upload", "webcam"], |
| format="mp4", |
| height=300 |
| ) |
|
|
| gr.Markdown("## Normal Map") |
| normal_map_input = gr.Image( |
| label="Upload Normal Map", |
| type="numpy", |
| height=300, |
| tooltip="Upload a normal map image (grayscale or color)" |
| ) |
|
|
| depth_strength = gr.Slider( |
| minimum=0.1, |
| maximum=2.0, |
| value=1.0, |
| step=0.1, |
| label="Depth Strength", |
| info="Control the intensity of the depth effect" |
| ) |
|
|
| process_btn = gr.Button("Apply Depth Effect", variant="primary", size="lg") |
|
|
| with gr.Column(): |
| gr.Markdown("## Output Video with Depth Effect") |
| video_output = gr.Video( |
| label="Processed Video", |
| format="mp4", |
| height=400, |
| autoplay=True |
| ) |
|
|
| gr.Markdown(""" |
| ### How it works: |
| 1. Upload a video file or use your webcam |
| 2. Upload a normal map image (grayscale works best) |
| 3. Adjust the depth strength slider |
| 4. Click 'Apply Depth Effect' to process |
| 5. View the result with enhanced depth |
| |
| ### Tips: |
| - Use high-contrast normal maps for best results |
| - Start with lower depth strength and increase gradually |
| - Normal maps should match the video resolution for optimal effect |
| """) |
|
|
| |
| gr.Markdown("## Examples") |
| examples = gr.Examples( |
| examples=[ |
| [ |
| "https://gradio-builds.s3.amazonaws.com/assets/video_sample.mp4", |
| "https://gradio-builds.s3.amazonaws.com/assets/normal_map_sample.png", |
| 1.0 |
| ], |
| [ |
| "https://gradio-builds.s3.amazonaws.com/assets/video_sample.mp4", |
| "https://gradio-builds.s3.amazonaws.com/assets/normal_map_sample2.png", |
| 0.7 |
| ] |
| ], |
| inputs=[video_input, normal_map_input, depth_strength], |
| outputs=[video_output], |
| fn=process_video, |
| cache_examples=True, |
| examples_per_page=2, |
| label="Try these examples:" |
| ) |
|
|
| |
| process_btn.click( |
| fn=process_video, |
| inputs=[video_input, normal_map_input, depth_strength], |
| outputs=[video_output], |
| api_visibility="public", |
| api_name="apply_depth_effect" |
| ) |
|
|
| |
| gr.Markdown(""" |
| --- |
| ### Built with [anycoder](https://huggingface.co/spaces/akhaliq/anycoder) 🚀 |
| """) |
|
|
| |
| demo.launch( |
| theme=gr.themes.Soft( |
| primary_hue="blue", |
| secondary_hue="indigo", |
| neutral_hue="slate", |
| font=gr.themes.GoogleFont("Inter"), |
| text_size="lg", |
| spacing_size="lg", |
| radius_size="md" |
| ).set( |
| button_primary_background_fill="*primary_600", |
| button_primary_background_fill_hover="*primary_700", |
| block_title_text_weight="600", |
| ), |
| footer_links=[ |
| {"label": "Built with anycoder", "url": "https://huggingface.co/spaces/akhaliq/anycoder"}, |
| {"label": "Gradio Docs", "url": "https://www.gradio.app/docs"}, |
| {"label": "GitHub", "url": "https://github.com/gradio-app/gradio"} |
| ], |
| css=""" |
| .gradio-container { |
| max-width: 1200px !important; |
| } |
| .gr-box { |
| border-radius: 12px !important; |
| } |
| """, |
| show_error=True, |
| share=True |
| ) |