Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions docs/COMPARISON.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ but it is not zero, and a selector's is.
|---|---|---|---|---|---|
| **Per-destination audio mix** from one multitrack ingest | No | No | No | No | No |
| **Channel-level mix matrix** with per-cell gain | No | No | No | No | No |
| **Per-destination loudness target**, measured after routing | No | No | No | No | No |
| **Per-destination loudness TARGET you set** — I, LRA and TP, measured after routing | Filter only<sup>4</sup> | No | No | No | No |
| **Multitrack archive** — every ingest track preserved, stream-copied | No | No | Via OBS | Via OBS | Yes |
| **Per-track stems** as 24-bit WAV or FLAC, segment-aligned to the master | No | No | No | No | No |
| Track annotations — what each incoming track actually is | No | No | n/a | n/a | Unverified |
| Typed SRT rejections — the publisher is told *why* it was refused | No | n/a | n/a | n/a | Unverified |
| A second audio mix to the same destination, from one ingest | No | n/a | n/a | n/a | Twitch Enhanced Broadcasting only; competitors unverified |
| A second audio mix to the same destination, from one ingest | No | n/a | n/a | n/a | Twitch Enhanced Broadcasting, and it needs a supported GPU; competitors unverified |

Two cells say **Unverified** rather than "No" on purpose. MistServer's own
recording row is a loss and is marked as one: a recording target with
Expand Down Expand Up @@ -221,7 +221,7 @@ reconnect when a platform drops, show you whether it is working.
| Video re-encoded per destination | **No** (`-c:v copy`) | Optional | Yes, server-side | Optional, as a process |
| Recording | Multitrack, stream-copied | No built-in<sup>2</sup> | Yes | Yes, tracks selectable |
| Unified chat | Yes | No | Yes | No |
| Metrics / API | Prometheus + REST | REST | REST | REST |
| Metrics / API | Prometheus + REST | See<sup>4</sup> | REST | REST |
| Hardware encoding | NVENC, QSV, VA-API, VideoToolbox, AMF | Yes | n/a | NVENC |
| Public release in the last 12 months | Yes<sup>3</sup> | No<sup>3</sup> | n/a, hosted | Yes<sup>3</sup> |
| Cost | Your hardware | Your hardware | Subscription | Your hardware |
Expand All @@ -244,6 +244,23 @@ tagged v0.1.0 on 2026-07-31 and v0.6.0 on 2026-08-09 — which is the velocity o
something pre-release with one maintainer, and is worth exactly that much. All
three checked 2026-08-09.

<sup>4</sup> **This row said "No" against Restreamer and that was wrong.**
Restreamer mounts a filter select per publication service, and `loudnorm` is one
of the filters it offers, so it does apply a per-destination loudness filter.
What it does not offer is a *target you set*: its control is an on/off checkbox
that emits the bare string `loudnorm` with no I, LRA or TP, so it takes FFmpeg's
defaults and an operator cannot ask for −16 LUFS rather than −24. That is the
real difference and it is narrower than the row used to claim. Corrected
2026-08-14 after a sweep read their source rather than their feature list — the
same correction the recording row needed in the other direction, and the second
time a cell here has asserted a competitor lacks something they ship.

The metrics row above was wrong the same way and is now blank on their side:
Restreamer's own README advertises resource monitoring "optionally by
Prom-Metrics", datarhei Core documents Prometheus support, and it serves GraphQL
as well as REST — so "REST" understated them twice over and implied Prometheus
was ours alone.

---

