Skip to content

feat: add a {{channel:number}} variable for remote source urls - #216

Open
Ministorm3 wants to merge 2 commits into
ErsatzTV:mainfrom
Ministorm3:feat/channel-number-variable
Open

feat: add a {{channel:number}} variable for remote source urls#216
Ministorm3 wants to merge 2 commits into
ErsatzTV:mainfrom
Ministorm3:feat/channel-number-variable

Conversation

@Ministorm3

@Ministorm3 Ministorm3 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

What this adds

A remote source URI can contain {{channel:number}}. The channel worker replaces it at playback with the number of the channel it is transcoding.

"uri": "http://origin.lan:8000/{{channel:number}}/stream.m3u8"

On channel 30 that opens http://origin.lan:8000/30/stream.m3u8. One playout item now serves a whole lineup. Today the same item has to be written once per channel with a literal URI.

Why

A source URI supports only {{VAR}}, which expand_template reads from the server environment. Every channel gets the same string back, so the URIs differ only by a number the server already knows.

The syntax matters more than this one variable. I have further features in progress that need to put server-known values into remote stream URIs. They should all use one convention, and settling its shape is what I am really asking for here.

Design

There is one delimiter, {{ }}. What sits inside it decides who resolves it.

written in the playout who resolves it
{{MY_SECRET}} the environment, exactly as today
{{channel:number}} the server, at playback
{{query:region}} nobody yet, so it passes through as written

A colon means the name is namespaced and the server owns it. No colon means it is an environment variable, and nothing about those changes.

Sharing the delimiter is safe because of how expand_template already behaves. A name that fails is_valid_var_name is written to the output unchanged, neither resolved nor rejected. A colon fails that check, so the environment pass walks past {{channel:number}} and the new pass claims it. That is behavior rather than a documented contract, so one test sends a URI through both passes in order.

The alternative was a second delimiter, { } or [[ ]]. It works, but it leaves two conventions and only history to say which one a variable uses.

The namespace also removes a trap. {{channel_number}} reads like the correct spelling, and it would fail as a missing environment variable.

The new pass runs on http and rtsp URIs only.

Values are substituted with no escaping, and nothing bounds which part of the URI a value can change. That is acceptable only because the operator writes the channel number. A value from a less trusted source needs percent-encoding and an origin rule, which the function documents.

Compatibility

On current main, {{name:with:colons}} is written to the output as written, so no working URI uses this syntax today. No field is added.

Cost

No new dependency. One public function, 23 lines plus tests.

Verification

cargo test --workspace passes 126 tests, 12 of them in the new module. Clippy with -D clippy::all and cargo +nightly fmt --all -- --check are clean.

I ran nine mutations. Seven break one decision each, and each killed only the tests that cover that decision. The other two break copied text rather than substituted text, and killed eleven and nine of the twelve tests. empty_input_stays_empty survives all nine, because it guards an edge case rather than a decision.

It is also running successfully in my production box today.

Open questions

Is the shared delimiter right? It rests on expand_template passing unknown names through. A test holds that, but you may prefer the two syntaxes kept apart. This is the answer my later features are waiting on.

Is channel:number the right name? It leaves room for more channel attributes later. A flat {{channel_number}} is not available, since that is a valid environment variable name.

Is ersatztv-playout the right home? The playout declares the templates. The alternative is the channel worker, the only caller today.

A playout item's remote source uri is fixed apart from {{ENV_VAR}}
expansion, which resolves from the server environment and so produces the
same string for every channel. A uri that should differ per channel has to
be written out per channel.

Add {{channel:number}}, resolved at playback to the channel being
transcoded, for http and rtsp source uris.

The new form shares the {{ }} delimiters with environment templates and is
told apart by its namespace prefix. That is safe because a name containing
a colon is not a valid environment variable name, so expand_template
re-emits it verbatim instead of resolving or rejecting it. This pass runs
second and claims what the first pass left behind. A test pins that
contract, since the whole design rests on it.

Reusing the delimiters avoids a second convention. It also avoids the trap
where {{channel_number}} reads as the natural spelling but fails as a
missing environment variable.

The value comes from the lineup and is substituted verbatim. Nothing here
reads anything a request carries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Ministorm3
Ministorm3 marked this pull request as ready for review August 16, 2026 01:18
@Ministorm3
Ministorm3 marked this pull request as draft August 17, 2026 21:15
@Ministorm3
Ministorm3 marked this pull request as ready for review August 27, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant