Live from the studio
A small program on a computer in your gallery takes the programme sound, streams it to Signa, and the signer on your overlay signs as people speak, a couple of seconds behind them.
What you need
- An overlay already on screen in your graphics system.
- A computer that can hear the programme: through a capture card (SDI), a sound card's line input, or a stream (SRT, RTMP, NDI-to-audio).
- Node.js 18 or later (or Python 3.10) and ffmpeg on that computer.
Which sound
Send the cleanest speech you have: the presenters' microphones or a clean feed without music beds and effects. Signing follows speech; music and crowd noise only get in its way.
The bridge
Node.js
// signa-bridge.mjs: studio audio in, the signer on air.
// Needs Node 18+ and ffmpeg. Run: npm i ws && node signa-bridge.mjs
import { spawn } from "node:child_process";
import WebSocket from "ws";
const KEY = process.env.SIGNA_KEY; // sk_live_…
const OVERLAY = process.env.OVERLAY_ID; // from POST /v1/overlays
const LANG = process.env.LANG_SPOKEN ?? "rw";
const TO = process.env.LANG_SIGNED ?? "rsl";
// Where the sound comes from: any ffmpeg input (see the table below).
const INPUT = (process.env.SIGNA_INPUT ?? "-f alsa -i default").split(" ");
function run() {
const ws = new WebSocket("wss://api.usesigna.org/v1/live");
let ffmpeg;
ws.on("open", () =>
ws.send(JSON.stringify({ type: "start", lang: LANG, token: KEY, overlay: OVERLAY, to: TO })),
);
ws.on("message", (data, binary) => {
if (binary) return;
const msg = JSON.parse(data.toString());
if (msg.type === "ready") {
// 16 kHz, mono, 16-bit PCM on stdout, in real time.
ffmpeg = spawn("ffmpeg", ["-hide_banner", "-loglevel", "error", ...INPUT,
"-vn", "-ac", "1", "-ar", "16000", "-f", "s16le", "pipe:1"]);
ffmpeg.stdout.on("data", (pcm) => ws.readyState === ws.OPEN && ws.send(pcm));
ffmpeg.on("exit", () => ws.close());
console.log("On air: signing", LANG, "→", TO);
}
if (msg.type === "final") console.log("signed:", msg.text);
if (msg.type === "error") console.error("Signa:", msg.message);
});
// Sessions end after 12 hours or a network drop: start again.
ws.on("close", () => {
ffmpeg?.kill();
setTimeout(run, 2000);
});
ws.on("error", () => {});
}
run();Python
# signa_bridge.py: the same in Python. pip install websockets
import asyncio, json, os, shlex
import websockets
KEY, OVERLAY = os.environ["SIGNA_KEY"], os.environ["OVERLAY_ID"]
INPUT = shlex.split(os.environ.get("SIGNA_INPUT", "-f alsa -i default"))
async def session():
async with websockets.connect("wss://api.usesigna.org/v1/live", max_size=None) as ws:
await ws.send(json.dumps({"type": "start", "lang": "rw", "token": KEY,
"overlay": OVERLAY, "to": "rsl"}))
ff = None
async for message in ws:
msg = json.loads(message)
if msg["type"] == "ready":
ff = await asyncio.create_subprocess_exec(
"ffmpeg", "-hide_banner", "-loglevel", "error", *INPUT,
"-vn", "-ac", "1", "-ar", "16000", "-f", "s16le", "pipe:1",
stdout=asyncio.subprocess.PIPE)
async def pump():
while chunk := await ff.stdout.read(3200): # 100 ms
await ws.send(chunk)
asyncio.create_task(pump())
elif msg["type"] == "final":
print("signed:", msg["text"])
if ff:
ff.kill()
async def main():
while True: # sessions end after 12 hours or a network drop
try:
await session()
except Exception as e:
print("reconnecting:", e)
await asyncio.sleep(2)
asyncio.run(main())Run it
SIGNA_KEY=sk_live_… OVERLAY_ID=5f0c2b8e-… \
SIGNA_INPUT="-f decklink -i 'DeckLink Mini Recorder'" \
node signa-bridge.mjsChoosing the input
SIGNA_INPUT is whatever comes before the output in an ffmpeg command. Common studio sources:
| Source | SIGNA_INPUT |
|---|---|
| SDI through a Blackmagic DeckLink card | -f decklink -i 'DeckLink Mini Recorder' |
| Line input, Linux | -f alsa -i hw:1 |
| Line input, Windows | -f dshow -i audio="Line In (USB Audio)" |
| Line input, macOS | -f avfoundation -i :0 |
| An SRT feed from your encoder | -i srt://0.0.0.0:9000?mode=listener |
| An RTMP push from vMix, OBS or an encoder | -listen 1 -i rtmp://0.0.0.0:1935/live/signa |
| An HLS or web stream | -re -i https://…/playlist.m3u8 |
| A test, from a file | -re -i amakuru.mp4 |
ffmpeg -sources (and ffmpeg -f decklink -list_devices 1 -i dummy for DeckLink) lists what your computer has.
On air
- Start the bridge before the programme and leave it running; it reconnects by itself.
- The caption on the overlay shows what is being signed, so the gallery can see it working.
- Between programmes, stop the bridge or send silence: live minutes count while it is connected.
- To sign scripted parts (the news read from the prompter) word-perfect, send the script with /v1/overlays/:id/sign instead, and use live audio for the unscripted parts.