Skip to content

Latest commit

 

History

History
288 lines (216 loc) · 8.18 KB

File metadata and controls

288 lines (216 loc) · 8.18 KB

StoryBooth

StoryBooth

StoryBooth is a configurable, fully offline video story-capture kiosk. A participant steps up to a touch display, reviews the organizer's consent language, answers a set of prompts on camera, reviews their takes, and submits the finished session to local storage.

It is a single Tauri desktop application for Linux: React powers the touch UI, while Rust and GStreamer own camera discovery, live preview, recording, and durable session storage. No cloud service, user account, analytics SDK, or internet connection is used at runtime.

The project was built for and proven at a live, one-day community storytelling event. The public repository contains no event-specific wording, branding, participant data, or hardware profile. Those belong in ignored deployment configuration.

What it does

  • Runs as a fullscreen, touch-oriented kiosk with an attendant exit gesture.
  • Collects zero or more configurable participant metadata fields.
  • Displays deployment-specific consent, instructions, and recording tips.
  • Supports any number of video questions.
  • Records H.264 video with AAC audio directly to local MP4 files.
  • Provides a live preview, countdown, time warnings, skip, re-record, and final review.
  • Stores the exact consent text, participant fields, prompts, and take metadata alongside each session.
  • Resets abandoned sessions after a configurable inactivity timeout.
  • Selects camera, microphone, monitor, timings, content, and branding without source changes.

Participant flow

attract
  → participant fields
  → consent
  → instructions
  → recording tips
  → question × N
  → review / re-record
  → submit
  → thank you

Declining consent or abandoning a session discards its working directory. Submitting writes one self-contained folder with the retained takes and a session.json sidecar.

Architecture

React touch UI
      │ Tauri commands
      ▼
Rust application core
  ├─ configuration + kiosk runtime
  ├─ session lifecycle + JSON metadata
  └─ native GStreamer capture
       ├─ V4L2 camera
       ├─ PulseAudio microphone
       ├─ loopback-only MJPEG preview
       └─ H.264/AAC MP4 recording

Capture is intentionally native. WebKitGTK's browser-media path could not reliably negotiate a production UVC camera, while direct GStreamer/V4L2 capture was stable. The webview never receives camera permissions or raw recording bytes; it only drives Rust commands and displays the loopback preview.

See Architecture for the capture pipeline, configuration precedence, storage model, and privacy boundaries.

Platform and hardware

StoryBooth currently targets x86-64 Linux.

  • A V4L2 camera that advertises an MJPG mode
  • A PulseAudio/PipeWire microphone source
  • A touch display for kiosk use
  • GStreamer with the base, good, bad, ugly, and libav plugin sets
  • WebKitGTK 4.1, required by Tauri

It has been exercised with a mirrorless camera in USB-streaming mode, a USB audio interface, an external touch display, and integrated laptop devices. Software x264 encoding is the portable default; optional VAAPI H.264 encoding is available after validation on compatible Intel hardware.

Quick start

1. Install system dependencies

Arch Linux:

sudo pacman -S --needed \
  base-devel curl wget file openssl webkit2gtk-4.1 \
  appmenu-gtk-module libappindicator-gtk3 librsvg \
  gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad \
  gst-plugins-ugly gst-libav v4l-utils

Debian or Ubuntu:

sudo apt install \
  build-essential curl wget file libssl-dev libwebkit2gtk-4.1-dev \
  libxdo-dev libayatana-appindicator3-dev librsvg2-dev \
  gstreamer1.0-tools gstreamer1.0-plugins-base \
  gstreamer1.0-plugins-good gstreamer1.0-plugins-bad \
  gstreamer1.0-plugins-ugly gstreamer1.0-libav \
  gstreamer1.0-pulseaudio v4l-utils

Install stable Rust and Bun.

2. Clone and configure

gh repo clone chbornman/storybooth
cd storybooth
bun install
cp config.example.toml config.toml

Edit config.toml before running. At minimum:

  • choose a camera and microphone;
  • replace the example consent language;
  • set participant fields and prompts;
  • choose timings and recording geometry;
  • add organizer branding if desired.

config.toml is ignored by Git.

3. Run in development

bun run tauri dev

Keep [display].kiosk = false until the complete flow and attendant exit have been tested.

4. Build

bun run tauri build

Use the Tauri command rather than plain cargo build --release; Tauri embeds the frontend and produces the distributable bundles.

Deployment configuration

config.example.toml documents every setting. A deployment owns:

  • monitor, camera, microphone, resolution, frame rate, and encoder;
  • countdown and timeout behavior;
  • participant field keys, labels, and required policy;
  • attract, consent, instruction, tip, question, and thank-you text;
  • logo, accessible logo label, and accent color.

Configuration is resolved in this order:

  1. STORYBOOTH_CONFIG=/absolute/path/to/profile.toml
  2. ignored config.toml during development
  3. the per-user app config created on first release run
  4. the generic embedded example

For multiple deployments, keep ignored profiles such as config.conference.local.toml and launch one explicitly:

STORYBOOTH_CONFIG="$PWD/config.conference.local.toml" \
  src-tauri/target/release/storybooth

Branding

Committed generic assets live in public/. Put deployment-only assets under public/branding.local/, which is ignored:

public/branding.local/organizer-logo.svg

Then reference the bundled web path:

[branding]
logo = "/branding.local/organizer-logo.svg"
logo_alt = "Organizer name"
accent = "#ffcc33"

An empty logo disables the logo card.

Interactive device selection

After building, ./run-booth enumerates cameras and microphones, lets the operator select them, generates a temporary launch profile, and starts the binary. It uses config.toml as its base when present and otherwise uses the example.

Override either path when packaging elsewhere:

STORYBOOTH_BASE_CONFIG=/opt/storybooth/event.local.toml \
STORYBOOTH_BIN=/opt/storybooth/storybooth \
  ./run-booth

Storage and privacy

Sessions are stored under the platform app-data directory:

~/.local/share/com.iris.storybooth/sessions/<date>_<id>/
├── q1.mp4
├── q2.mp4
└── session.json

The application:

  • makes no outbound network requests at runtime;
  • binds its preview server only to 127.0.0.1;
  • never uploads recordings;
  • removes declined or abandoned working sessions;
  • stores the exact displayed consent language with submitted recordings.

Deployment operators remain responsible for legally appropriate consent, physical access, retention, export, backup, and deletion policies. The text in config.example.toml is deliberately marked as an example and is not legal advice.

See the Operator guide for preflight, device checks, dry runs, export, and troubleshooting.

Development checks

bun run build
cargo fmt --manifest-path src-tauri/Cargo.toml --check
cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings
cargo test --manifest-path src-tauri/Cargo.toml

The ignored local deployment can be checked explicitly:

STORYBOOTH_CONFIG="$PWD/config.toml" bun run tauri dev

The headless capture test needs exclusive access to a real camera:

cargo test --manifest-path src-tauri/Cargo.toml --lib \
  capture::tests::records_a_real_take -- --ignored --nocapture

Current scope

Built and field-tested:

  • config-driven participant flow;
  • native preview and MP4 capture;
  • local session manifests;
  • device validation, inactivity reset, review, and kiosk exit;
  • software and optional VAAPI H.264 encoding.

Not yet included:

  • an attendant-facing session browser;
  • in-app USB export;
  • configurable retention automation;
  • Windows or macOS capture backends.

License

MIT © 2026 Caleb Bornman / Iris Automation Systems LLC.