| import gradio as gr |
| import cv2 |
| import numpy as np |
| import tempfile |
| import os |
| from framevis import FrameVis |
| import json |
|
|
| class InteractiveFrameVis(FrameVis): |
| """Extended FrameVis class that tracks frame positions""" |
| |
| def visualize(self, source, nframes, height=None, width=None, direction="horizontal", trim=False, quiet=True): |
| """Extended visualize method that returns both the visualization and frame data""" |
| video = cv2.VideoCapture(source) |
| if not video.isOpened(): |
| raise FileNotFoundError("Source Video Not Found") |
| |
| |
| total_frames = video.get(cv2.CAP_PROP_FRAME_COUNT) |
| fps = video.get(cv2.CAP_PROP_FPS) |
| keyframe_interval = total_frames / nframes |
| |
| |
| output_image = super().visualize(source, nframes, height, width, direction, trim, quiet) |
| |
| |
| frame_data = [] |
| img_height, img_width = output_image.shape[:2] |
| |
| for i in range(nframes): |
| frame_pos = int(keyframe_interval * (i + 0.5)) |
| timestamp = frame_pos / fps |
| |
| if direction == "horizontal": |
| x_start = (i * img_width) // nframes |
| x_end = ((i + 1) * img_width) // nframes |
| frame_info = { |
| "frame": frame_pos, |
| "time": timestamp, |
| "x_start": int(x_start), |
| "x_end": int(x_end), |
| "y_start": 0, |
| "y_end": img_height |
| } |
| else: |
| y_start = (i * img_height) // nframes |
| y_end = ((i + 1) * img_height) // nframes |
| frame_info = { |
| "frame": frame_pos, |
| "time": timestamp, |
| "x_start": 0, |
| "x_end": img_width, |
| "y_start": int(y_start), |
| "y_end": int(y_end) |
| } |
| frame_data.append(frame_info) |
| |
| video.release() |
| return output_image, frame_data |
|
|
| def extract_frame(video_path, frame_number): |
| """Extract a specific frame from the video""" |
| if not video_path: |
| return None |
| |
| cap = cv2.VideoCapture(video_path) |
| cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number) |
| ret, frame = cap.read() |
| cap.release() |
| |
| if ret: |
| return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) |
| return None |
|
|
| def process_video(video_path, nframes, height, width, direction, trim, average, blur_amount): |
| """Process video using FrameVis and return the visualization with frame data""" |
| try: |
| fv = InteractiveFrameVis() |
| |
| |
| output_image, frame_data = fv.visualize( |
| video_path, |
| nframes=nframes, |
| height=height if height > 0 else None, |
| width=width if width > 0 else None, |
| direction=direction, |
| trim=trim, |
| quiet=False |
| ) |
| |
| |
| if average: |
| output_image = fv.average_image(output_image, direction) |
| elif blur_amount > 0: |
| output_image = fv.motion_blur(output_image, direction, blur_amount) |
| |
| |
| output_image = cv2.cvtColor(output_image, cv2.COLOR_BGR2RGB) |
| |
| |
| temp_dir = tempfile.gettempdir() |
| data_path = os.path.join(temp_dir, "frame_data.json") |
| with open(data_path, "w") as f: |
| json.dump({"video_path": video_path, "frames": frame_data}, f) |
| |
| return output_image, data_path |
| |
| except Exception as e: |
| raise gr.Error(str(e)) |
|
|
| def on_mouse_move(evt: gr.EventData, frame_data_path): |
| """Handle mouseover on the visualization image""" |
| if not frame_data_path: |
| return None |
| |
| try: |
| |
| with open(frame_data_path) as f: |
| data = json.load(f) |
| |
| video_path = data["video_path"] |
| frames = data["frames"] |
| |
| |
| x, y = evt.index[0], evt.index[1] |
| |
| |
| for frame in frames: |
| if (frame["x_start"] <= x <= frame["x_end"] and |
| frame["y_start"] <= y <= frame["y_end"]): |
| |
| preview = extract_frame(video_path, frame["frame"]) |
| if preview is not None: |
| return preview, f"Frame {frame['frame']} (Time: {frame['time']:.2f}s)" |
| |
| except Exception as e: |
| print(f"Error handling mouseover: {e}") |
| return None, "" |
|
|
| |
| with gr.Blocks(title="FrameVis - Video Frame Visualizer") as demo: |
| gr.Markdown(""" |
| # 🎬 FrameVis - Video Frame Visualizer |
| Upload a video to create a beautiful visualization of its frames. The tool will extract frames at regular intervals |
| and combine them into a single image. **Click anywhere on the visualization to see the original frames!** |
| """) |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| |
| video_input = gr.Video(label="Upload Video") |
| with gr.Row(): |
| nframes = gr.Slider(minimum=1, maximum=500, value=100, step=1, |
| label="Number of Frames") |
| direction = gr.Radio(["horizontal", "vertical"], value="horizontal", |
| label="Direction") |
| |
| with gr.Row(): |
| height = gr.Number(value=0, label="Frame Height (0 for auto)") |
| width = gr.Number(value=0, label="Frame Width (0 for auto)") |
| |
| with gr.Row(): |
| trim = gr.Checkbox(label="Auto-trim black bars") |
| average = gr.Checkbox(label="Average colors") |
| blur_amount = gr.Slider(minimum=0, maximum=200, value=0, step=1, |
| label="Motion Blur Amount") |
| |
| process_btn = gr.Button("Generate Visualization", variant="primary") |
| |
| with gr.Column(scale=2): |
| |
| frame_data = gr.State() |
| output_image = gr.Image(label="Visualization Result", tool="select", height=300) |
| frame_info = gr.Markdown("Click on the visualization to see frame details") |
| preview_frame = gr.Image(label="Frame Preview", interactive=False, height=300) |
| |
| |
| result = process_btn.click( |
| fn=process_video, |
| inputs=[ |
| video_input, |
| nframes, |
| height, |
| width, |
| direction, |
| trim, |
| average, |
| blur_amount |
| ], |
| outputs=[output_image, frame_data] |
| ) |
| |
| |
| output_image.select( |
| fn=on_mouse_move, |
| inputs=[frame_data], |
| outputs=[preview_frame, frame_info] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |