Skip to content

Latest commit

 

History

History
402 lines (310 loc) · 13.4 KB

stage-publishing.md

File metadata and controls

402 lines (310 loc) · 13.4 KB

Publish and schedule a Stage

Stage is Telex's transient text-cell video surface. A publisher converts video outside the IRC daemon, then sends complete Unicode/xterm256 frames and optional AAC audio through the separate Stage WebSocket. Frames are not chat messages and never become history, unread state, search results, reactions, or Telex database rows.

The bundled publisher supports two paths:

  • publish converts a local media file while broadcasting it;
  • encode converts once into a .tstage archive, while play and station broadcast that archive later without FFmpeg, Chafa, or the source media.

Use the second path for a recurring channel, a library of clips, or a host where predictable playback matters more than saving local disk.

1. Provision narrow authority

Create a bot automation and one app key in the web client's automation management view. Save both one-time values:

  • the authentication ID, shaped like StageBot/<server-generated-key-uuid>;
  • the ak1... app key.

The authentication ID suffix is the server-generated key UUID, not the key's human label.

In the target channel's automation view, add the account with a custom grant containing only:

stage.publish

Set invocation context to zero. Stage publication needs no message, history, roster, or invocation permission. That grant lets the key occupy the channel's single transient Stage and nothing else. Removing the grant or revoking the key ends a live publisher.

2. Install the publisher

Node.js 22 or newer is the only playback dependency. Conversion and preview also need FFmpeg, FFprobe, Chafa development headers, pkg-config, and a C compiler.

On Ubuntu:

sudo apt install \
  nodejs ffmpeg chafa libchafa-dev libglib2.0-dev pkg-config build-essential

examples/stage/build-projector.sh
examples/stage/telex-stage doctor

On macOS with Homebrew:

brew install node ffmpeg chafa pkg-config

examples/stage/build-projector.sh
examples/stage/telex-stage doctor

The repository release process can build a self-contained source kit:

packaging/package-stage-publisher.sh

The resulting tar archive keeps the small shared Stage protocol/session modules, publisher, projector source, documentation, license, revision, and per-file checksums together. It also contains a deterministic, silent examples/stage/sample.tstage test pattern. Unpack it, run examples/stage/build-projector.sh, and use examples/stage/telex-stage as below.

For a downloaded release, verify the checksum published beside the outer archive before extraction. Then verify every packaged file and inspect the included sample:

tar -xzf telex-stage-publisher-0.2.0-REVISION.tar.gz
cd telex-stage-publisher-0.2.0
sha256sum -c SHA256SUMS

examples/stage/telex-stage inspect \
  --input examples/stage/sample.tstage

The included sample needs only Node.js 22 or newer. It can be published with play, placed in a station playlist, or regenerated exactly:

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

Optional --columns, --rows, --fps, --seconds, and --title values produce a custom synthetic test pattern. The canonical no-option sample is 64×18 cells, 12 fps, four seconds, silent, and contains no third-party media.

Container option

Build the checked-in container from the repository or unpacked kit:

docker build \
  --file examples/stage/Containerfile \
  --build-arg TELEX_STAGE_VERSION=0.2.0 \
  --build-arg TELEX_STAGE_REVISION="$(git rev-parse HEAD 2>/dev/null || printf unknown)" \
  --tag telex-stage-publisher:0.2.0 \
  --tag telex-stage-publisher:latest \
  .

The image compiles its projector in a disposable build stage, retains only the runtime media libraries, and runs as the unprivileged node user. The :latest tag below is a local alias; no registry account or image publication is required.

Check the finished image before using it:

docker run --rm telex-stage-publisher:latest doctor

Mount media read-only and an output directory read-write:

docker run --rm -it \
  --volume "$PWD/media:/media:ro" \
  --volume "$PWD/stages:/stages" \
  telex-stage-publisher:latest \
  encode \
  --input /media/clip.mp4 \
  --output /stages/clip.tstage \
  --title "Clip"

For publication, export IRC_CLIENT_PASSWORD in the invoking shell and pass only the variable name into the container:

docker run --rm -it \
  --env IRC_CLIENT_PASSWORD \
  --volume "$PWD/stages:/stages:ro" \
  telex-stage-publisher:latest \
  play \
  --url https://irc.example.net:8098 \
  --auth-id 'StageBot/019f0000-0000-7000-8000-000000000000' \
  --channel '#movie-night' \
  --input /stages/clip.tstage

Do not place the app key in an image, URL, playlist, or command argument.

3. Preview and publish directly

The normal baseline is 160 columns, automatic rows, and 24 frames per second. Preview one source frame locally:

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

Open the HTML file in a browser. It is self-contained and never contacts the server.

Publish the source directly:

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

The app key comes from IRC_CLIENT_PASSWORD or a hidden terminal prompt. --raw-audio preserves source levels instead of normalizing to -16 LUFS. --silent omits audio.

Available visual presets are:

Preset Columns Intended use
compact 120 small displays or constrained links
balanced 160 default
detail 200 deliberate higher detail

Every preset uses automatic rows and 24 fps. Explicit --columns, --rows, --fps, --sample-scale, --profile, and --color-mode overrides remain available.

4. Pre-render a .tstage archive

Encode once:

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

Encoding never connects to Telex and refuses to replace an existing output file. A .tstage archive contains:

  • the validated codec, glyph dictionary, palette model, geometry, rate, and optional audio declaration;
  • the exact encoded Stage video and audio packet timeline;
  • playback lead and bounded packet statistics;
  • a gzip-compressed record stream; and
  • a SHA-256 checksum covering the manifest and compressed packet body.

