Skip to content

Latest commit

 

History

History
228 lines (183 loc) · 7.49 KB

File metadata and controls

228 lines (183 loc) · 7.49 KB

Stage publishers

The synthetic publisher is a transport test, not a video encoder. It sends an animated telex.cells test pattern through the same ticket, WebSocket, relay, timing, and backpressure path that the eventual FFmpeg/Chafa projector will use. Nothing it sends becomes IRC traffic or retained chat history.

Provision

Create a bot or agent automation and an app key, then grant it only stage.publish in the target channel. No native automation operations are needed for Stage publication:

{
  "channel": "#movie-night",
  "expected_revision": 1,
  "account_name": "StageBot",
  "grant": {
    "permissions": ["stage.publish"],
    "history_window_seconds": null,
    "invocation_context_events": 0
  }
}

The account does not receive channel messages through this transport. Its publisher authority exists only while that exact grant remains active.

Run

Use Node.js 22 or newer from the repository root. Supply the app key through the hidden prompt or IRC_CLIENT_PASSWORD; never place it on the command line.

node examples/stage/synthetic-publisher.js \
  --url https://irc.example.net:8098 \
  --auth-id 'StageBot/019f0000-0000-7000-8000-000000000000' \
  --channel '#movie-night' \
  --columns 120 \
  --rows 34 \
  --fps 20 \
  --seconds 20

Use the generated authentication_id returned with the app key. Its suffix is the server-generated app-key UUID, not the human key label. The process closes the Stage when the pattern ends or the process exits. Starting another publisher while the channel Stage is occupied is rejected. The server drops a publisher whose grant is removed while it is live.

Reference video projector

The reference projector is deliberately outside the daemon. FFmpeg decodes and resizes the source into a constant-rate RGB stream; a small Chafa helper turns each frame into curated glyphs and xterm256 foreground/background cells; the Node publisher packs and sends complete Stage frames. When the source has an audio stream, a second FFmpeg path publishes synchronized AAC-LC in fragmented MP4 on the same transient Stage connection.

On Ubuntu 26.04, install the build/runtime dependencies:

sudo apt install ffmpeg chafa libchafa-dev libglib2.0-dev pkg-config

Build the helper:

examples/stage/build-projector.sh

Then publish a local clip:

node examples/stage/video-publisher.js \
  --url https://irc.example.net:8098 \
  --auth-id 'StageBot/019f0000-0000-7000-8000-000000000000' \
  --channel '#movie-night' \
  --input ./clip.mp4 \
  --columns 160 \
  --rows auto \
  --fps 24 \
  --profile video \
  --color-mode foreground

Visual output remains constant-rate. Audio defaults to AAC-LC at 48 kHz, stereo, and 128 kbit/s in approximately 500 ms fragments. It adds about 16 KiB/s per listening viewer beside the much larger cell-video stream. The publisher normalizes source audio to -16 LUFS with a -1.5 dB true-peak ceiling so unusually quiet or loud clips enter the Stage at a predictable listening level. Use --raw-audio to preserve source levels or --silent to suppress source audio. With --rows auto, the publisher probes the source display aspect and derives the cell rows for the reference 8x16 cell shape. A 5:4 source becomes 160x64, the 704x570 comparison clip becomes 160x65, and a 16:9 source becomes 160x45. This avoids transmitting black padding cells. An explicit --rows remains available for a deliberate fixed canvas. --sample-scale controls how many source pixels Chafa sees per cell-width; its default is four. Foreground color mode keeps the presentation visibly character-based by drawing colored glyphs over black. --color-mode cell retains Chafa's foreground and background colors for denser ANSI-art-like output.

To inspect one projected frame without a server, generate a self-contained Canvas preview:

node examples/stage/frame-preview.js \
  --input ./clip.mp4 \
  --output /tmp/stage-preview.html \
  --time 8 \
  --columns 160 \
  --rows 65 \
  --profile video \
  --color-mode foreground

See docs/stage.md for the protocol boundary, selected baseline, measured raw rates, and cross-device acceptance record.

Guided publisher

stage-publisher.js is the normal entry point for file playback. The lower level video publisher and frame preview remain available for integrations and experiments.

First verify the local toolchain:

examples/stage/telex-stage doctor

The guided publisher has three 24 fps visual presets. Rows remain automatic so the source display aspect is preserved:

Preset Columns Approximate raw video rate Intended use
compact 120 2.4–3.3 Mbit/s small screens or constrained links
balanced 160 4.1–5.9 Mbit/s accepted default
detail 200 6.5–9.2 Mbit/s deliberate high-detail playback

Rates span 16:9 through 5:4 sources and exclude the small protocol, WebSocket, and TLS overhead. Relay egress is the selected rate per viewer. All preset choices can be overridden explicitly; they are publisher conveniences, not new protocol profiles.

Project one real frame through the same FFmpeg/Chafa path before broadcasting:

node examples/stage/stage-publisher.js preview \
  --input ./clip.mp4 \
  --preset balanced \
  --time 8 \
  --output /tmp/telex-stage-preview.html

Open the resulting HTML file locally. It is self-contained and never contacts the server.

After reviewing the preview, publish with the same preset:

node examples/stage/stage-publisher.js publish \
  --url https://irc.example.net:8098 \
  --auth-id 'StageBot/019f0000-0000-7000-8000-000000000000' \
  --channel '#movie-night' \
  --input ./clip.mp4 \
  --preset balanced

The command checks dependencies and media readability, resolves the exact cell geometry, reports the estimated raw cell-video rate and audio behavior, and only then asks for the app key. Use --help after any subcommand for its focused options. The password remains confined to IRC_CLIENT_PASSWORD or the hidden prompt.

Create a small deterministic test archive without FFmpeg, Chafa, media, or a server:

examples/stage/telex-stage sample \
  --output ./telex-stage-sample.tstage

Release source kits already include that canonical sample at examples/stage/sample.tstage. It is silent, redistributable under the repository license, and can be passed directly to inspect, play, or station.

Pre-rendered shows and stations

Convert once into a checksummed .tstage artifact:

examples/stage/telex-stage encode \
  --input ./clip.mp4 \
  --output ./clip.tstage \
  --title "Opening clip" \
  --preset balanced

The artifact contains the exact compressed Stage packet timeline, not the source file or a credential. Verify it independently:

examples/stage/telex-stage inspect --input ./clip.tstage

Publish it without FFmpeg, Chafa, or the source media:

examples/stage/telex-stage play \
  --url https://irc.example.net:8098 \
  --auth-id 'StageBot/019f0000-0000-7000-8000-000000000000' \
  --channel '#movie-night' \
  --input ./clip.tstage

station takes a bounded JSON playlist of .tstage files, verifies every item before requesting the app key, and plays them sequentially. An optional atomic state file restarts an interrupted item from its beginning. See playlist.example.json and the complete Stage publishing guide for provisioning, packages, the canonical sample, containers, archive/XDCC exchange semantics, station operation, release checks, and troubleshooting.