Skip to content

Repository files navigation

perpetual-video

Viewers type in a YouTube live chat. Each message is moderated, rewritten into a house style, generated as a 5-second clip by MiniMax H3-Max, moderated again as video, and put on air about 40 seconds later. Between new clips the channel replays its library.

The requester is recorded in the log and in the clip's library entry. It is not drawn on the video, so a viewer cannot tell which clip is theirs. That is the largest missing feature and it is the first item in TODO.md.

Status

A one-day experiment. It ran live on YouTube on 2026-08-30, served 19 viewer prompts from 8 people, and cost $31.05. It is not a product, it is not maintained, and the author is not running it any more. The code is here because it works and someone else may want it.

It ran here:

Warning, before you set anything up

Pointing this at a YouTube channel puts that channel at risk. Strangers write the prompts. Four automated filters screen them on the viewer-prompt path, and each one drops the clip rather than airing it when a service is unavailable or a key is missing. They will still miss things.

They score policy categories such as hate, sexual content and violence. They do not score taste, cultural context, trademark, impersonation, or whether YouTube will issue a strike. A real example from the one day this ran: a viewer asked for people in thobes and kaffiyehs, and the resulting clip showed recognisable Gulf Arab dress on humanoid figures dancing in a club. It passed the chat filter, the rewritten-prompt filter and the image filter, because it is not hate speech and not violent. A human noticed it and pulled it. The prompt rewriter now forbids real-world religious, ethnic and national markers, and nothing reviews output for taste.

If you run this on a channel you care about, review clips before they air. The code does not do that for you.

You also pay the API bills, and you are responsible for what the channel broadcasts.

How it works

YouTube live chat
   -> OpenAI moderation on the raw message          (refuses on failure)
   -> gpt-4.1-mini rewrites it into house style     (refuses on failure)
   -> OpenAI moderation on the rewritten prompt     (refuses on failure)
   -> MiniMax H3-Max generates a 5-second clip
   -> OpenAI image moderation on 3 sampled frames   (refuses on failure)
   -> jumps the queue, airs next, credited to the viewer
   -> joins the library and replays afterwards

About 40 seconds from message to broadcast when the queue is empty, 50 to 70 including YouTube's own latency.

Architecture

Three ideas are worth knowing before reading the code.

The publisher is a separate process. It owns the RTMP connection and holds the FIFO open O_RDWR, so the controller can crash, be restarted, or be edited without the broadcast noticing. This matters more than it sounds: on the day this ran, a ten-second RTMP gap with YouTube's Auto-Stop enabled ended the broadcast, changed the video ID, and killed every link that had already been shared.

Generation is windowed. One clip per window regardless of how many people are waiting. A first-in-first-out queue would make cost scale with audience size, so a busy chat would drain the balance in minutes. Below a threshold everyone is served in turn; above it the window draws at random, because a long queue promises a slot that arrives after the viewer has left.

The library rotates. Generation runs at roughly 0.4x realtime per worker and costs $0.05 per second of video, so a continuously unique 24/7 channel is not affordable. Clips are keyed by MiniMax task id, never overwritten, and replayed between new ones.

API keys, and what this actually cost

You need two accounts. Neither has a free tier that covers video generation.

MiniMax does the video. Get a key at https://platform.minimax.io/user-center/basic-information/interface-key. The key type matters and cost an hour to work out: MiniMax issues a Subscription Key (sk-cp-) for its Token Plan and Credits products, and a separate pay-as-you-go API key (sk-api-). The H3 video series is not available on Token Plan or Credits. Sending a sk-cp- key returns TokenPlan or Credit does not currently support MiniMax-H3 series models. Create the pay-as-you-go key and top up the account balance, which is a different wallet from credits. The minimum single top-up is $25.

OpenAI does moderation and prompt rewriting. Get a key at https://platform.openai.com/api-keys. The moderation endpoint (omni-moderation-latest) is free, including image moderation. Rewriting uses gpt-4.1-mini at roughly $0.0002 per prompt, so it rounds to nothing.

What the one day cost

MiniMax, 123 clips $31.05 (most at 480P, a few at 768P and 2K while testing)
OpenAI moderation, several hundred calls $0.00, the endpoint is free
OpenAI rewriting, about 25 prompts under $0.01
Two MiniMax top-ups, tax included $53.20 charged, $49.45 usable
Left over about $18

The rate

MiniMax publishes no 480P price. This one was measured by watching the balance move.