It does not contain the source video, Telex credentials, channel name, account name, or IRC history. It is not encrypted. Treat private or licensed material inside the rendered frames and audio accordingly.

Inspect and fully verify an archive:

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

Use --json for a machine-readable manifest. Verification checks the complete archive checksum, bounded decompression, exact packet lengths, contiguous transport sequence, per-track timing, audio initialization, and declared statistics.

Publish it:

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

play verifies the entire archive before it requests the app key or opens a Stage. Playback then streams and decompresses records in bounded memory. Required audio packets retry briefly under backpressure; late video frames are disposable so transport congestion cannot grow delay without bound.

The v1 archive is deliberately sequential. It supports deterministic playback and playlists, not seeking or mid-clip resume.

Archive compatibility and exchange

The 0.1.x publisher kit targets Stage API 1, archive telex.stage.archive version 1, and cell codec telex.cells version 1. inspect, play, and station reject unknown versions rather than guessing. While Telex remains pre-1.0, distribute the publisher kit or its exact revision beside archives instead of assuming an arbitrary future kit will accept them.

.tstage files are ordinary opaque artifacts and fit the external-content/XDCC interface without giving the content provider Stage authority. A catalogue entry should use:

{
  "package_id": "opening-clip",
  "display_name": "Opening clip",
  "description": "Telex Stage archive v1",
  "filename": "opening-clip.tstage",
  "path": "./files/opening-clip.tstage",
  "media_type": "application/vnd.telex.stage",
  "visibility": "human_members",
  "consumption": "human_members"
}

The ArtifactProvider computes the descriptor's byte size and whole-file SHA-256. The digest printed by telex-stage inspect is the archive's internal digest over its header, manifest, and compressed packet body; it is not the same representation as the provider's whole-file digest. Verify the external transfer digest first, then run inspect before playback.

The provider needs only its normal external-content permissions. A recipient who wants to broadcast the downloaded archive uses a separate automation key with stage.publish in the destination channel. Neither the .tstage file nor its catalogue descriptor carries credentials or grants publication authority.

5. Run a sequential station

Create a playlist beside its archives:

{
  "format": "telex.stage.playlist",
  "version": 1,
  "loop": true,
  "intermission_ms": 1000,
  "items": [
    { "path": "./opening.tstage" },
    { "path": "./feature.tstage" }
  ]
}

Relative item paths resolve from the playlist directory. Start the station:

examples/stage/telex-stage station \
  --url https://irc.example.net:8098 \
  --auth-id 'StageBot/019f0000-0000-7000-8000-000000000000' \
  --channel '#movie-night' \
  --playlist ./playlist.json \
  --state ./station-state.json

Before prompting for the key, the station reads the bounded playlist and fully verifies every archive. It then:

  1. opens one Stage for the current item;
  2. plays the item to completion;
  3. ends that Stage;
  4. atomically advances the private checkpoint; and
  5. starts the next item after the configured intermission.

Each item may have different geometry or audio. A new Stage epoch makes that transition explicit to viewers. If the process dies during an item, the checkpoint intentionally restarts that item from its beginning. Changing the playlist invalidates the old cursor and starts at item one. A non-looping list records completion and does not replay on service restart.

The station never publishes a chat announcement and therefore still needs only stage.publish. A future announcer should use a separate key or an explicitly reviewed message.publish grant rather than silently widening this process.

examples/stage/telex-stage-station.service.example shows a minimal systemd service using LoadCredential. Replace every example path, account, host, and channel before installing it. The credential source should be a root-readable file with restrictive permissions; systemd exposes it to the service through the private credentials directory rather than a process argument or ordinary environment file.

Capacity and operational notes

The reference full-frame format favors simplicity and resilience over delta compression. Typical balanced video is roughly 4–6 Mbit/s before small WebSocket/TLS overhead, plus about 0.128 Mbit/s for AAC. Relay egress is that rate per viewing client. A pre-rendered archive is often much smaller on disk because gzip can exploit similarity between neighboring text-cell frames, but network playback still sends complete frames.

Only one publisher can occupy a channel Stage. A second receives an occupied error. Frames remain outside chat retention and Telex database backups; .tstage, playlist, and station-state files belong to the publisher host's own storage and backup policy.

Troubleshooting:

  • authentication_required: use the returned account/server-generated-key-uuid authentication ID and matching ak1... key, not the key label.
  • forbidden or stage_unavailable: confirm the exact channel grant contains stage.publish and the account/key are active.
  • occupied: another publisher owns the channel Stage.
  • missing projector: install Chafa development headers, then run examples/stage/build-projector.sh.
  • no audible playback: confirm source audio exists, browser audio is unmuted, and the browser accepted its explicit play gesture.

Release verification

From a clean source checkout, build the publisher archive twice, compare it for reproducibility, verify all packaged checksums, regenerate the canonical sample, inspect it, and exercise the packaged projector toolchain with:

packaging/test-stage-publisher-package.sh

Pass an existing release archive to test it without rebuilding:

packaging/test-stage-publisher-package.sh \
  /path/to/telex-stage-publisher-0.2.0-REVISION.tar.gz

If conversion dependencies are unavailable, the pure-Node package and sample checks still run and the projector build is reported as skipped. Set TELEX_STAGE_SMOKE_CONVERSION=required to make that skip fail.

Two acceptance checks remain deliberately external to the automated suite:

  • build the Containerfile with Docker or Podman on a container-capable host;
  • run a real two-item station, observe the item transition from multiple viewers, stop it mid-item, and confirm systemd restart begins that item again.

The wire, timing, Canvas, authority, and retention boundaries are specified in Stage text-cell video.