Vertical (9:16) output for non-Twitch destinations + VOD/EB convenience + dashboard clarity#30
Merged
Merged
Conversation
…venience (v0.1.9)
Reuse Twitch Dual Format's vertical canvas to simulcast 9:16 to YouTube
Shorts / Kick mobile / TikTok with zero extra encoding.
- config: per-destination `stream_format` (horizontal default | vertical),
persisted lean, with a `wants_vertical()` guard (Twitch forced horizontal).
- h264: bounds-checked SPS dimension parser + `detect_vertical_primary_track`
(orientation from the wire, not Twitch's private session JSON); generalize
`select_video_bytes` to `VideoEgress { Passthrough, Track(u8) }` so a vertical
destination forwards only the portrait canvas, flattened, and drops the
horizontal primary.
- controller/main/buffer: per-dest `egress_vertical` + `vertical_primary_track`
(0xFF = unresolved -> waits, others unaffected); recomputed every supervisor
tick so Dual Format on/off self-heals. Seq-header replay is format-aware.
- web UI: Stream format dropdown (hidden for Twitch, with a "Twitch does both"
note), status JSON `stream_format` + `vertical_ready`, a card format icon with
hover tooltip, a distinct "Waiting for Dual Format" status, and tour coverage.
- VOD+EB: one-click "Set up VOD + EB" with a red-to-green checklist, a
`--launch-eb` cold-start flag, a desktop-shortcut generator (.lnk, .cmd
fallback), and a tray "Launch OBS (VOD + EB)" item.
- docs: README EN/ES + CHANGELOG; test count 200 -> 205 (5 new h264 tests).
YouTube/Kick have no Twitch-style single-feed multitrack; vertical maps to
their normal separate-key vertical ingest, so there is no conflict.
…e (IVS low-latency accepts them)
…smooth/stable sparklines, dynamic tour target
…t); clamp sparkline overshoot
…essive disclosure)
… cards (full-bleed signal strip, hover actions)
… toggle switch, colored edge); encoder-neutral connection copy
…card dim on enable/disable
…o opacity pop); stabilize header width so toggling doesn't shift it
…rd actions slide reveal keeps blur, smooth both ways
… glance, encoder-neutral copy, crossfade tip
…anic on malformed input) + huge-crop/fuzz tests
…s (avoids idle-connect reconnect churn when Dual Format is off)
… into the toggle); scope resets, refine layout/colors
…warning, restart banner, cancel button; stabilize rate-pill width to stop header shift
…the destination modal)
…der pill; extract+test stream_format rule; test count -> 209
…e font, no screen overflow, critical rows fixed, scroll fallback
…ng in a small dock
…r (re)seed - clean reconnects, no P-frame glitch on cut/reconnect
YouTube's vertical ingest is viewable but flaky with Enhanced-RTMP avc1 - it aborts the socket on a ~11s cycle (os error 10053) while the horizontal primary, which reaches YouTube as legacy AVC, stays rock-solid. Rewrite a non-Twitch vertical AVC track (OneTrack seq-header / coded frames) down to legacy AVC (0x17/0x27) so it gets the exact framing YouTube already ingests happily. Non-AVC codecs and ManyTracks bundles keep the E-RTMP flatten; avc1 metadata/colour packets are dropped rather than mixed into a legacy stream. Adds select_video_bytes_vertical_avc_rewrites_to_legacy (210 tests).
…ta.is_idr - Remove the orphaned pass_through_multitrack doc block left by the VideoEgress refactor (it was misdocumenting seq_header_track_id). - Add an accurate doc comment to select_video_bytes describing the Passthrough / Track(n) / legacy-AVC behaviour. - Keyframe-lead gate reuses the precomputed meta.is_idr (any-track classification) instead of re-parsing the tag on the hot path.
…eq-headers once per status poll Dashboard clarity for the vertical / Twitch Dual Format flow: - Disable the "Vertical" stream-format choice when no enabled Twitch destination exists (inline reason + link to Twitch's guide). Vertical has no source without Twitch Enhanced Broadcasting, so the choice is no longer a silent dead-end. A dest already set to vertical is never force-flipped - the option stays in case its source returns. - A waiting vertical dest now says why it isn't live: "Needs Twitch EB" (no Twitch source), "No vertical canvas" (EB live but no 9:16 on the wire), or "Waiting for Dual Format" (nothing streaming). Copy makes clear Dual Format is Enhanced Broadcasting + Aitum Vertical (per Twitch's guide), not a single OBS toggle, so the setup path is honest. - The Twitch editor already hides the format control (native dual-canvas passthrough) - unchanged. Perf: the per-destination status endpoint parses each cached video sequence header once per poll instead of once per destination (was a per-dest mutex lock + Exp-Golomb parse on a frequently-polled path). Docs: CHANGELOG entry; fix ES README binary badge 1.2 -> 1.3 MB (matches the measured 1.29 MB release binary and the table).
Folds in Dependabot's #29 so the checkout v7 bump ships with this branch instead of racing it. Covers ci.yml, codeql.yml, and release.yml (all 5 usages).
Soulhackzlol
force-pushed
the
dual-format-verify-and-vod-eb-fix
branch
from
July 1, 2026 20:34
6eae4a2 to
36755e9
Compare
The other e2e scenarios all run with delay=0 (pure passthrough), so the app's core feature - arm a delay, fill the ring, activate, cut on an IDR - was never exercised end-to-end, only unit-tested via compute_delay_cut. Scenario E arms a 2 s delay against a live ffmpeg stream, waits for the ring to fill (phase 'ready'), then activates and asserts: - the delay engages (phase 'active', current_delay_ms > 0), - the sink keeps receiving IDR windows past the cut (stream didn't stall), - exactly one publish accept - a cut that shipped a referenceless P-frame would drop the downstream and reconnect, which this catches. Adds two helpers: Wait-State (poll /state for a predicate) and a non-blocking Start-FfmpegSource so a scenario can arm/activate mid-stream. Verified locally: all 5 scenarios pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Vertical (9:16) output for non-Twitch destinations + VOD/EB convenience + dashboard clarity
Continues the Dual Format work after #28. Adds per-destination vertical
output, VOD-audio/Enhanced-Broadcasting quality-of-life, a dashboard pass
for the vertical flow, and a small status-path perf cleanup.
Vertical output (YouTube Shorts / Kick mobile / TikTok)
Broadcasting + Aitum Vertical) and flattens it to a standard single-track
RTMP feed non-Twitch platforms accept natively. No second scene, no extra
encoder.
orientation (portrait = height > width), not by guessing track IDs. The
SPS parser is fully bounds-checked and fuzzed - never panics on partial or
hostile bytes.
ingest is flaky with Enhanced-RTMP
avc1, aborting on a ~11 s cycle), and avertical destination leads with its own keyframe after every (re)connect
or cut - no mid-GOP glitch.
connect (avoids idle-connect reconnect churn); it waits and self-heals
within a supervisor tick when the canvas appears. Twitch and horizontal
destinations stream untouched.
Dashboard clarity for the vertical / Dual Format flow
source exists, with an inline reason + link to Twitch's guide (never a
silent dead-end). A dest already set to vertical is never force-flipped.
Needs Twitch EB,No vertical canvas, orWaiting for Dual Format. Copy makes clear Dual Format isEnhanced Broadcasting + Aitum Vertical (per Twitch's guide), not a single
OBS toggle.
pill that lights up when a vertical canvas is live.
VOD audio + Enhanced Broadcasting convenience
with
--config-url, re-verifies, red-to-green checklist, partial-successaware).
--launch-ebflag.Performance
once per poll instead of once per destination (was a per-dest mutex lock
CI
actions/checkoutv6 -> v7 across ci/codeql/release workflows,folding in Dependabot ci: bump actions/checkout from 6 to 7 #29 so it ships here (ci: bump actions/checkout from 6 to 7 #29 can then be closed).
Testing
cargo test: 210 pass / 0 failed.cargo clippy --all-targets: clean.cargo build --release: clean; binary 1,353,728 bytes (1.29 MB, matches the1.3 MB stated in the READMEs).
Related
actions/checkoutv7 bump is folded in here)