How Helen Gets Made A making-of, not the live show Watch her live

YouTube · the two streams that never stop

Two streams that never stop, from a PC with no capture card and no OBS

On 2026-08-30 the mosaic stream went dark twice in under an hour while Task Scheduler said the task was running and ffmpeg was alive. That failure, and the rule it produced, is most of what separates a stream that lasts a weekend from one that stays up. Helen's channel has two, a four-camera mosaic and a head-on Face cam, from the Windows desktop that runs Frigate: six copies of ffmpeg, two PowerShell loops, no OBS, no capture card.

Why the streams are shaped the way they are, and the two rules that cost a week, are here. The full scripts are on the ffmpeg page.

Helen, a dilute tortoiseshell calico, at the water bowl; the Water Cam is one of the four panes in the 24/7 mosaic
The Water Cam is the top-right pane of the mosaic stream. The same 640×360 sub-stream Frigate detects on is what YouTube gets.

Any RTSP camera and a computer that stays on is enough to put a camera on YouTube permanently, with nothing bought. The channel's Live tab has both of ours, running since 2026-08-29 from the machine described on the Frigate-on-Windows page; the pasteable scripts, the exact ffmpeg arguments and the numbers they produce are on the ffmpeg page.

What the two streams are

The mosaic. Four cameras in one 1920×1080 picture, two by two: Face and Water on top, Treat and Bed underneath. Ten frames a second, 3.5 Mbit/s, a music bed we synthesise ourselves. This is the stream a viewer leaves open on a second monitor. One URL, four jobs; if she is not at the bowl she is probably on the bed. At the time of writing it is watch?v=HaRC7bsF-pM.

The Face cam. One camera, head-on at the food bowl, full 2560×1440 at 15 frames a second, 2.5 Mbit/s. This is the camera that is not public on the website (only a curated still is), so the 24/7 stream is the only place to see it move. At the time of writing it is watch?v=JrPoY5QHLdw.

Why a mosaic instead of four streams? YouTube treats each live stream as a separate video with its own chat, its own thumbnail and its own place in the feed. Four of them dilute the audience four ways and quadruple the things that can go wrong at 3 a.m. One mosaic gives a viewer the whole room, and the one single-camera stream exists because Face is the money shot and deserves the full resolution.

The pane choice is a privacy decision, written in the script itself. The overhead Court camera sees the room's entrance, where people pass. Face does not. So the public mosaic shows Face, not Court, and the Court camera stays on the website as a refreshing still that a person can check but nobody can scrub.

The mosaic as it is being encoded right now. Watch the four panes for a moment: Face and Water on top, Treat and Bed below, all at the same 10 frames a second, and no pane ever shows the room’s entrance. If the picture is up, the four tile processes and the mosaic encoder in the table below are all running.

Why there is no OBS

OBS is a screen-and-scene compositor for a person sitting at the machine. We wanted a service: something that starts at boot, runs as SYSTEM with no desktop, and recovers on its own. ffmpeg is that, since it reads RTSP, stacks pictures, talks RTMP and has no window.

The other reason is that the cameras were already in one place. Frigate uses go2rtc to pull each camera once and re-serve it on rtsp://127.0.0.1:8554/<camera>, so Frigate's detector, Frigate's recorder and the live-stream ffmpeg all read the same local copy and the cameras see one client. Nothing on the YouTube side ever touches a camera password. The setup for that half is on the camera-setup page.

Six ffmpeg processes, what each one costs
ProcessWhat it does
4 × tileReads one camera's 640×360 sub-stream from go2rtc, drops the audio, copies the video untouched into MPEG-TS on a local UDP port. No decode, no encode. About 20 MB of memory each; 22 CPU-seconds in 80 minutes.
1 × mosaicReads the four UDP ports, scales each pane to 960×540, stacks them 2×2, loops the music, encodes with h264_qsv on the Intel UHD 630, pushes RTMP.
1 × FaceReads the Face camera's full 2560×1440 main stream, decodes it with d3d11va, re-times to 15 fps, loops its own music, encodes with h264_qsv, pushes RTMP.
Both encoders togetherAbout 2,230 CPU-seconds over 80 minutes on a 12-thread i7-8700, under 4 % of the machine, because the actual H.264 work is on the iGPU. The whole box, with Frigate decoding ten cameras at the same time, sat at 14–15 %.

The two rules that took a week

Rule one: never stream-copy a camera straight to YouTube. The obvious first attempt is -c:v copy from the camera to RTMP, because it costs nothing. It ran, and YouTube showed it, and then the player buffered and skipped while ffmpeg's log filled with non-monotonic DTS warnings. A Tapo camera emits frames on its own clock with no audio YouTube can use; YouTube's ingest wants constant frame rate, a keyframe every two seconds, and an audio track. The fix is to decode and re-encode for the streams YouTube sees: re-time to a fixed 10 or 15 fps with -vsync cfr, set the GOP to two seconds' worth of frames (-g 20 at 10 fps, -g 30 at 15), and mux in a looping WAV. The tiles still stream-copy, because they only go one hop over loopback to the compositor, which is what re-times them. The camera's own microphone is never used; the comment in the script says Never camera mic and means it.

