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.
- 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.
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.
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.
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.
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-utilsDebian 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-utilsgh repo clone chbornman/storybooth
cd storybooth
bun install
cp config.example.toml config.tomlEdit 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.
bun run tauri devKeep [display].kiosk = false until the complete flow and attendant exit have
been tested.
bun run tauri buildUse the Tauri command rather than plain cargo build --release; Tauri embeds
the frontend and produces the distributable bundles.
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:
STORYBOOTH_CONFIG=/absolute/path/to/profile.toml- ignored
config.tomlduring development - the per-user app config created on first release run
- 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/storyboothCommitted 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.
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-boothSessions 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.
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.tomlThe ignored local deployment can be checked explicitly:
STORYBOOTH_CONFIG="$PWD/config.toml" bun run tauri devThe 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 --nocaptureBuilt 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.
MIT © 2026 Caleb Bornman / Iris Automation Systems LLC.