Skip to content

Built-in nodes

Every node that ships inside the runtime, with its ports, settings and defaults, and what it does on Linux and macOS.

Built-in nodes are native code inside the runtime. Their per-frame work never enters Python, so they suit the parts of a stream with deadlines: capture, display, codecs and recording. Import them from tatolab.stream and add them like any node:

from tatolab.stream import CameraSource
camera = stream_builder.add(CameraSource, config={"max_width": 1280, "max_height": 720})

Each one’s settings are a TypedDict named after it (CameraSourceConfig, and so on), so your editor completes the keys and a type checker flags typos. Every setting is optional unless it says otherwise.

Captures live video from a camera: V4L2 on Linux, AVFoundation on macOS. Frames go straight to the GPU when the device can hand them over, and through one CPU upload when it can’t.

Outputs video: frames, stamped with the moment the device captured them
Setting Default
device_id the first camera found A V4L2 device path on Linux (/dev/video0), or a camera’s unique ID on macOS. On macOS the default is the first built-in camera.
max_width 1920 The largest width to capture
max_height 1080 The largest height to capture

A device_id that can’t be opened is refused when the node starts. On macOS, the first run asks for camera access without waiting: the stream starts, and frames begin when access is allowed.

Two limits to know on Linux. CameraSource reads uncompressed NV12 or YUYV only: cameras that send only MJPEG, monochrome or raw Bayer aren’t read, and most USB 2.0 webcams send uncompressed HD at 5 to 10 frames per second. It picks the largest size within max_width and max_height, whatever its frame rate, so lower them (640 × 480 usually runs at 30) or use a USB 3 camera; v4l2-ctl --list-formats-ext shows what yours offers. And there are no exposure, gain, focus, white-balance or frame-rate settings: set those with v4l2-ctl before the stream starts.

Color bars, for a stream on a machine without a camera.

Outputs video
Setting Default
width 1280
height 720

Shows video in a window, in step with the display’s refresh. Add as many as you like.

Inputs video (newest)
Setting Default
title "StreamLib" The window’s title
width 1280 Starting width, in the desktop’s logical pixels
height 720 Starting height, in logical pixels
scaling "fit" "fit": the whole frame, with black bars; "fill": fill the window, cropping the frame; "stretch": stretch the frame to the window

A window that can’t open logs DisplayWindow: no window — running degraded with the reason, and still reads its input, so the nodes before it keep running. On a machine with no display, leave windows out and look at frames with tatolab exchange.

Presents video as a camera other applications can select. The camera exists while the node runs. See Virtual camera.

Inputs video (newest)
Setting Default
name StreamLib Camera plus a short ID The camera’s name in applications, cut to 31 bytes
door "auto" "v4l2loopback", "pipewire", or "auto": v4l2loopback when permitted, PipeWire otherwise

On macOS, a stream containing one is refused when it loads.

Captures audio as timestamped blocks of samples. With no audio system at all, it produces silence, so the same stream runs on a server.

Outputs audio: blocks that AudioBlock reads
Setting Default
device_id the system default (on macOS, following changes) The audio system’s device name. On PipeWire, <sink>.monitor captures what that output plays. On macOS, a Core Audio device UID.

A device_id that can’t be opened is an error, never a quiet switch to another device. On macOS, the first run asks for microphone access.

Plays audio blocks. It converts each block to the device’s sample rate and period, and mixes channels down to mono or up from mono as needed. With no audio system, it discards its input.

Inputs audio (ordered)
Setting Default
device_id the system default (on macOS, following changes) As for MicrophoneSource

Blocks must be f32 or i16. A channel conversion that isn’t to or from mono, such as stereo into a five-channel device, is refused.

Compress video with the GPU’s hardware encoder: Vulkan Video on Linux, VideoToolbox on macOS. Each output bag is one compressed frame.

Inputs video (ordered)
Outputs encoded_video
Setting Default
bitrate_bps a fixed quantizer, with no target bitrate Target bitrate, in bits per second
keyframe_interval_seconds 2 Seconds between keyframes. 0 is refused on macOS.
fps 60 Frame rate to assume when frames don’t carry one
width, height from the first frame The expected size: a check, not a resize. A frame of another size is encoded as it is, with a warning.
effort_level the codec’s default How hard the hardware works. Refused on macOS.

The encoder starts from the first frame’s size, and starts again if the size changes. Without a hardware encoder it logs one error, keeps running, and drops every frame.

Decompress video with the hardware decoder, back into ordinary frames.

Inputs encoded_video (ordered)
Outputs video
Setting Default
max_width, max_height from the stream The largest coded size to prepare for. Set both or neither.

The decoder must match the encoder: H.264 with H.264, H.265 with H.265. Without a hardware decoder, it fails when the stream starts.

Compresses audio with Opus, in 20 ms packets. It converts its input to Opus’s 48 kHz itself, keeping the source’s channel count (1 to 8).

Inputs audio (ordered)
Outputs encoded_audio
Setting Default
application "audio" "audio" for fidelity, "voip" for speech, "lowdelay" for the least latency
bitrate_bps chosen by Opus Target bitrate, in bits per second

Decompresses Opus packets into audio blocks: 48 kHz, f32, in the packet’s channel count. It has no settings. A gap in the packets is logged and left as a gap; nothing is invented to fill it.

Inputs encoded_audio (ordered)
Outputs audio

Records encoded video and audio into one fragmented MP4 file. Every link into tracks becomes one track: H.264 or H.265 makes a video track, Opus an audio track. Each track is named after the link’s source, <producer node ID>/<port>, as tatolab graph shows it. See Record and encode.

Inputs tracks (ordered), any number of links
Setting Default
path required The file to write. Use a full path: the node runs inside the runtime, not in your project folder. The folder must exist.

The file is created, or emptied, each time the stream loads, so a kept stream reloaded after a restart starts its file over; see Recordings and restarts. If the process dies, the file plays up to its last complete fragment. A track that can’t continue, such as one whose video parameters change mid-file, stops with a message naming it while the others keep recording.

Nodes from packages are used the same way. tatolab-webrtc adds WhipPublisher and WhepPlayer; see WebRTC. Anything else is a node you write.