The Camera service provides access to camera devices for listing, photo capture, and video frame streaming. Access it via app.camera.

devices = app.camera.list_devices()
for d in devices:
    print(f"Found camera: {d.name}")

frame = app.camera.capture_photo()
frame.save("/tmp/photo.jpg")

Permission Required

Camera access requires the user to grant the Camera permission. Use app.permissions.check(nib.Permission.CAMERA) and app.permissions.request(nib.Permission.CAMERA) before accessing the camera.

Methods

list_devices()

List all available camera devices on the system.

app.camera.list_devices() -> list[CameraDevice]

Returns a list of CameraDevice dataclass instances.

capture_photo(device_id, format, quality)

Capture a single photo from a camera.

app.camera.capture_photo(
    device_id: str | None = None,
    format: str = "jpeg",
    quality: float = 0.9,
) -> CameraFrame
Parameter Type Default Description
device_id str \| None None Camera device ID from list_devices(). Uses the default camera if None
format str "jpeg" Image format: "jpeg" or "png"
quality float 0.9 JPEG compression quality from 0.0 to 1.0. Ignored for PNG

Returns a CameraFrame dataclass with the captured image data.

start_stream(callback, device_id, fps)

Start streaming video frames from a camera. Frames are delivered to the callback function at approximately the specified frame rate. Only one stream can be active at a time.

app.camera.start_stream(
    callback: Callable[[CameraFrame], None],
    device_id: str | None = None,
    fps: int = 30,
) -> bool
Parameter Type Default Description
callback Callable[[CameraFrame], None] -- Function called with each frame
device_id str \| None None Camera device ID. Uses the default camera if None
fps int 30 Target frames per second (1--60)

Returns True if streaming started successfully.

stop_stream()

Stop the active video frame stream.

app.camera.stop_stream() -> bool

Returns True if streaming stopped successfully.


Data Classes

CameraDevice

Information about an available camera device.

Property Type Description
id str Unique device identifier, used in capture_photo() and start_stream()
name str Human-readable device name (e.g., "FaceTime HD Camera")
position CameraPosition Physical position of the camera
is_built_in bool Whether the camera is built into the device

CameraFrame

A captured camera frame, either from a photo or a video stream.

Property Type Description
data bytes Raw image data
width int Image width in pixels
height int Image height in pixels
format str Image format ("jpeg" or "png")

CameraFrame.save(path)

Save the frame to a file.

frame.save(path: str) -> None
Parameter Type Description
path str File path to save the image (e.g., "/tmp/photo.jpg")

Enums

CameraPosition

from nib.services.camera import CameraPosition
Value Description
CameraPosition.FRONT Front-facing camera (e.g., FaceTime camera)
CameraPosition.BACK Rear-facing camera (not typical on Macs)
CameraPosition.EXTERNAL External USB or Thunderbolt camera

Examples

List cameras and capture a photo

import nib

def main(app: nib.App):
    app.title = "Camera"
    app.icon = nib.SFSymbol("camera")
    app.width = 320
    app.height = 200

    status = nib.Text("Ready", foreground_color=nib.Color.SECONDARY)
    device_list = nib.Text("", foreground_color=nib.Color.SECONDARY)

    def list_cameras():
        devices = app.camera.list_devices()
        if devices:
            names = [f"{d.name} ({d.position.value})" for d in devices]
            device_list.content = "\n".join(names)
        else:
            device_list.content = "No cameras found"

    def take_photo():
        status.content = "Capturing..."
        frame = app.camera.capture_photo(format="jpeg", quality=0.9)
        frame.save("/tmp/nib_photo.jpg")
        status.content = f"Saved {frame.width}x{frame.height} photo"

    app.on_appear = list_cameras

    app.build(
        nib.VStack(
            controls=[
                nib.Text("Camera", font=nib.Font.HEADLINE),
                device_list,
                nib.HStack(
                    controls=[
                        nib.Button("List Cameras", action=list_cameras),
                        nib.Button("Take Photo", action=take_photo,
                                   style=nib.ButtonStyle.BORDERED_PROMINENT),
                    ],
                    spacing=8,
                ),
                status,
            ],
            spacing=12,
            padding=20,
        )
    )

nib.run(main)

Stream video frames for processing

import nib

def main(app: nib.App):
    app.title = "Stream"
    app.icon = nib.SFSymbol("video")
    app.width = 300
    app.height = 150

    frame_count = nib.Text("Frames: 0")
    count = 0

    def on_frame(frame):
        nonlocal count
        count += 1
        if count % 30 == 0:  # Update display every 30 frames
            frame_count.content = f"Frames: {count} ({frame.width}x{frame.height})"

    def start():
        app.camera.start_stream(on_frame, fps=15)
        frame_count.content = "Streaming..."

    def stop():
        app.camera.stop_stream()
        frame_count.content = f"Stopped at {count} frames"

    app.build(
        nib.VStack(
            controls=[
                frame_count,
                nib.HStack(
                    controls=[
                        nib.Button("Start", action=start, style=nib.ButtonStyle.BORDERED_PROMINENT),
                        nib.Button("Stop", action=stop, role=nib.ButtonRole.DESTRUCTIVE),
                    ],
                    spacing=8,
                ),
            ],
            spacing=12,
            padding=20,
        )
    )

nib.run(main)