480P $0.05 per second of video
768P $0.08 per second (published)
2K $0.13 per second (published)
One 5-second 480P clip $0.25
One clip per minute about $15/hour

Generating 24 hours of unique content would cost about $4,300 a day, which is why the library rotates instead.

Controls

MAX_BILLABLE_SECONDS is the real ceiling. Seconds already spent are recomputed from the clip ledger at startup, so restarting does not hand the process a fresh budget.

STOP_AT_USD stops generation while a reserve remains, but it only works if you also set BALANCE_START_USD to your own account balance. There is no default, because guessing someone else's balance would produce a guard that never fires. Left blank, the floor is disabled and the process says so on startup. Neither value queries MiniMax; both are computed from the local ledger, so top up again and you must update BALANCE_START_USD.

MAX_CLIPS only catches runaway loops and should sit well above what the money ceiling allows. A tight value silently halts generation with budget still available. PRICE_PER_SECOND affects the printed tally only and does not control what you are charged.

Requirements

Node 22+, ffmpeg with libx264, and a Mac or Linux box that can encode video in real time. The library and ledger start empty. On a fresh clone there is nothing to broadcast, so the stream shows a generated SMPTE test card until the first clip exists. The balance floor does nothing until at least one clip has been generated, because it is computed from the ledger. A MiniMax pay-as-you-go API key (the sk-api- kind; Token Plan and Credits keys are rejected for the H3 series), an OpenAI API key, and a YouTube stream key if you want to broadcast.

Running it locally

The defaults publish nothing and spend nothing.

cp .env.example .env      # fill in MINIMAX_API_KEY and OPENAI_API_KEY
npm run preflight         # tells you whether the key works and whether it has balance
npm start                 # local preview at localhost:8080, library only, no generation
command what it does
npm start local HLS preview, no generation, no spend
npm start -- --generate generates from prompts-interdim.json
npm start -- --generate --youtube sends video to your RTMP destination. If YouTube Auto-Start is on, this puts you publicly live
npm start -- --chat --video=<id> reads live chat and generates from viewer messages. This spends money

Broadcasting to YouTube

Create the stream as a scheduled stream through the Manage tab, not the instant "Stream now" key. A scheduled stream tolerates brief data loss and keeps its video ID; the instant one ends the broadcast on any disconnect, and the URL you shared stops working. Share youtube.com/@yourhandle/live, which always resolves to whatever is currently live.

scripts/broadcast.sh is the unattended entrypoint. It starts the publisher once, keeps the controller alive around it, and holds the machine awake with caffeinate:

LOTTERY_WINDOW_SEC=60 MAX_BILLABLE_SECONDS=100 \
  ./scripts/broadcast.sh --youtube --only=interdim --chat --video=<video id>

--generate is honoured only on the first launch. Restarts drop it and rebroadcast the library, because each new process resets its own attempt counter and a crash loop would otherwise re-spend on every restart.

Note that --youtube on its own already publishes. It does not need --generate; the two switches are independent, and --chat spends money whether or not --generate is set.

Reading chat

The chat reader polls YouTube's internal innertube endpoint, the same route the chat iframe uses. It needs no authentication, which is why the one-day build used it. It is undocumented, can change shape without warning, may fall foul of YouTube's terms, and the consequences land on your account. The supported alternative is the YouTube Data API with OAuth (liveChatMessages), which also lets a bot reply.

Known limitations

  • Nothing reviews generated video for taste or cultural appropriateness.
  • One lottery entry per channel id. Several accounts get several entries. YouTube's own slow mode or subscribers-only chat is the practical mitigation.
  • A FIFO write can block indefinitely if the publisher's ffmpeg dies in a particular way, because the controller cannot see that the reader is gone.
  • No singleton lock on the controller. Two ran at once during development, which risks double spending.
  • Output is 480p at 1.5 Mbps and looks soft. MiniMax returns 5.6 Mbps; the publisher discards most of it, and YouTube gives 480p inputs a low bitrate allocation.
  • On a laptop, closing the lid ends the broadcast.

Related work

@levelsio launched a similar chat-driven AI channel, Infinite Slop, one day earlier. It is closed source. This is an independent build.

Licence

MIT, covering the code in this repository. It does not cover generated clips, model outputs, or third-party services.

Provided as is, with no warranty. Not affiliated with YouTube, MiniMax or OpenAI. You pay for API usage, you are responsible for what your channel broadcasts, and generated video can violate platform policy after passing every filter here. The author is not running this and cannot support your stream.

Releases

Packages

Contributors

Languages