Spaces:
Running
on
Zero
Running
on
Zero
| import asyncio | |
| import numpy as np | |
| import gradio as gr | |
| import spaces | |
| from fastrtc import WebRTC, get_turn_credentials, AsyncStreamHandler | |
| from fastrtc.utils import wait_for_item | |
| class FrameFlipperHandler(AsyncStreamHandler): | |
| """ | |
| A persistent, reactive handler for flipping video frames. | |
| This class creates a long-running process for each user connection. | |
| - The `receive` method is called by the backend whenever a new frame arrives from the user. | |
| - The `emit` method is called by the backend in a loop to get processed frames to send back. | |
| - An internal `asyncio.Queue` connects these two methods for reactive, low-latency processing. | |
| """ | |
| def __init__(self): | |
| super().__init__() | |
| self.frame_queue = asyncio.Queue() | |
| async def receive(self, frame: tuple[int, np.ndarray]) -> None: | |
| """Called for each frame received from the client's webcam.""" | |
| # This is non-blocking and instantly puts the frame into our processing queue. | |
| self.frame_queue.put_nowait(frame) | |
| async def emit(self): | |
| """Called in a loop to get the next frame to send to the client.""" | |
| # This waits for a frame to be available in the queue with a small timeout. | |
| frame_data = await wait_for_item(self.frame_queue, timeout=0.01) | |
| if frame_data: | |
| _rate, frame_array = frame_data | |
| if frame_array is not None: | |
| # Flip the frame both vertically and horizontally | |
| flipped_frame = np.flip(frame_array, axis=(0, 1)) | |
| return (None, flipped_frame) # The sample rate is ignored for video | |
| return None | |
| def copy(self): | |
| """Creates a new instance of this handler for each new connection.""" | |
| return FrameFlipperHandler() | |
| # --- Gradio UI Layout --- | |
| with gr.Blocks(theme=gr.themes.Soft(), title="FastRTC Webcam Double Flipper") as demo: | |
| gr.Markdown("# 🚀 FastRTC Webcam Double Flipper (Reactive)") | |
| gr.Markdown( | |
| "*This version uses a persistent `AsyncStreamHandler` for low-latency, reactive streaming, ideal for environments like ZeroGPU.*" | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### 1. Your Webcam Feed (Input)") | |
| webcam_input = WebRTC( | |
| label="Webcam Input", | |
| modality="video", | |
| mode="send", | |
| width=640, | |
| height=480, | |
| rtc_configuration=get_turn_credentials(), | |
| ) | |
| with gr.Column(): | |
| gr.Markdown("### 2. Flipped Video (Output)") | |
| video_output = WebRTC( | |
| label="Flipped Output Stream", | |
| modality="video", | |
| mode="receive", | |
| width=640, | |
| height=480, | |
| rtc_configuration=get_turn_credentials(), | |
| ) | |
| # Instantiate our custom handler | |
| handler = FrameFlipperHandler() | |
| # Pass the handler instance to the stream method | |
| webcam_input.stream( | |
| fn=handler, | |
| inputs=[webcam_input], | |
| outputs=[video_output], | |
| time_limit=120, | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue().launch() |