Spaces:
Sleeping
Sleeping
| """Video Processing Space - Gradio Interface for AI Video Analysis.""" | |
| import gradio as gr | |
| import cv2 | |
| import numpy as np | |
| import json | |
| import tempfile | |
| import os | |
| def get_metadata(video_file): | |
| """Extract video metadata.""" | |
| if video_file is None: | |
| return "No video uploaded" | |
| cap = cv2.VideoCapture(video_file) | |
| if not cap.isOpened(): | |
| return "Error: Could not open video" | |
| metadata = { | |
| "frame_count": int(cap.get(cv2.CAP_PROP_FRAME_COUNT)), | |
| "fps": round(cap.get(cv2.CAP_PROP_FPS), 2), | |
| "width": int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), | |
| "height": int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)), | |
| } | |
| if metadata["fps"] > 0: | |
| metadata["duration_sec"] = round(metadata["frame_count"] / metadata["fps"], 2) | |
| else: | |
| metadata["duration_sec"] = 0 | |
| codec_int = int(cap.get(cv2.CAP_PROP_FOURCC)) | |
| metadata["codec"] = "".join([chr((codec_int >> 8 * i) & 0xFF) for i in range(4)]) | |
| cap.release() | |
| return json.dumps(metadata, indent=2) | |
| def create_contact_sheet(video_file, grid_size): | |
| """Generate contact sheet grid from video.""" | |
| if video_file is None: | |
| return None, "No video uploaded" | |
| grid_size = int(grid_size) | |
| cap = cv2.VideoCapture(video_file) | |
| if not cap.isOpened(): | |
| return None, "Error: Could not open video" | |
| total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
| fps = cap.get(cv2.CAP_PROP_FPS) | |
| width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
| height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
| duration = total_frames / fps if fps > 0 else 0 | |
| total_cells = grid_size * grid_size | |
| step = max(1, total_frames // total_cells) | |
| thumb_width = 200 | |
| thumb_height = int(height * (thumb_width / width)) | |
| frames = [] | |
| for i in range(total_cells): | |
| frame_idx = i * step | |
| cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx) | |
| ret, frame = cap.read() | |
| if ret: | |
| thumb = cv2.resize(frame, (thumb_width, thumb_height)) | |
| cv2.putText(thumb, str(i), (5, 20), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) | |
| frames.append(thumb) | |
| else: | |
| blank = np.zeros((thumb_height, thumb_width, 3), dtype=np.uint8) | |
| frames.append(blank) | |
| cap.release() | |
| rows = [] | |
| for r in range(grid_size): | |
| start_idx = r * grid_size | |
| end_idx = start_idx + grid_size | |
| row_frames = frames[start_idx:end_idx] | |
| if row_frames: | |
| rows.append(np.hstack(row_frames)) | |
| if rows: | |
| grid_image = np.vstack(rows) | |
| grid_image_rgb = cv2.cvtColor(grid_image, cv2.COLOR_BGR2RGB) | |
| info = { | |
| "grid_size": grid_size, | |
| "total_cells": total_cells, | |
| "video_duration_sec": round(duration, 2), | |
| "seconds_per_cell": round(duration / total_cells, 2), | |
| } | |
| return grid_image_rgb, json.dumps(info, indent=2) | |
| return None, "Error: Could not generate contact sheet" | |
| def extract_clip(video_file, start_sec, end_sec): | |
| """Extract video segment by timestamps.""" | |
| if video_file is None: | |
| return None, "No video uploaded" | |
| cap = cv2.VideoCapture(video_file) | |
| if not cap.isOpened(): | |
| return None, "Error: Could not open video" | |
| fps = cap.get(cv2.CAP_PROP_FPS) | |
| width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
| height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
| total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
| duration = total_frames / fps if fps > 0 else 0 | |
| if start_sec < 0: | |
| start_sec = 0 | |
| if end_sec > duration: | |
| end_sec = duration | |
| if start_sec >= end_sec: | |
| cap.release() | |
| return None, f"Invalid time range: {start_sec} to {end_sec}" | |
| start_frame = int(start_sec * fps) | |
| end_frame = int(end_sec * fps) | |
| # Create temp output file | |
| output_path = tempfile.mktemp(suffix='.mp4') | |
| fourcc = cv2.VideoWriter_fourcc(*'mp4v') | |
| out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) | |
| cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame) | |
| frames_written = 0 | |
| current_frame = start_frame | |
| while current_frame < end_frame: | |
| ret, frame = cap.read() | |
| if not ret: | |
| break | |
| out.write(frame) | |
| frames_written += 1 | |
| current_frame += 1 | |
| cap.release() | |
| out.release() | |
| info = { | |
| "start_sec": start_sec, | |
| "end_sec": end_sec, | |
| "duration_sec": round(end_sec - start_sec, 2), | |
| "frames_written": frames_written, | |
| } | |
| return output_path, json.dumps(info, indent=2) | |
| # Gradio Interface | |
| with gr.Blocks(title="Video Process - AI Video Analysis", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown(""" | |
| # 🎬 Video Process | |
| **AI-powered video analysis and editing** | |
| Upload a video to inspect, analyze, or extract clips. | |
| """) | |
| video_input = gr.Video(label="Upload Video") | |
| with gr.Tabs(): | |
| # Tab 1: Metadata | |
| with gr.TabItem("📊 Metadata"): | |
| meta_btn = gr.Button("Get Metadata", variant="primary") | |
| meta_output = gr.Code(language="json", label="Video Properties") | |
| meta_btn.click(get_metadata, inputs=video_input, outputs=meta_output) | |
| # Tab 2: Contact Sheet | |
| with gr.TabItem("👁️ Inspect (Contact Sheet)"): | |
| gr.Markdown("Generate a visual grid to 'see' the entire video at once") | |
| grid_slider = gr.Slider(minimum=2, maximum=10, value=6, step=1, label="Grid Size") | |
| inspect_btn = gr.Button("Generate Contact Sheet", variant="primary") | |
| contact_sheet_output = gr.Image(label="Contact Sheet") | |
| inspect_info = gr.Code(language="json", label="Info") | |
| inspect_btn.click(create_contact_sheet, | |
| inputs=[video_input, grid_slider], | |
| outputs=[contact_sheet_output, inspect_info]) | |
| # Tab 3: Extract Clip | |
| with gr.TabItem("✂️ Extract Clip"): | |
| gr.Markdown("Cut a segment from the video by timestamps") | |
| with gr.Row(): | |
| start_input = gr.Number(value=0, label="Start (seconds)") | |
| end_input = gr.Number(value=5, label="End (seconds)") | |
| extract_btn = gr.Button("Extract Clip", variant="primary") | |
| clip_output = gr.Video(label="Extracted Clip") | |
| extract_info = gr.Code(language="json", label="Info") | |
| extract_btn.click(extract_clip, | |
| inputs=[video_input, start_input, end_input], | |
| outputs=[clip_output, extract_info]) | |
| gr.Markdown(""" | |
| --- | |
| *Built with OpenCV + Gradio | VAM-Seek inspired contact sheet approach* | |
| """) | |
| if __name__ == "__main__": | |
| demo.launch() | |