multimodalart's picture
Update app.py
77185d8 verified
import gradio as gr
import numpy as np
import spaces
from fastrtc import WebRTC, get_turn_credentials
# This is the core processing function that will be assigned to the GPU.
# It receives a generator (`frame_stream`) that yields frames from the webcam
# and it yields processed frames back to the output component.
@spaces.GPU
def process_frames_on_gpu(frame_stream):
"""
This function runs as a persistent process on the GPU.
It iterates over incoming frames, processes them, and yields the results.
"""
print("πŸš€ GPU Frame processing loop started.")
if frame_stream is None:
print("Input stream is None, ending.")
return
# This loop will block until a new frame is available, making it reactive.
for frame in frame_stream:
if frame is not None:
# This is where your GPU-intensive work would happen.
flipped_frame = np.flip(frame, axis=(0, 1))
# Yield the processed frame to the output stream.
yield flipped_frame
print("πŸ›‘ GPU Frame processing loop finished.")
# --- Gradio UI Layout ---
with gr.Blocks(theme=gr.themes.Soft(), title="FastRTC ZeroGPU Flipper") as demo:
gr.Markdown("# πŸš€ FastRTC Webcam Flipper for ZeroGPU")
gr.Markdown(
"*This version uses a separate button to trigger the stream, correctly using Gradio's streaming iterator pattern for ZeroGPU without conflicting with the `fastrtc` component's internal logic.*"
)
with gr.Row():
with gr.Column(scale=1):
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(),
)
start_button = gr.Button("πŸš€ Start Processing Stream", variant="primary")
with gr.Column(scale=1):
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(),
)
# By using a button's `click` event to start the stream, we use Gradio's
# standard streaming API, which correctly provides the iterator to our
# decorated function without causing a conflict.
start_button.click(
fn=process_frames_on_gpu,
inputs=[webcam_input],
outputs=[video_output]
)
if __name__ == "__main__":
demo.queue().launch()