Rule two: watch the socket, not the process. The first watchdog restarted ffmpeg when it exited, and ffmpeg does not exit when YouTube hangs up. In the 2026-08-30 outage schtasks reported the task as Running and the process was alive; the TCP connection to YouTube's port 1935 had gone to CloseWait and ffmpeg was writing into a dead pipe. The watchdog now gives a fresh ffmpeg 45 seconds to complete the RTMP handshake (observed at 10–50 s), and after that requires Get-NetTCPConnection -OwningProcess <pid> -RemotePort 1935 to show at least one Established row every five seconds. Anything else, it kills and restarts. The wrapper log for the afternoon of 2026-09-02 shows what that looks like in practice: twelve RTMP connection unhealthy (state=CloseWait) restarts one minute apart while YouTube's edge was unhappy, then a session that has run clean since 16:48 with speed=1x and the bitrate pinned at 3,650 kbit/s. Nobody noticed.

There is a third, smaller rule. The four tile processes have no socket to watch, and a plain -c:v copy from an RTSP source that has been open for ten hours can quietly start passing broken frames (non-existing PPS 0 referenced in the compositor's log). So a daily task at 04:00 stops and restarts the four tiles and then the compositor, and that failure has not come back.

  1. Get the camera into go2rtc. If you run Frigate you already have go2rtc; add the camera under go2rtc: streams: with both its RTSP paths (Tapo: stream1 main, stream2 sub). From then on everything reads rtsp://127.0.0.1:8554/<camera>. On Windows this works because WSL2 is in mirrored networking mode; see the WSL2 files.
  2. Create the stream in YouTube Studio. Go live → Stream → Create stream. Copy the server URL and the stream key. The default key is persistent: it does not change when ffmpeg reconnects, so a restart resumes the same broadcast. The key is a credential: anyone with it can broadcast as your channel.
  3. Put the URL and the key in a .env file next to the script. Two lines, YT_RTMP_URL= and YT_STREAM_KEY=. The script reads the file at start and builds the destination from it. The scheduled task's XML never contains the key, so exporting or sharing the task is safe.
  4. Install ffmpeg and test one camera by hand. winget install Gyan.FFmpeg gives you an ffmpeg.exe with h264_qsv and d3d11va built in (we run 8.1.1). In a console, run one command: RTSP in over TCP, -vf fps=15,format=nv12, a looping WAV as the second input, h264_qsv at 2,500 kbit/s with -g 30, AAC 128k, -f flv to <YT_RTMP_URL>/<YT_STREAM_KEY>. Within a minute Studio's Stream health should say Excellent or Good. If it says "no data", check the key; if it complains about frame rate or keyframes, you have skipped rule one.
  5. Wrap it in a restart loop with the socket check. A PowerShell while($true) that starts ffmpeg with Start-Process -PassThru, polls every five seconds, kills it over a memory cap, and after 45 seconds kills it if there is no Established connection to port 1935. Log the reason each time. The complete script is on the ffmpeg page.
  6. Register it as a scheduled task at boot. Run as SYSTEM, trigger At startup, hidden window, MultipleInstancesPolicy IgnoreNew so a second copy can never start. Check the task's "Stop the task if it runs longer than" setting: Task Scheduler defaults to three days, and ours still showed 72:00:00 when we looked, masked only by the daily refresh. Set it to unlimited.
  7. Verify with the socket, not the task list. Get-NetTCPConnection -RemotePort 1935 | Select OwningProcess, State. One Established row per stream. Then open the watch page on a phone and count to ten; the stream should be within about fifteen seconds of real time at these settings.

About the music

YouTube wants an audio track and a silent one is treated as a fault, so every stream carries a bed. Ours is synthesised from scratch in Python (numpy and scipy, nothing sampled: one loop-perfect cue of 18 to 20 seconds per station, all in one key, sharing a three-note signature) and rendered to 44.1 kHz stereo WAV, so there is nothing for Content ID to match and nothing to license on a channel meant to run for years. If you use a library track instead, keep the attribution in the stream description and check the licence covers a broadcast that never ends.

What this does not give you

  • It is not a security feed. There is 10–20 seconds of latency and YouTube can pause the broadcast whenever it likes. The recording that matters is Frigate's, on the local disk; see retention.
  • Windows Update will reboot the box. Both streams are boot tasks and come back on their own; the stream key is persistent so the watch URL survives. YouTube may still create a new video id if the broadcast is recreated, which is why the durable link is the Live tab.
  • The iGPU has limits. Two QSV encoders plus detection on the same UHD 630 is comfortable. Four 1080p encoders would not be. If you want more streams, make one mosaic bigger rather than adding encoders.
  • No chat moderation, no overlays, no scene switching. That is what OBS is for.