Video Overlays¶
Render dynamic content — text, images, tables — onto live video frames. The overlay system plugs into the existing video pipeline as a VideoFilterProvider.
Quick start¶
from roomkit.video.pipeline.overlay import OverlayFilter, Overlay, TextOverlayRenderer
overlay = OverlayFilter(renderers=[TextOverlayRenderer()])
overlay.add_overlay(Overlay(id="title", content="Live Meeting"))
# Add to your video pipeline config
config = VideoPipelineConfig(filters=[overlay])
Live subtitles¶
The killer use case: user speaks French → STT transcribes → AI translates → English subtitles on video.
from roomkit.video.pipeline.overlay import subtitle_overlay
# One-liner: creates overlay + wires ON_TRANSCRIPTION hook
overlay = subtitle_overlay(kit, font_scale=0.8, color=(255, 255, 0))
config = VideoPipelineConfig(filters=[overlay])
With translation¶
async def translate(text: str) -> str:
response = await ai.generate(f"Translate to English: {text}")
return response.text
overlay = subtitle_overlay(kit, translate_fn=translate, max_lines=3)
SubtitleManager (full control)¶
from roomkit.video.pipeline.overlay import SubtitleManager, OverlayPosition
mgr = SubtitleManager(
kit,
translate_fn=translate,
position=OverlayPosition.BOTTOM_CENTER,
max_lines=2,
style={"font_scale": 0.9, "bg_color": (0, 0, 0)},
)
# Add the filter to your pipeline
config = VideoPipelineConfig(filters=[mgr.overlay_filter])
# Manual control
mgr.set_text("Custom subtitle")
mgr.clear()
Multiple overlays¶
Stack overlays with z-ordering:
overlay = OverlayFilter(renderers=[TextOverlayRenderer()])
# Title at the top
overlay.add_overlay(Overlay(
id="title", content="Live Meeting",
position=OverlayPosition.TOP_CENTER,
z_order=10,
))
# Subtitles at the bottom (higher z = on top)
overlay.add_overlay(Overlay(
id="subtitle", content="",
position=OverlayPosition.BOTTOM_CENTER,
z_order=100,
))
# Dynamic update at runtime
overlay.update_overlay("title", content="Live Meeting — Recording")
overlay.remove_overlay("subtitle")
Image overlays¶
from roomkit.video.pipeline.overlay import ImageOverlayRenderer
overlay = OverlayFilter(renderers=[
TextOverlayRenderer(),
ImageOverlayRenderer(),
])
# Add a logo
with open("logo.png", "rb") as f:
logo_bytes = f.read()
overlay.add_overlay(Overlay(
id="logo",
content=logo_bytes,
overlay_type="image",
position=OverlayPosition.TOP_LEFT,
style={"width": 80}, # scale to 80px wide, preserve aspect ratio
))
Rich overlays (tables, styled text)¶
Requires pip install roomkit[video-overlay] (Pillow).
import json
from roomkit.video.pipeline.overlay import RichOverlayRenderer
overlay = OverlayFilter(renderers=[
TextOverlayRenderer(),
RichOverlayRenderer(),
])
# Table overlay
table = json.dumps({
"headers": ["Metric", "Value"],
"rows": [["Users", "42"], ["Latency", "12ms"]],
})
overlay.add_overlay(Overlay(
id="stats",
content=table,
overlay_type="rich",
position=OverlayPosition.TOP_RIGHT,
style={"width": 250, "font_size": 14},
))
Positions¶
| Position | Placement |
|---|---|
TOP_LEFT |
Top-left with padding |
TOP_CENTER |
Top-center |
TOP_RIGHT |
Top-right |
CENTER_LEFT |
Middle-left |
CENTER |
Dead center |
CENTER_RIGHT |
Middle-right |
BOTTOM_LEFT |
Bottom-left |
BOTTOM_CENTER |
Bottom-center (default for subtitles) |
BOTTOM_RIGHT |
Bottom-right |
CUSTOM |
Manual x, y coordinates |
Performance¶
- Cached rendering: Renderers cache their output patches. On unchanged content, they blit the cached patch — no re-rendering at 30fps.
- Empty fast-path: When no overlays are configured,
filter()returns the frame immediately (no copy). - Thread-safe:
OverlayFilteruses athreading.Lock— safe to update overlays from async hooks while the video thread renders.
Renderers¶
| Renderer | Content type | Dependencies |
|---|---|---|
TextOverlayRenderer |
Text strings | opencv, numpy (via roomkit[local-video]) |
ImageOverlayRenderer |
PNG/JPEG bytes | opencv, numpy |
RichOverlayRenderer |
Styled text, JSON tables | Pillow (via roomkit[video-overlay]) |
MockOverlayRenderer |
Any (pass-through) | None |
Custom renderers: implement OverlayRenderer ABC with overlay_type, render(), invalidate_cache(), and clear_cache().