## See also
Expand Down
23 changes: 20 additions & 3 deletions web/src/pages/comparison.astro
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ const shared = [
// Restreamer product simply does not put recording in front of you, which is
// why the request exists. See docs/COMPARISON.md for the full footnote.
["Recording", "Multitrack, stream-copied", "No built-in", "Yes"],
["Metrics / API", "Prometheus + REST", "REST", "REST"],
// Restreamer's cell said "REST" and implied Prometheus was ours alone. Its
// own README advertises resource monitoring "optionally by Prom-Metrics" and
// datarhei Core documents Prometheus support, and it serves GraphQL as well
// as REST. Neither half of the old cell survived contact with their docs, so
// the row now states what we do and says nothing about what they do.
["Metrics / API", "Prometheus + REST", "", ""],
["Hardware encoding", "NVENC, QSV, VA-API, VideoToolbox, AMF", "Yes", "n/a"],
["Unified chat", "Yes", "No", "Yes"],
["Self-hosted", "Yes", "Yes", "No"],
Expand All @@ -50,12 +55,24 @@ const shared = [
const wins = [
["Per-destination audio mix from one multitrack ingest", "Yes", "No", "No"],
["Channel-level mix matrix with per-cell gain", "Yes", "No", "No"],
["Per-destination loudness target, measured after routing", "Yes", "No", "No"],
// "No" against Restreamer was wrong as written: it mounts a filter select per
// publication service and loudnorm is one of the filters offered, so it DOES
// apply a per-destination loudness filter. What it does not offer is a
// configurable target -- its control is an on/off checkbox emitting the bare
// string `loudnorm`, taking FFmpeg's defaults for I, LRA and TP. That is the
// real difference, so the row states it and leaves their cell alone.
["Per-destination loudness TARGET you set — I, LRA and TP, measured after routing", "Yes", "", ""],
["Multitrack archive — every ingest track preserved, stream-copied", "Yes", "No", "No"],
["Per-track stems as 24-bit WAV or FLAC, segment-aligned", "Yes", "No", "No"],
["Track annotations — what each incoming track actually is", "Yes", "No", "No"],
["Typed SRT rejections — the publisher is told why it was refused", "Yes", "No", "n/a"],
["A second audio mix to the same destination, from one ingest", "Yes", "—", "—"],
// "Yes" on its own was the wrong shape for this cell, and wiring the feature
// is what made it matter: Twitch refuses Enhanced Broadcasting without a
// supported GPU, so on the headless VPS this audience actually runs, ticking
// the toggle gets a quiet fallback to the ordinary ingest. A bare "Yes" in a
// comparison table is the worst place to omit that. COPY-CONSTRAINTS.md says
// to state it in the same block that announces the feature, not a footnote.
["A second audio mix to the same destination, from one ingest", "Yes — Twitch Enhanced Broadcasting, needs a GPU", "—", "—"],
["Runs on hardware you control, no per-destination pricing", "Yes", "Yes", "No"],
];

Expand Down
3 changes: 2 additions & 1 deletion web/src/pages/features.astro
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ const sections = [
body: [
"Twitch Enhanced Broadcasting negotiates a second audio track alongside the live one. polyemesis builds both from the same ingest, in one filter graph, and sends them over one connection — the live mix with the music bed, and a VOD track without it. No second upload, no second encode of the picture.",
"A single video rendition is enough. Multitrack video is not a precondition for the second audio track, which is the part most people expect to be required — the feature is named for it and does not need it.",
"It needs a GPU. Twitch refuses the negotiation outright if the encoder reports none, so this is the one thing here a headless server cannot use. Every other destination still receives its own single mix, exactly as before. The picture is still copied rather than re-encoded; the second track is a second AAC encode, which is cheap and is not free.",
"It needs a GPU, and it needs you to say so. Twitch refuses the negotiation outright if the request reports no GPU, so this is the one thing here a headless server cannot use. polyemesis does not guess at your hardware — you declare it in Settings, because Twitch validates the inventory and a fabricated one is refused by name. Leave it blank and the destination publishes to the ordinary Twitch ingest and tells you once, which is the right behaviour on the majority of installs rather than an error.",
"Every other destination still receives its own single mix, exactly as before. The picture is still copied rather than re-encoded; the second track is a second AAC encode, which is cheap and is not free.",
],
shot: "02-routing.png",
caption: "The archive mix is per destination, and off unless asked for.",
Expand Down
2 changes: 1 addition & 1 deletion web/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ const features = [
polyemesis ingests once and lets you pick, per destination, which
tracks get summed into the stereo feed that destination receives.
YouTube gets the clean mix. Your local archive keeps everything. Your
Discord restream keeps the mic hot. On Twitch it goes one step further — the broadcast can carry the music bed while a separate archive track leaves it out, from the same upload.
Discord restream keeps the mic hot. On Twitch it goes one step further — the broadcast can carry the music bed while a separate archive track leaves it out, from the same upload. That last one is Twitch Enhanced Broadcasting, and it <strong>needs a supported GPU</strong>: Twitch refuses to negotiate it otherwise, so on a headless server the destination publishes to the ordinary ingest instead and says so.
</p>
</div>
</div>
Expand Down
Loading