Skip to main content
Version: QTrobot V3

Video

robot.media plays video on the same two lanes used for audio — foreground (FG) and background (BG) — from files or as a raw streamed RGBA signal. These examples assume you already have a connected robot — see Connection if you haven't set one up yet.

How the FG/BG video system works​

Video uses the same two-lane idea as Audio, but the lanes aren't symmetric, and there's alpha (transparency) control instead of volume:

  • BG is the opaque base layer — a plain image, no transparency.
  • FG sits on top of BG and is alpha-blended over it. robot.media.set_fg_video_alpha() controls how much of the FG layer shows through; there's no separate BG alpha, since BG is always the fully-opaque bottom layer.

Just like audio, each lane accepts a file and a stream input, and file playback takes priority — while a file is playing on a lane, frames arriving on that lane's stream are dropped, and the lane reverts to the stream once the file finishes.

An important difference from audio: there's no video equivalent of TTS using FG — it's the opposite. QTrobot's own face system uses the BG lane: emotions (robot.face.show_emotion(...)) play as a BG file, and a continuous live face rendering is pushed to the BG stream whenever no emotion file is playing. The FG lane is left free for you — anything you play or stream on FG is alpha-blended on top of whatever the face is currently showing on BG.

Creative possibilities​

Because FG always overlays whatever's on BG, you can layer your own content on top of the robot's normal face/emotions without replacing them:

  • Reactive overlays — show a small icon, sticker, or shape on FG (like the pulsing heart below) that fades in and out over an emotion or the idle face, using set_fg_video_alpha() for the fade.
  • Captions or visual cues — overlay text or a simple graphic on FG while an emotion plays on BG underneath, for accessibility or for a teaching/story context.
  • Speech-synced effects — combine FG video (a custom overlay) with FG audio (TTS) for a richer "speaking" effect, while BG continues showing the robot's current emotion or idle face.
  • Smooth transitions — fade an FG overlay in just as a BG emotion finishes, to bridge between two visual states instead of an abrupt cut.

Setup​

mkdir ~/example
cd ~/example
python -m venv .venv
# or: uv venv .venv

source .venv/bin/activate

pip install luxai-robot
# or: uv pip install luxai-robot

Connect​

from luxai.robot.core import Robot

robot = Robot.connect_zmq(robot_id="QTRD000123")
print(f"connected to {robot.robot_id} ({robot.robot_type})")

See Connection for MQTT, WebRTC, and other connection options.

Foreground alpha (transparency)​

The foreground video lane sits on top of the background lane — its alpha controls how transparent it is.

import time

# Fully opaque
robot.media.set_fg_video_alpha(1.0)

# Half-transparent
robot.media.set_fg_video_alpha(0.5)
time.sleep(2)

# Restore to fully opaque
robot.media.set_fg_video_alpha(1.0)

Play a background video file​

A file path must exist on the robot itself (not on your laptop) for these examples to work.

import time

video_file_on_robot = "/home/qtrobot/robot/data/emotions/QT/kiss.avi"

# Play and wait for it to finish
ret = robot.media.play_bg_video_file(video_file_on_robot)
Logger.info(f"Done. Result: {ret}")

# Play non-blocking, then cancel it after 2 seconds
h = robot.media.play_bg_video_file_async(video_file_on_robot)
time.sleep(2)
h.cancel()

Pause and resume​

import time

video_file_on_robot = "/home/qtrobot/robot/data/emotions/QT/kiss.avi"

play_handler = robot.media.play_bg_video_file_async(video_file_on_robot)
time.sleep(2)

robot.media.pause_bg_video_file()
Logger.info("Paused. Waiting 3 seconds...")
time.sleep(3)

robot.media.resume_bg_video_file()
play_handler.wait() # wait for playback to finish

Stream raw video frames​

This example streams a pulsing heart shape to the foreground lane, fading its alpha in time with a heartbeat.

import time
import math
import numpy as np
from luxai.magpie.utils.common import get_uinque_id
from luxai.magpie.frames import ImageFrameRaw

def _make_heart_frame(width, height, scale=1.5):
image = np.zeros((height, width, 4), dtype=np.uint8) # transparent RGBA

y, x = np.ogrid[:height, :width]
x = (x - width / 2) / (width / 2)
y = -((y - height / 2) / (height / 2)) # flip vertically
x *= scale
y *= scale

heart_mask = (x**2 + y**2 - 1)**3 - x**2 * y**3 <= 0
image[heart_mask] = (255, 0, 0, 255) # red, full alpha

return ImageFrameRaw(
data=image.tobytes(), format="raw",
width=width, height=height, channels=4, pixel_format="RGBA",
)

stream_id = get_uinque_id()
fps = 30
duration_s = 5.0
total_frames = int(fps * duration_s)
frame_interval = 1.0 / fps

heart_frame = _make_heart_frame(width=400, height=280, scale=5.0)
heart_frame.gid = stream_id

bpm = 72.0
freq = bpm / 60.0 # beats per second

robot.media.set_fg_video_alpha(0.0)
writer = robot.media.stream.open_fg_video_stream_writer()

t0 = time.perf_counter()
for frame_id in range(1, total_frames + 1):
heart_frame.id = frame_id
writer.write(heart_frame)

t = time.perf_counter() - t0
alpha = 0.5 * (1 + math.sin(2 * math.pi * freq * t))
alpha = alpha ** 2 # makes it feel more like a "beat"
robot.media.set_fg_video_alpha(alpha)
time.sleep(frame_interval)

robot.media.set_fg_video_alpha(1.0)
robot.media.cancel_fg_video_stream()

Next steps​

Continue with the Microphone tutorial, or see the full robot.media namespace in the Python API Reference.