<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cybiqon</title>
    <description>The latest articles on DEV Community by Cybiqon (@cybiqon).</description>
    <link>https://dev.to/cybiqon</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4192140%2F7a679301-9004-49ff-b99a-47cb5c5af3d2.png</url>
      <title>DEV Community: Cybiqon</title>
      <link>https://dev.to/cybiqon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cybiqon"/>
    <language>en</language>
    <item>
      <title>We turned web pages into Snake mazes. The hard part was finding the empty space.</title>
      <dc:creator>Cybiqon</dc:creator>
      <pubDate>Sun, 01 Nov 2026 04:30:02 +0000</pubDate>
      <link>https://dev.to/cybiqon/we-turned-web-pages-into-snake-mazes-the-hard-part-was-finding-the-empty-space-44lk</link>
      <guid>https://dev.to/cybiqon/we-turned-web-pages-into-snake-mazes-the-hard-part-was-finding-the-empty-space-44lk</guid>
      <description>&lt;p&gt;On 28 July we wrote down an idea: click a button and the page you're reading becomes a Snake level. The snake moves through the empty space between paragraphs. The words are walls.&lt;/p&gt;

&lt;p&gt;It sat in a notes file for 49 days. On 15 September we built it, and on 4 October &lt;a href="https://cybiqon.in/products/page-arcade" rel="noopener noreferrer"&gt;Page Arcade&lt;/a&gt; went live on the Chrome Web Store. It now has eight games, and version 1.1.0 shipped on 22 October.&lt;/p&gt;

&lt;p&gt;The games turned out to be the easy part. The hard part was one question the notes file answered wrongly: &lt;strong&gt;where on a web page is the empty space?&lt;/strong&gt; A person can tell at a glance. Code has to decide 12 pixels at a time, on a page it has never seen, and it has to be right. If it marks a word as empty, the snake slides through text. If it marks empty space as a wall, the snake dies on nothing, and the player blames the game.&lt;/p&gt;

&lt;p&gt;This post covers how we answered that question: the screenshot-first freeze, the grid, the rule that lets a snake cross a 1-pixel line but not a word, and the bug report from the owner dying on ChatGPT next to an icon they never touched. It also covers the part that doesn't flatter us: this product broke our own rule, and it still earns nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The page is never edited. It's photographed, and the game is played on the photograph.&lt;/strong&gt; The extension takes a PNG screenshot &lt;em&gt;before&lt;/em&gt; it injects anything, then reads the page's layout, then lays one overlay on top. Press Esc and the page is exactly as it was.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Empty" is decided by two tests that cover each other's blind spots.&lt;/strong&gt; A 12-pixel cell is a wall if its pixels vary by more than 24 in any colour channel, &lt;em&gt;or&lt;/em&gt; if the browser says text or media sits there. The pixel test sees what's drawn. The layout test catches very faint text the pixel test would pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thin lines had to become passable, and that took a classifier.&lt;/strong&gt; Before it, a 1-pixel &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; walled off two rows of cells and split the board in half. After it, there were &lt;strong&gt;0 deaths in 1,456 scripted crossings&lt;/strong&gt; of lines and borders at three zoom levels. A run of underscores still blocks the snake, because underscores are content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The best bug report was a death on ChatGPT.&lt;/strong&gt; Invisible button boxes, hidden screen-reader labels and text boxes grazing a cell by one pixel were all being treated as walls. On a ChatGPT-like test page, walls with nothing drawn in them went from &lt;strong&gt;124 of 472 to 0 of 345&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A random empty cell is not reachable.&lt;/strong&gt; The original notes said it was. The free space on a page is a set of islands, so the game keeps the largest island, joins the second one with a portal when it is big enough, and refuses to start on pages with fewer than 80 free cells.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It asks for three permissions and no site access.&lt;/strong&gt; &lt;code&gt;activeTab&lt;/code&gt;, granted by your click, is enough to take the screenshot and inject the game. The extension does nothing on any page until you ask it to.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Page Arcade, in one paragraph
&lt;/h2&gt;

&lt;p&gt;Page Arcade is a Chrome extension. Click its icon, or press Alt+Shift+P, and the page you're on freezes and becomes a game level. There are eight games.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Snake&lt;/strong&gt; lives in the page's empty space; the edges wrap, and thin lines can be crossed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Word Blaster:&lt;/strong&gt; the page's own words fall in waves and you shoot them from a rocket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Storm:&lt;/strong&gt; you type the falling words to destroy them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brickout:&lt;/strong&gt; every word on the screen is a brick.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whack-a-Word:&lt;/strong&gt; click the words that pop out of the page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page Roller:&lt;/strong&gt; roll the page up, smallest words first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gravity Crumble:&lt;/strong&gt; knock words loose and let the page collapse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page Chomp:&lt;/strong&gt; eat the dots in the empty space while four ghosts chase you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no account. Scores, XP, 23 achievements and 18 skins live in your browser. Per-site leaderboards and share links are opt-in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea is old. That's the point.
&lt;/h2&gt;

&lt;p&gt;We weren't the first to play on web pages, and the research file we wrote on day one says so.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://kickassapp.com/" rel="noopener noreferrer"&gt;Kick Ass&lt;/a&gt; (2010) put an Asteroids ship on any page and let you shoot the elements.&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://kathack.com/" rel="noopener noreferrer"&gt;Katamari Hack&lt;/a&gt; (2011) rolled a page's words up into a growing ball.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://elgoog.im/gravity/" rel="noopener noreferrer"&gt;Google Gravity&lt;/a&gt; (2009) dropped the Google homepage into a physics engine.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://playanypage.com/" rel="noopener noreferrer"&gt;PlayAnyPage&lt;/a&gt;, a bookmarklet from April 2026, has Brickout and a Snake that lives in text. It's the closest thing to what we built.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What the hits had in common was &lt;strong&gt;instant play on the page you were already on&lt;/strong&gt;. That set the bar. There's no level select and no loading screen: the game starts on whatever page you're reading. It also set the hard constraint. If the level is whatever page you happen to be on, the code has to understand pages it has never seen, and it gets one try.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step one: freeze the page without touching it
&lt;/h2&gt;

&lt;p&gt;The obvious approach is to inject a canvas over the live page and run the game on top of it. That fails in small ways. Carousels keep rotating. Videos keep playing. The page's own keyboard shortcuts fire when you press an arrow key. And if the game edits the page, removing words as they're shot for example, there's no clean way back.&lt;/p&gt;

&lt;p&gt;So the page is never edited. It's photographed, and the photograph is played on.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Capture first.&lt;/strong&gt; When you click, the background worker calls &lt;code&gt;chrome.tabs.captureVisibleTab&lt;/code&gt; as a &lt;strong&gt;PNG&lt;/strong&gt;, &lt;em&gt;before&lt;/em&gt; anything is injected, so the image is the page and not our overlay. It has to be PNG: the free-space test compares exact pixel values, and JPEG noise looks like content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the layout.&lt;/strong&gt; A content script asks the browser where every line of text and every image, video, canvas, SVG and form field is drawn. This happens before the overlay exists, otherwise every lookup would hit the overlay itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mount one overlay.&lt;/strong&gt; It's a single custom element on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;, not &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, because a transform on &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; would break &lt;code&gt;position: fixed&lt;/code&gt;. It uses a closed shadow root and the maximum z-index, and holds two canvases: the backdrop (the screenshot, redrawn only when a word is destroyed) and the game layer, redrawn every frame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freeze, don't disable.&lt;/strong&gt; Keys are swallowed at the capture phase, but Ctrl, Alt and Meta shortcuts and the F-keys still pass through. Playing videos and audio are paused. Scrolling is &lt;em&gt;pinned&lt;/em&gt; to the current position rather than switched off: &lt;code&gt;overflow: hidden&lt;/code&gt; would remove the scrollbar, reflow the page and make every measurement we had just taken wrong.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Press Esc and the overlay goes, the listeners go, the scroll position comes back and paused media resumes. The page never knew.&lt;/p&gt;

&lt;p&gt;Destroyed words are painted into a &lt;em&gt;copy&lt;/em&gt; of the screenshot. The original bitmap is never drawn on, so Retry can restore the page exactly.&lt;/p&gt;

&lt;p&gt;One measurement trap: the screenshot's scale comes from &lt;code&gt;screenshot.width / innerWidth&lt;/code&gt;, not from &lt;code&gt;devicePixelRatio&lt;/code&gt;. Under browser zoom and Windows display scaling, the two disagree, and the size of the captured image is the only number that is always right.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://cybiqon.in/lab/page-arcade-turn-any-website-into-a-game" rel="noopener noreferrer"&gt;Diagram: What happens between the click and the first move. Nothing touches the page itself. See it in the original post.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step two: what counts as empty
&lt;/h2&gt;

&lt;p&gt;The grid cuts the overlay into &lt;strong&gt;12 CSS-pixel cells&lt;/strong&gt;, centred so the leftover strip is split between both edges. Each cell has to be classified as free or wall, and there are two ways to be wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pixel test.&lt;/strong&gt; For every cell, take its screenshot pixels and measure the spread (max − min) in each colour channel. If any channel varies by more than &lt;strong&gt;24&lt;/strong&gt;, something is drawn there, and the cell is a wall. Uniform means empty, &lt;em&gt;whatever the colour&lt;/em&gt;. Dark themes, tinted panels and gentle gradients all count as space. Text, icons, borders, images and drawings don't. Cell boundaries round outwards, so a line sitting exactly on a cell edge blocks both neighbours. A free cell that is really a border is the worse of the two mistakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The layout test.&lt;/strong&gt; The browser knows exactly where every line of text is drawn: &lt;code&gt;Range.getClientRects()&lt;/code&gt; returns a rectangle per line. It also knows the box of every image, video, canvas, SVG, iframe and form field. A cell those rectangles overlap is a wall. This catches what the pixel test misses: very low-contrast text that varies by less than 24 against its background.&lt;/p&gt;

&lt;p&gt;Neither test is enough alone. The pixel test can't see grey-on-grey text. The layout test can't see a background image with text baked into it. Together they mostly agree with what a person would call empty.&lt;/p&gt;

&lt;p&gt;On a Wikipedia article at 1,366 pixels wide, this produces something you might not expect: &lt;strong&gt;the article itself is solid wall.&lt;/strong&gt; 12-pixel cells don't fit between lines of body text, so the snake lives in the gutters, the margins and the gaps around headings. That turned out to be the right game. The page's layout becomes a maze, and every site's maze is different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step three: the snake must not die on a line
&lt;/h2&gt;

&lt;p&gt;The first version had a bug the owner found in minutes. The snake died on &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; dividers and thin borders.&lt;/p&gt;

&lt;p&gt;Under the rules above that was correct: a 1-pixel line makes a cell non-uniform, so it's a wall. Because boundaries round outwards, a 1-pixel rule actually walled off &lt;em&gt;two&lt;/em&gt; rows of cells. On a page with a divider across the middle, the board was cut in half.&lt;/p&gt;

&lt;p&gt;A person sees a thin line as decoration, not as a wall. Teaching the grid the same thing took a classifier, and most of the work was in what it must &lt;em&gt;not&lt;/em&gt; free.&lt;/p&gt;

&lt;p&gt;A wall cell becomes a passable &lt;strong&gt;line cell&lt;/strong&gt; only if all of these hold:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one background colour covers &lt;strong&gt;at least 60%&lt;/strong&gt; of the cell;&lt;/li&gt;
&lt;li&gt;every "ink" pixel lies in at most one horizontal and one vertical band, each &lt;strong&gt;at most 3 CSS pixels thick&lt;/strong&gt; (plus anti-aliasing), so dashed and dotted lines still count;&lt;/li&gt;
&lt;li&gt;the band touches a cell edge, because a hyphen in the middle of a cell isn't a border;&lt;/li&gt;
&lt;li&gt;the band is a single colour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The layout data adds hints. &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; elements and element borders of 3 pixels or less loosen the rules where they overlap, so dashed borders whose dashes stop short of the cell edge still pass. Hints only loosen the rules; they never free a cell on their own. A final pass frees rounded corners whose neighbours on both sides are lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text is never a line.&lt;/strong&gt; An underscore, a row of dashes or an icon's straight stroke looks exactly like a thin border to a pixel classifier. So any cell where the browser says text, an icon or a button is drawn is ruled out before the classifier runs. That single rule removed the worst class of bug: snakes sliding through &lt;code&gt;________&lt;/code&gt; in a form.&lt;/p&gt;

&lt;p&gt;The evidence is in the tests: 16+ classifier cases, including 1-pixel and 3-pixel lines (free), 4-pixel lines (wall), 2× scale, dashed, dotted, a hyphen, a stray pixel, two colours, anti-aliasing, a shadow, text over a line and a dark theme. A headless test page full of &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt;s, bordered divs and a table was crossed &lt;strong&gt;1,456 times by a scripted snake at 1×, 1.25× and 2× zoom, with 0 deaths.&lt;/strong&gt; On one Wikipedia article, 536 cells that used to be walls became crossable lines.&lt;/p&gt;

&lt;p&gt;Press &lt;strong&gt;G&lt;/strong&gt; during Snake and the game paints its own reading of the page: walls in red, line cells in cyan. It's a debug view we left in, because it's also the fastest way to tell whether a death was fair.&lt;/p&gt;

&lt;h2&gt;
  
  
  The death on ChatGPT
&lt;/h2&gt;

&lt;p&gt;On 17 September the owner died playing Snake on ChatGPT, next to a copy icon they hadn't touched. They sent a screenshot of the G view. Red cells covered empty space.&lt;/p&gt;

&lt;p&gt;There were four causes. All of them were walls made of nothing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A button's box is bigger than its icon.&lt;/strong&gt; A copy button is a 32-pixel box around a 20-pixel icon. Any cell the &lt;em&gt;box&lt;/em&gt; touched by half a pixel became a wall, which put a band of invisible wall around every row of icons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text boxes grazing a cell.&lt;/strong&gt; A text line's rectangle that clipped a 12-pixel cell by one or two pixels walled the whole cell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visually hidden text.&lt;/strong&gt; Screen-reader labels and transparent tooltips are invisible, but they still have layout boxes. Three of them sat in ChatGPT's empty sidebar header, and each one was a wall.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Icons read as lines.&lt;/strong&gt; Once icons stopped being box walls, their straight strokes started passing the line classifier. The share and regenerate icons showed up cyan.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A layout rectangle now counts only if it reaches &lt;strong&gt;at least 3 pixels into a cell on both axes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Text rectangles lose their empty top 15% and bottom 10% before they're tested.&lt;/li&gt;
&lt;li&gt;Icons, buttons and text are walls &lt;strong&gt;only if something is actually drawn in the cell&lt;/strong&gt;, and they're never eligible to be lines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We measured the result as the number of wall cells whose pixels are blank, on a local test page built to look like ChatGPT: a reply, a row of 32-pixel icon buttons, a composer, a hidden label and a transparent tooltip. &lt;strong&gt;Before: 124 of 472 walls had nothing drawn in them. After: 0 of 345,&lt;/strong&gt; with 0 line cells inside any icon or button. On Wikipedia's &lt;em&gt;Snake&lt;/em&gt; article the count went from 210 to 96. The 96 left are flat dark areas inside a photo, correctly walled by the image's box.&lt;/p&gt;

&lt;p&gt;A game is fair when the player can see why they died. Every one of these bugs was a death the player couldn't see a reason for.&lt;/p&gt;

&lt;h2&gt;
  
  
  A random empty cell is not reachable
&lt;/h2&gt;

&lt;p&gt;The original notes from July had one confident line in them: put food on any random free cell, and it's "guaranteed reachable."&lt;/p&gt;

&lt;p&gt;It isn't. The free space on a web page isn't one space. Margins, gutters and paragraph gaps are &lt;strong&gt;islands&lt;/strong&gt;, walled off from each other by text. Food in the left margin is unreachable from a snake in the right margin.&lt;/p&gt;

&lt;p&gt;So the grid is flood-filled into labelled islands, 4-connected and never diagonally. Since Snake's edges wrap, the fill wraps too: a left margin and a right margin that meet at the screen edge become one island. Then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;largest island&lt;/strong&gt; is the arena. Food and power-ups only ever land on it, so everything on the board is reachable.&lt;/li&gt;
&lt;li&gt;If the &lt;strong&gt;second-largest island has at least 30 cells&lt;/strong&gt;, a pair of portals joins it to the arena.&lt;/li&gt;
&lt;li&gt;With &lt;strong&gt;fewer than 80 free cells&lt;/strong&gt;, or no straight run of 5, the game doesn't start. It tells you there isn't room and suggests scrolling to another part of the page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We kept the wrong line in the notes file, with a pointer to the correction. The mistake is worth remembering: it sounded obvious, and it would have produced a game where food sometimes appears where you can't get to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eight games, one grid
&lt;/h2&gt;

&lt;p&gt;Once the grid existed, the other games came quickly, because each one is a different question asked of the same page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Snake and Page Chomp&lt;/strong&gt; use the free cells. Page Chomp's dots and ghosts live in the same islands Snake does, and the four ghosts chase with the classic arcade personalities: one targets you, one aims four cells ahead of you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Word Blaster, Type Storm and Brickout&lt;/strong&gt; use the &lt;em&gt;text&lt;/em&gt; rectangles. Each word on the screen is a target or a brick at the exact position it was drawn. Word Blaster names its waves after the page's own section headings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page Roller and Gravity Crumble&lt;/strong&gt; use the words as physical objects. Gravity Crumble is the only game that uses a physics library (&lt;a href="https://piqnt.com/planck.js/" rel="noopener noreferrer"&gt;Planck.js&lt;/a&gt;, a JavaScript port of Box2D). Everything else runs on our own small kit: a spatial hash, swept circle-against-box collisions and a Verlet rope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is one runtime dependency, no framework and no audio files. The sound effects are synthesised with WebAudio. Screen shake, hit-stop (a 30 ms pause on a kill, 200 ms on death) and a pool of 2,000 particles in typed arrays are shared across the games. In headless Chromium at 2× on Wikipedia and MDN, median and 95th-percentile frame times were both &lt;strong&gt;16.7 ms&lt;/strong&gt;, a steady 60 fps.&lt;/p&gt;

&lt;p&gt;Reduced motion is a setting that follows the operating system's preference, and it turns the shake off. Screen flashes are capped at one per 340 ms, which keeps them under the three-per-second limit in WCAG 2.3.1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permissions: three, and no site access
&lt;/h2&gt;

&lt;p&gt;The extension asks for &lt;code&gt;activeTab&lt;/code&gt;, &lt;code&gt;scripting&lt;/code&gt; and &lt;code&gt;storage&lt;/code&gt;. &lt;strong&gt;There is no host permission and no content script declared in the manifest,&lt;/strong&gt; so it installs without the "read and change all your data on all websites" warning and does nothing on any page until you click.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;activeTab&lt;/code&gt; is the whole trick. Clicking the icon, or pressing the keyboard shortcut, grants temporary access to that one tab. That's enough to screenshot it and inject the game, and it ends when you leave.&lt;/p&gt;

&lt;p&gt;The store build adds one more host permission, &lt;code&gt;api.cybiqon.in&lt;/code&gt;, for leaderboards and share links. Both are off until a player turns them on. Leaderboards store the site's hostname, the game, the score, a nickname and a random player ID. Never the page or its address beyond the hostname. Share links keep a picture of the wrecked page for 30 days. The full list is in the &lt;a href="https://cybiqon.in/products/page-arcade/privacy" rel="noopener noreferrer"&gt;privacy policy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One packaging detail: the content script is built as a single self-contained file. The default build tool would have split it into a chunk loaded on demand, and loading that chunk into the page needs a host permission we deliberately don't ask for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that doesn't flatter us
&lt;/h2&gt;

&lt;p&gt;Our company has a rule, written at the top of the file every session reads: &lt;strong&gt;no new product until there is revenue.&lt;/strong&gt; The failure mode it guards against is starting things.&lt;/p&gt;

&lt;p&gt;Page Arcade broke it. It was the fifth time. The decision is recorded in the product's own notes, with the date and the fact that total earnings that day were $0.03. The scope was supposed to be a local prototype: no store listing, no backend.&lt;/p&gt;

&lt;p&gt;It grew in four steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Day one was about &lt;strong&gt;2,100 lines&lt;/strong&gt;: two games.&lt;/li&gt;
&lt;li&gt;Later that same day, &lt;strong&gt;5,100 lines&lt;/strong&gt; and three games.&lt;/li&gt;
&lt;li&gt;Two days later, eight games, achievements, skins, a share card and a live backend for leaderboards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today it's &lt;strong&gt;16,809 lines of source and 5,323 of tests.&lt;/strong&gt; Every expansion was chosen, not drifted into. Each one still made the product bigger, and none of them made it earn.&lt;/p&gt;

&lt;p&gt;It earns &lt;strong&gt;₹0&lt;/strong&gt;. There are no ads, no purchases and no plan to add either. The honest case for it is that it's the most shareable thing we've built: it turns any page into a screenshot worth sending. It's also a working demonstration of something we sell: reading a web page the way a person does, from its pixels and its structure together. That's the same skill behind scraping a Google Maps result in &lt;a href="https://cybiqon.in/lab/mapwit-chrome-extension-google-maps-leads" rel="noopener noreferrer"&gt;MapWit&lt;/a&gt; or capturing real footage for &lt;a href="https://cybiqon.in/lab/hyperframes-heygen-ai-video-pipeline" rel="noopener noreferrer"&gt;our videos&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It still has no kill criterion, a date or a number at which we stop. That is the next decision about it, and it's overdue.&lt;/p&gt;

&lt;p&gt;One more confession. On 22 September we added a cheat for playtesting: type &lt;code&gt;iddqd&lt;/code&gt; on the menu and nothing costs a life. The note said to remove it before the store submission. On 4 October the owner decided to keep it. God-mode runs never post to the leaderboards, so it can't spoil anyone else's score.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we'd tell someone building the same thing
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Screenshot first, inject second.&lt;/strong&gt; Every measurement depends on the page being still, and the only reliable way to keep it still is to stop reading the live page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use pixels and layout together.&lt;/strong&gt; Either one alone is wrong in ways the other catches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide which mistake is worse, then bias towards it.&lt;/strong&gt; For us, a free cell that is really content is worse than a wall that is really empty. Then fix the walls that are really empty anyway, because players notice those.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leave the debug view in.&lt;/strong&gt; The G overlay turned a vague "I died for no reason" into a screenshot with the bug visible in it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure with scripted play, not by eye.&lt;/strong&gt; A scripted snake can cross the same line 1,456 times at three zoom levels; a person playing tests a handful. We did the same for our puzzle games, where &lt;a href="https://cybiqon.in/lab/eight-bots-played-all-500-levels" rel="noopener noreferrer"&gt;eight bots played every level&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Page Arcade is free on the &lt;a href="https://chromewebstore.google.com/detail/inefhhclfkojgjjncaajopdhbabjkfbf?utm_source=lab&amp;amp;utm_campaign=page-arcade-post" rel="noopener noreferrer"&gt;Chrome Web Store&lt;/a&gt;. Try it on the page you're reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do you turn a website into a game?
&lt;/h3&gt;

&lt;p&gt;Page Arcade, a free Chrome extension, takes a screenshot of the page and asks the browser where every line of text and every image is drawn. It then lays a game over the frozen page. The page's empty space becomes Snake's playing field, and its words become bricks, targets and falling enemies. Pressing Esc removes the game and leaves the page exactly as it was.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Page Arcade decide where the empty space on a page is?
&lt;/h3&gt;

&lt;p&gt;Page Arcade cuts the page into 12-pixel cells. A cell is a wall if its screenshot pixels vary by more than 24 in any colour channel, or if the browser reports text or media in it. Thin lines of 3 pixels or less, such as &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; dividers and borders, are classified separately and can be crossed. Text, icons and buttons never can.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Page Arcade read or change my data on websites?
&lt;/h3&gt;

&lt;p&gt;No. Page Arcade asks for &lt;code&gt;activeTab&lt;/code&gt;, &lt;code&gt;scripting&lt;/code&gt; and &lt;code&gt;storage&lt;/code&gt;, with no host permission. It does nothing on a page until you click its icon or press Alt+Shift+P, and it never edits the page. Leaderboards and share links are opt-in, and they send only the site's hostname, your score and a nickname, or the share picture you chose to upload.&lt;/p&gt;

&lt;h3&gt;
  
  
  What games are in Page Arcade?
&lt;/h3&gt;

&lt;p&gt;Page Arcade has eight games: Snake, Word Blaster, Type Storm, Brickout, Whack-a-Word, Page Roller, Gravity Crumble and Page Chomp. Version 1.1.0, released on 22 October 2026, added new modes, power-ups and bosses to Snake, Word Blaster, Type Storm and Brickout, plus a redesigned menu and HUD.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a cheat code in Page Arcade?
&lt;/h3&gt;

&lt;p&gt;Yes. Typing &lt;code&gt;iddqd&lt;/code&gt; on the Page Arcade menu turns on god mode for the session, so nothing costs a life. It was added for playtesting and kept on purpose. God-mode runs still earn local records and XP, but they never post to the leaderboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.chrome.com/docs/extensions/reference/api/tabs#method-captureVisibleTab" rel="noopener noreferrer"&gt;chrome.tabs.captureVisibleTab&lt;/a&gt; — Chrome for Developers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.chrome.com/docs/extensions/develop/concepts/activeTab" rel="noopener noreferrer"&gt;The activeTab permission&lt;/a&gt; — Chrome for Developers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Range/getClientRects" rel="noopener noreferrer"&gt;Range.getClientRects()&lt;/a&gt; — MDN&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kickassapp.com/" rel="noopener noreferrer"&gt;Kick Ass&lt;/a&gt; — the 2010 Asteroids-on-any-page bookmarklet&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://kathack.com/" rel="noopener noreferrer"&gt;Katamari Hack&lt;/a&gt; — rolling up a page's words, 2011&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://elgoog.im/gravity/" rel="noopener noreferrer"&gt;Google Gravity&lt;/a&gt; — the Google homepage in a physics engine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://playanypage.com/" rel="noopener noreferrer"&gt;PlayAnyPage&lt;/a&gt; — a 2026 bookmarklet with Brickout and Snake on any page&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://zty.pe/" rel="noopener noreferrer"&gt;ZType&lt;/a&gt; — the typing shooter Type Storm follows&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://piqnt.com/planck.js/" rel="noopener noreferrer"&gt;Planck.js&lt;/a&gt; — the 2D physics library behind Gravity Crumble&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.w3.org/WAI/WCAG21/Understanding/three-flashes-or-below-threshold.html" rel="noopener noreferrer"&gt;Understanding WCAG 2.3.1: Three Flashes or Below Threshold&lt;/a&gt; — W3C&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://www.mathforgameprogrammers.com/gdc2016/GDC2016_Eiserloh_Squirrel_JuicingYourCameras.pdf" rel="noopener noreferrer"&gt;Juicing Your Cameras With Math&lt;/a&gt; — Squirrel Eiserloh, GDC 2016, on trauma-based screen shake&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cybiqon.in/lab/page-arcade-turn-any-website-into-a-game?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;cybiqon.in&lt;/a&gt;. Cybiqon is a small Indian software studio: websites, apps and AI automation for growing businesses. &lt;a href="https://cybiqon.in/services?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;See what we build&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>chromeextensions</category>
      <category>engineering</category>
      <category>games</category>
    </item>
    <item>
      <title>Twelve videos in four days. Nobody picked up a camera.</title>
      <dc:creator>Cybiqon</dc:creator>
      <pubDate>Sun, 25 Oct 2026 04:30:02 +0000</pubDate>
      <link>https://dev.to/cybiqon/twelve-videos-in-four-days-nobody-picked-up-a-camera-4feo</link>
      <guid>https://dev.to/cybiqon/twelve-videos-in-four-days-nobody-picked-up-a-camera-4feo</guid>
      <description>&lt;p&gt;&lt;em&gt;Updated on 12 October 2026. The studio is now open source: &lt;a href="https://github.com/cybiqon-ai/faceless-video-studio" rel="noopener noreferrer"&gt;cybiqon-ai/faceless-video-studio&lt;/a&gt; (MIT), with a step-by-step setup guide. It has moved on since this post was written: 38 gates (a new one, B18, fails a Short whose bottom 40% is empty), 22 look families instead of 15, and two more render engines alongside HyperFrames — the vendored animate styles and Manim. The figures below are as of 11 October.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On 8 October our company changed what it does with its evenings. The products were built and almost nobody knew they existed, so from that day the focus became content: a video every day for YouTube and Instagram. There was one condition. I would not film anything. No camera, no microphone session, no editing timeline.&lt;/p&gt;

&lt;p&gt;Four days later there are twelve finished videos. Eight came out of the daily studio: teaching Shorts, two seven-minute long-forms, product promos. Four are explainers of our own blog posts, now on YouTube. All twelve are narrated in my voice, which I recorded once. Every frame is HTML and GSAP animation rendered to MP4 by &lt;a href="https://github.com/heygen-com/hyperframes" rel="noopener noreferrer"&gt;HyperFrames&lt;/a&gt;. The narration is a &lt;a href="https://www.heygen.com/" rel="noopener noreferrer"&gt;HeyGen&lt;/a&gt; clone. A Claude Code agent does the rest — it picks the topic, researches it, writes and lints the script, voices it, builds the frames with parallel sub-agents, checks its own work, renders, and sends the MP4 and a post kit to my Telegram before I wake up.&lt;/p&gt;

&lt;p&gt;That is the flattering version. The honest one is that my first review said "too fast, too much content", the fourth said the script "considers the viewer as an expert, which is wrong", and in between I told the agent its videos looked so alike that "it will soon be noticed as AI generated". This post is the whole machine: what it is made of, every complaint and the gate it turned into, the voice clone, what it actually costs to the cent, and how to set it up on your own computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;12 finished videos in 4 days, with no filming.&lt;/strong&gt; 8 from the daily studio and 4 blog explainers on YouTube, between 8 and 11 October 2026, across 34 commits. HyperFrames renders the frames locally; HeyGen supplies the voice clone and music. No avatars and no cloud rendering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The voice cost $8.20 in total.&lt;/strong&gt; We loaded $15 into HeyGen's pay-as-you-go API wallet; on 11 October the API reported $6.80 left. Measured on the clone, a minute of narration costs about $0.14. The agent's own runs cost far more: about $18.35 for one 85-second Short and about $26 for a seven-minute long-form plus its companion Short.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;37 pass/fail gates, and most of them exist because something went wrong.&lt;/strong&gt; The pace gate came from "too fast", the 12-year-old gates from "too expert", the variety gate from "looked alike", the hook gate from "the hook doesn't make sense", and the newest gate from words that were being cut in half.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The voice was the hardest part.&lt;/strong&gt; At HeyGen speed 0.9 the clone spoke about 3 words a second against our 2.0 target, and its timestamps run about 0.2 s late, which clipped the start off a third of all sentences. The fix is one take per sentence at 0.75, trimmed on the actual sound.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A render takes 2.5 to 19.5 minutes on a 4-CPU machine.&lt;/strong&gt; The longest, 12,722 frames for a 7-minute video, took 1,168 seconds. Every fix after rendering costs a full render again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We did not track cost per video.&lt;/strong&gt; Every ledger entry has &lt;code&gt;cost_usd: null&lt;/code&gt;. That is the first thing we would do differently.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What HyperFrames and HeyGen each do
&lt;/h2&gt;

&lt;p&gt;These two tools get confused, partly because HyperFrames is published by HeyGen. They do completely different jobs here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HyperFrames is the renderer.&lt;/strong&gt; Its own description is "an open-source framework for turning HTML, CSS, media, and seekable animations into deterministic MP4 videos", under the Apache 2.0 licence. A video is an HTML file. Each element declares when it appears with &lt;code&gt;data-*&lt;/code&gt; attributes, animation runs on a GSAP timeline that the renderer can seek to any frame, and a headless Chrome captures every frame before FFmpeg encodes them. Because the page is plain HTML, an agent that can write a web page can write a video, and because rendering is deterministic, the same file gives the same MP4 every time. We pinned versions 0.8.141 to 0.8.145 over the four days, one per project, and render everything on our own machine.&lt;/p&gt;

&lt;p&gt;HyperFrames also ships agent skills — the README counts 21 — that teach a coding agent how to build compositions: a router skill, the composition contract, animation rules, a CLI skill and workflow skills such as &lt;code&gt;faceless-explainer&lt;/code&gt; and &lt;code&gt;product-launch-video&lt;/code&gt;. We installed them on 20 September and use them under our own skill, described below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HeyGen is the voice.&lt;/strong&gt; HeyGen is best known for AI avatars, and we use none of that. We use three things, all through its API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An instant voice clone of me&lt;/strong&gt;, made once and verified on 8 October with word timings. Every video uses the same voice; that is part of the brand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text-to-speech with that clone&lt;/strong&gt;, which returns a WAV file and per-word timestamps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background music retrieval&lt;/strong&gt;, which costs nothing per call in our tests, and once returned nothing at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else is ours: the research, the script, the design system, the frames, the checks and the delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline: from a topic to an MP4 without a camera
&lt;/h2&gt;

&lt;p&gt;The studio is a Claude Code skill called &lt;code&gt;video-studio&lt;/code&gt;, run from cron with &lt;code&gt;claude -p "/video-studio daily"&lt;/code&gt;. Every stage writes its output to disk inside the video's folder, so a run that dies at 3 a.m. is resumed by the next night's run instead of being thrown away.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://cybiqon.in/lab/hyperframes-heygen-ai-video-pipeline" rel="noopener noreferrer"&gt;Diagram: The eight stages of one studio run. The two shaded stages are the gates; no money is spent on voice until gate A passes. See it in the original post.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pick.&lt;/strong&gt; &lt;code&gt;ledger.py today&lt;/code&gt; returns the delivery date, the day's pillar, the format and what was made recently. A topic I have scheduled on our content planner takes priority. Otherwise the agent searches the web for what is trending or confusing that week. &lt;code&gt;ledger.py check&lt;/code&gt; refuses a reused slug, or any topic whose words overlap 50% or more with anything from the last 30 days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research dossier.&lt;/strong&gt; Every fact gets a numbered source URL in &lt;code&gt;research/sources.md&lt;/code&gt;. For a teaching video the solution is written and run first, and the output shown on screen is pasted from the real run. The skill's rule: "Nothing goes on screen that is not in this folder."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan.&lt;/strong&gt; &lt;code&gt;PLAN.md&lt;/code&gt; holds three hooks of different types, each scored out of 12; the story; a beat-by-beat storyboard; a script with a word budget; and a look. The agent picks the look after researching two or three reference creators, and the ledger refuses one used in any of the last three videos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gate A.&lt;/strong&gt; The script must pass a linter and three separate reviewer agents (described below) before a single second of audio is generated. Voice is the only step that costs money per attempt, so it waits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice.&lt;/strong&gt; &lt;code&gt;voice_build.py&lt;/code&gt; voices the script one sentence at a time with my clone, trims each take on its real sound, and writes a manifest of every file's hash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frames.&lt;/strong&gt; One general-purpose sub-agent per beat builds that beat's HTML in parallel; a long-form goes in waves of eight. A HyperFrames helper assembles them into one &lt;code&gt;index.html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gate B.&lt;/strong&gt; &lt;code&gt;npx hyperframes check --strict&lt;/code&gt;, then snapshots at the middle of every beat. The agent opens the snapshots as images and checks them against the rubric. After the render, a separate judge agent looks at frames decoded from the final MP4. At most two fix loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render and deliver.&lt;/strong&gt; &lt;code&gt;npx hyperframes render --quality high&lt;/code&gt;, then FFmpeg's &lt;code&gt;loudnorm&lt;/code&gt; brings the audio to −14 LUFS. &lt;code&gt;deliver.py&lt;/code&gt; checks the post kit and sends the video to Telegram with three titles, a YouTube description, an Instagram caption, hashtags, a pinned comment and the sources. It prints one line — &lt;code&gt;Rendered: &amp;lt;slug&amp;gt; &amp;lt;seconds&amp;gt; &amp;lt;MB&amp;gt; msg &amp;lt;id&amp;gt;&lt;/code&gt; — and that line is the only proof of success the cron trusts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The studio never posts. YouTube's API keeps uploads private until an app passes Google's audit, and Meta offers us no posting route, so I publish by hand from the Telegram message. That is why the most honest measure of the studio is posted divided by made, not made.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three tracks a day
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cron (IST)&lt;/th&gt;
&lt;th&gt;Track&lt;/th&gt;
&lt;th&gt;Ceiling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;23:00 daily&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;itspyguru: teaching Short, or a 16:9 long-form on Wednesday and Sunday&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;3 h / $25&lt;/strong&gt;; long-form nights 7 h / $60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;14:00 daily&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;blog: an explainer of that day's post on our AI blog&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 h / $20&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;16:30 Mon, Thu, Sat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;promo: a Cybiqon product or service&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.5 h / $25&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The weekly rotation is fixed in code: an AI term on Monday, Python on Tuesday, a long-form on a trending AI tool on Wednesday, a small build on Thursday, the week's tech story on Friday, an AI term or Python on Saturday, and a system-design long-form on Sunday. Both long-form days also get a vertical companion Short cut from the same research.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Claude Code skill behind it
&lt;/h2&gt;

&lt;p&gt;The skill is open source at &lt;a href="https://github.com/cybiqon-ai/faceless-video-studio" rel="noopener noreferrer"&gt;cybiqon-ai/faceless-video-studio&lt;/a&gt;; when this post was written it lived only in our company repository. Its &lt;code&gt;SKILL.md&lt;/code&gt; was 404 lines and has seven modes: &lt;code&gt;daily&lt;/code&gt;, &lt;code&gt;promo&lt;/code&gt;, &lt;code&gt;blog&lt;/code&gt;, &lt;code&gt;plan &amp;lt;topic&amp;gt;&lt;/code&gt;, &lt;code&gt;build &amp;lt;slug&amp;gt;&lt;/code&gt;, &lt;code&gt;revise &amp;lt;slug&amp;gt; "&amp;lt;feedback&amp;gt;"&lt;/code&gt; and &lt;code&gt;setup&lt;/code&gt;. &lt;code&gt;revise&lt;/code&gt; takes one sentence of my feedback and rebuilds only what that sentence touches.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;What is in it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;references/&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The quality rubric; 15 look families; the pillars; a prompt library; &lt;code&gt;jargon.txt&lt;/code&gt; (the terms a beginner wouldn't know)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;references/craft/&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nine researched craft notes: hooks, retention and story, script voice, teaching formats, visual design, product promos, platform specs, recording and lighting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;templates/&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PLAN.md&lt;/code&gt; and the &lt;code&gt;BRIEF.md&lt;/code&gt; handed to the HyperFrames workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;assets/&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The brand core, the logo, vendored fonts (Plus Jakarta Sans, JetBrains Mono) and four caption skins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;scripts/&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Six Python tools and their unit tests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The six scripts are the deterministic half of the system: the parts where we did not want a model's judgement.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Script&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ledger.py&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Today's pillar and format, the no-repeat check, the look-variety check, marking a video posted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;planner.py&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reads the topic I scheduled; "never raises: a planner outage must not stop a video"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;script_lint.py&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gate A18: sentence length, reading grade, number density, jargon, idioms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;voice_build.py&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One HeyGen take per sentence, trimmed on sound, with a hash manifest; &lt;code&gt;--check&lt;/code&gt; is gate B17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;gap_check.py&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gate B16: no silence over 0.7 s between lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;deliver.py&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Refuses delivery without the four PASS lines, the voice check and a &lt;code&gt;look&lt;/code&gt; block, then sends to Telegram and writes the ledger&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The craft notes are where the research went. They cite YouTube's own help pages on Shorts, Nielsen Norman Group on dark mode, a large study of edX lecture videos on how long people keep watching, 3Blue1Brown's FAQ, HeyGen's and ElevenLabs' voice-cloning guides, and &lt;a href="https://github.com/yihui-dev/awesome-opus5-5-videos" rel="noopener noreferrer"&gt;awesome-opus5-5-videos&lt;/a&gt;, an MIT-licensed list of videos made with Claude together with the prompts their creators shared. We read that list on 10 October, and it gave us the prompt library, three new looks, two gates and the most useful single rule in the visual notes: simulate the thing, don't depict it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running an agent unattended, safely enough
&lt;/h3&gt;

&lt;p&gt;The cron calls a wrapper, &lt;code&gt;studio/run.sh&lt;/code&gt;, which comes down to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flock &lt;span class="nt"&gt;-n&lt;/span&gt; /tmp/cybiqon-video-studio.lock &lt;span class="nb"&gt;timeout&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LIMIT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"/video-studio &lt;/span&gt;&lt;span class="nv"&gt;$ARGS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--permission-mode&lt;/span&gt; acceptEdits &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowedTools&lt;/span&gt; &lt;span class="s2"&gt;"Read,Write,Edit,Glob,Grep,WebSearch,WebFetch,Skill,Task,TodoWrite,Bash"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disallowedTools&lt;/span&gt; &lt;span class="s2"&gt;"Bash(git push:*),Bash(rm -rf:*),Bash(curl:*),Bash(wget:*),Bash(sudo:*),Bash(crontab:*),Bash(ssh:*),Bash(scp:*)"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--strict-mcp-config&lt;/span&gt; &lt;span class="nt"&gt;--mcp-config&lt;/span&gt; &lt;span class="s1"&gt;'{"mcpServers":{}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--max-budget-usd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BUDGET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four decisions are packed into that command.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;flock&lt;/code&gt; and &lt;code&gt;timeout&lt;/code&gt;.&lt;/strong&gt; Two agent sessions never overlap, and every run has a wall-clock ceiling. The long-form ceiling of seven hours still ends before the next scheduled job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--max-budget-usd&lt;/code&gt;.&lt;/strong&gt; Claude Code stops the run once its own estimate of API spend reaches the cap. The documentation is careful to say the estimate "can differ from your bill", and that spend can pass the cap, so leave headroom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bash allowed whole, with explicit denies.&lt;/strong&gt; Our first unattended run used a per-command allowlist and stalled at &lt;code&gt;cd hf &amp;amp;&amp;amp; node …&lt;/code&gt;: a headless run cannot answer a permission prompt. Since &lt;code&gt;python3&lt;/code&gt; was already allowed, listing commands bought no safety, only fragility. So the shell is open, and pushing, downloading, &lt;code&gt;sudo&lt;/code&gt;, &lt;code&gt;ssh&lt;/code&gt; and &lt;code&gt;rm -rf&lt;/code&gt; are refused. There is still no &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No MCP servers.&lt;/strong&gt; The run reads the web and then runs code it wrote, because "code on screen is code that ran". The tool scope is the limit on what a poisoned web page could make it do.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the check. A cron that exits 0 tells you nothing — our company's rules say that exit code 0 "has already meant 'did nothing' for 33 consecutive nights" on another job. So &lt;code&gt;run.sh&lt;/code&gt; counts: if this run's slice of the log has no &lt;code&gt;Rendered:&lt;/code&gt; line, I get a Telegram alert with the reason, whatever the exit code says. It also spots a logged-out CLI and says so.&lt;/p&gt;

&lt;h2&gt;
  
  
  37 quality gates, and the failure behind each one
&lt;/h2&gt;

&lt;p&gt;The rubric has 20 plan checks (A1–A20) and 17 build checks (B1–B17). "Each line is pass or fail; there is no partial credit." Thirty-two are hard, meaning a failure blocks the video (two of those apply only to teaching or long-form videos). Few of them were in the first version. Most were added on the day a video failed in that exact way.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gate&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Why it exists&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Narration at most 2.0 words a second, with a 0.4–0.8 s hold after a key reveal&lt;/td&gt;
&lt;td&gt;The first Short, Two Sum: 97 words in 44 s. My verdict: "too fast, too much content"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A14&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The viewer is fixed: "a smart 12-year-old who has used ChatGPT and never coded"&lt;/td&gt;
&lt;td&gt;The temperature Short's script "considers the viewer as an expert"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A15&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every term defined in the same breath&lt;/td&gt;
&lt;td&gt;That script had idioms and maths words, and 17 spoken numbers in 72 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A16&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Teach-back review: a fresh agent hears only the narration, a separate judge grades it&lt;/td&gt;
&lt;td&gt;The first reviewer was told to play "a developer", and the planner graded its own pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A18&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;script_lint.py&lt;/code&gt; passes: no sentence over 18 words, reading grade ≤ 7, ≤ 1 number per 10 s&lt;/td&gt;
&lt;td&gt;The mechanical half of A14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A19&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Expert accuracy review: simplified is fine, false is not&lt;/td&gt;
&lt;td&gt;An outside review caught three simplifications in the temperature Short that had become untrue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A2, A20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The hook is the viewer's own question, proven on screen by 3 s; a cold-viewer agent must get it&lt;/td&gt;
&lt;td&gt;"The hook doesn't make sense… I am noticing this in all videos"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B11&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Real footage fills ≥ 80% of the frame for ≥ 50% of the runtime&lt;/td&gt;
&lt;td&gt;"The actual visuals were limited to very small space on screen"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B13&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A fresh agent must not call this video and the last three the same template&lt;/td&gt;
&lt;td&gt;"The last two videos were quite similar"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B14&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A judge reviews ≥ 6 frames decoded from the final MP4, not just browser snapshots&lt;/td&gt;
&lt;td&gt;Snapshots are not the file: our rendered frame 0 once differed from every snapshot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B16&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No silence over 0.7 s between voice lines&lt;/td&gt;
&lt;td&gt;Padded frames left 3–5 s gaps in the first long-form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B17&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every word is heard: each voice line was made by &lt;code&gt;voice_build.py&lt;/code&gt; and starts and ends on silence&lt;/td&gt;
&lt;td&gt;A third of all sentences were losing their first ~0.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The three reviewer gates are worth a closer look, because they are where we learned the most about using agents to check agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A16, the teach-back.&lt;/strong&gt; A fresh sub-agent that has never seen the research, the plan or the storyboard gets only the narration and three "why" questions the planner wrote in advance. Its persona: "You are 12, smart and curious. You've used ChatGPT but never written code, and English is your second language." It explains back what it learned, answers the questions and lists every point where it would have asked "what's that?". A second, separate judge then sees the narration as well, so it can tell a real gap from a point the script already explains. The first version of this judge saw only the reviewer's notes. It kept failing points the script covered, every round of fixes added words, and a 355-word script grew to 482 without ever passing, although every teach-back had been correct. Now fixes may not grow a script past its length budget, and the planner may never overrule the judge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A19, the expert.&lt;/strong&gt; A beginner can understand something false. So a second fresh agent, playing "a senior ML engineer / Python core developer / systems engineer", flags only what is false, what plants a misconception, and causal claims that are wrong. Each flag comes with a replacement that is "just as simple and true".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A20, the cold viewer.&lt;/strong&gt; A third agent gets only the first three seconds: the first two narration lines, the on-screen text and a description of what is visible. "You are scrolling Instagram Reels. You know nothing about this video." If it can't say who the video is for and what problem it is about, the hook is rewritten.&lt;/p&gt;

&lt;p&gt;Then there is &lt;code&gt;deliver.py&lt;/code&gt;, which refuses to send a video unless &lt;code&gt;PLAN.md&lt;/code&gt; contains the literal lines &lt;code&gt;A16: PASS (judge)&lt;/code&gt;, &lt;code&gt;A18: PASS&lt;/code&gt;, &lt;code&gt;A19: PASS (expert)&lt;/code&gt; and &lt;code&gt;A20: PASS (cold viewer)&lt;/code&gt;. That is the difference between a rule and a gate. Gate A is text the agent reads; &lt;code&gt;deliver.py&lt;/code&gt; is code that refuses.&lt;/p&gt;

&lt;p&gt;Two things the logs show plainly. On the first long-form, the 12-year-old review "passed only on the third judge round"; the rubric says a second failure means splitting the topic, and the agent treated it as a wording fault instead. It wrote that down in the report: "That's a deviation." And on one Snackly revision, "the final-file review never formally passed": two judge rounds failed, the agent fixed every major point and checked the render frame by frame itself, but no third judge ran. Both were disclosed in the delivery, which is the rubric's rule — "Never deliver silently" — and both are why the gates keep tightening.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloning a voice with HeyGen: what it took
&lt;/h2&gt;

&lt;p&gt;The voice is the part a viewer notices first and the part that went wrong most often.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sample.&lt;/strong&gt; HeyGen's own guide asks for "30–60 seconds of expressive speech with your phone close to your mouth"; its developer docs for instant clones say only the first three minutes of a recording are used, in MP3 or WAV, up to 100 MiB. ElevenLabs notes that more than two or three minutes gives little extra for an instant clone. Our skill's guidance splits the difference: record one continuous take of 90 to 120 seconds, then submit the best 60 to 90. The other rules come from the same guides. Use a quiet room, because the clone copies the room. Read more expressively than feels natural, because cloning flattens tone. Read the kind of script the clone will speak, in your natural accent, including the words where the accent shows. Our practice script, about 230 words, has a Two Sum walkthrough, an embeddings explanation, a line in Hinglish ("price kya hai?") and a run of numbers. The skill's setup mode can create the clone through the API with a helper from the HyperFrames media skills (&lt;code&gt;heygen-voice.mjs clone &amp;lt;file&amp;gt; --name "Prajjwal"&lt;/code&gt;); ours is called "pj voice".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The speed problem.&lt;/strong&gt; The first fix, on 9 October, was to slow the clone to HeyGen speed 0.9. It was not enough: measured on later scripts, whole lines at 0.9 came out at 2.8 to 3.0 words a second, and the first take of the rate-limiting long-form ran 318 seconds at 3.02 words a second. Our target is 2.0, and I cap revisions at 2.2. Slowing the whole line further made it drawl.&lt;/p&gt;

&lt;p&gt;The fix was to stop voicing lines and start voicing sentences. &lt;code&gt;voice_build.py&lt;/code&gt; sends each sentence on its own at speed 0.75, then joins a line's sentences with a 0.35-second breath. That lands under 2.2 words a second while keeping the gap between lines under 0.7 seconds. It has a second benefit: a fix to one sentence re-voices only that sentence, and takes are cached, so a rerun costs nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The clipped-words problem.&lt;/strong&gt; On 11 October I reported that "few words are getting cut… proper words are not hearable". The cause was ours. The earlier script trimmed each take at HeyGen's first-word timestamp, minus 80 milliseconds. Those timestamps run about 0.2 seconds late, so the trim sliced the start off a third of all sentences — "The" became "e", "Step" became "tep". On the Snackly promo, 12 of 44 sentences were clipped. Now each take is cut at its first and last audible 10-millisecond window (anything above 3% of the take's peak), with padding and 8 ms fades, and the build fails if a trimmed take still starts or ends on speech. Gate B17 makes &lt;code&gt;deliver.py&lt;/code&gt; refuse any video whose voice files were not made by this script or were changed after it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rate limit.&lt;/strong&gt; HeyGen's documentation lists 30 requests a minute for &lt;code&gt;POST /v3/voices/speech&lt;/code&gt; on pay-as-you-go accounts, and a &lt;code&gt;429 Too Many Requests&lt;/code&gt; beyond that. We found it the hard way: on the rate-limiting long-form night it "refused 23 lines on the first try", and because the voicing ran as parallel batches, the refusals failed silently into a log. Now &lt;code&gt;voice_build.py&lt;/code&gt; retries with backoff and jitter, up to six times, and never runs more than about four requests in parallel. (Yes, a video about rate limiting hit a rate limit.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs to make an AI video with HeyGen and HyperFrames
&lt;/h2&gt;

&lt;p&gt;Every figure in this section is first-party unless it is marked as a list price.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Figure&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HeyGen wallet loaded&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$15&lt;/td&gt;
&lt;td&gt;Our top-up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wallet on 10 Oct, 13:08 UTC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$10.52&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /v3/users/me&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wallet on 11 Oct&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$6.80&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /v3/users/me&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HeyGen spent, all video work to date&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$8.20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$15 − $6.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clone TTS, test 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;346 characters → 22.8 s of audio → $0.05&lt;/td&gt;
&lt;td&gt;Balance before and after&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clone TTS, test 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;967 characters → 58.9 s of audio → $0.14&lt;/td&gt;
&lt;td&gt;Balance before and after&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Music retrieval&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;Balance did not move&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent run, temperature Short (85 s)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;about $18.35 of a $25 cap; about $10 of it the 11 parallel frame workers&lt;/td&gt;
&lt;td&gt;Run log, 9 Oct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent run, rate-limiting long-form + companion Short&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;about $26 of a $60 cap&lt;/td&gt;
&lt;td&gt;Run log, 10 Oct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Daily video-idea job&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;about $0.25&lt;/td&gt;
&lt;td&gt;Cron docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rendering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no per-render charge; our own 4-CPU machine&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three things follow from the table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The voice is cheap, and nearly all the cost is the agent.&lt;/strong&gt; The two tests put the clone at about $0.14 per minute of finished narration. Arithmetic, not a measurement: a clean single pass of the 415-second long-form is about 6.9 minutes of audio, roughly $0.97 of voice. The agent that wrote, reviewed and built that video ran for hours. The agent figures are Claude Code's own estimate of API spend, the same number &lt;code&gt;--max-budget-usd&lt;/code&gt; checks; the documentation says it can differ from a bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallelism is the expensive part.&lt;/strong&gt; On the temperature Short, the 11 frame sub-agents took about $10 of $18.35. Parallel workers are what make an 85-second Short with 11 distinct scenes possible overnight, but each one starts by loading the same skills and brief.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We cannot tell you what one video cost.&lt;/strong&gt; The ledger has a &lt;code&gt;cost_usd&lt;/code&gt; field, and it is &lt;code&gt;null&lt;/code&gt; on all eight entries. The wallet total covers every voice take for twelve videos, the revisions, the abandoned takes and two pricing tests. If you build something like this, write the cost of each run into its ledger entry from day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wallet or subscription?&lt;/strong&gt; On 10 October, with about $10 left, we asked whether to keep topping up the wallet or take HeyGen's Creator plan. HeyGen's pricing page lists Creator at $29 a month, or $24 a month paid annually. Third-party write-ups say web-plan credits do not pay for API calls, which are billed separately; HeyGen's pricing page links to API pricing but does not spell that out, so treat it as unconfirmed. We are staying on the wallet. Our three tracks need on the order of 175 minutes of narration a month on clean first passes. That is our own estimate, and at $0.14 a minute it comes to about $25 before revisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What didn't work
&lt;/h2&gt;

&lt;p&gt;Listed roughly in the order we found them. The visual ones cost render loops, and a loop on the Snackly revision took about 35 minutes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Too fast.&lt;/strong&gt; Two Sum, the first Short: 44 seconds, 97 words, nine beats. I liked the animation ("hats off to the animation") and said it was "too fast, too much content in just 44 seconds, we could have gone beyond a minute". The agent had optimised for research on short attention spans; the audience is people learning Python. &lt;strong&gt;Fix:&lt;/strong&gt; teaching runs 60–90 seconds at no more than 2.0 words a second, with a hold after every reveal (A8). "Cut beats, never pauses."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first unattended run stalled.&lt;/strong&gt; On 9 October the run got as far as building frames and then could not run &lt;code&gt;node&lt;/code&gt; or &lt;code&gt;npx&lt;/code&gt; without a permission prompt nobody was awake to answer. &lt;strong&gt;Fix:&lt;/strong&gt; allow Bash whole and deny the dangerous commands by name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A HyperFrames helper deleted footage.&lt;/strong&gt; The assembler moves each frame's video clip out of the frame file before checking the other frames; when a check fails it stops, and the clips are gone. Two clips vanished on the Page Arcade Short; the agent restored them and flagged it as a bug in the installed workflow, not in our code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The videos looked alike.&lt;/strong&gt; After the Page Arcade and ChitraYatra Shorts I wrote: "the last two videos were quite similar; this way it will soon be noticed as AI generated", and the real footage "was limited to very small space on screen". &lt;strong&gt;Fix:&lt;/strong&gt; only the brand core stays fixed — the voice, one green accent (&lt;code&gt;#00D890&lt;/code&gt;), a two-second end mark. The look changes every video, from a list of families that has grown to 15: blueprint, whiteboard, isometric 3D, documentary, split-screen, editorial paper and others. &lt;code&gt;ledger.py look-check&lt;/code&gt; refuses a family used in the last three videos, or a hook type or caption style repeated from the previous one. Footage goes full screen (B11).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The script assumed an expert.&lt;/strong&gt; The LLM-temperature Short was the first video where I said "the visuals &amp;amp; frames of this video was very good, I am impressed. But what's bad was the script itself." It skipped the foundation, explained the mechanism with maths, used idioms ("long shots") and spoke 17 numbers in 72 seconds. The review gate had flagged it twice and been overruled by the agent that wrote it. &lt;strong&gt;Fix:&lt;/strong&gt; the fixed 12-year-old viewer, the linter, the teach-back with a separate judge, the expert review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks that meant nothing to a stranger.&lt;/strong&gt; On the Snackly promo: "the whole video is good except the beginning… the hook doesnt make sense". Clever lines like "Ten comparisons, or five lookups" scored well on craft with a planner who already knew the topic. &lt;strong&gt;Fix:&lt;/strong&gt; line one is the viewer's own question in their words ("Why does your shop's website load so slowly?"), and a before-and-after proves it by three seconds (A2, A20).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft footage we didn't notice.&lt;/strong&gt; Chrome's screencast on our machine delivers 360×640 frames whatever size you ask for, so every screen recording before 10 October was a 3× upscale. &lt;strong&gt;Fix:&lt;/strong&gt; capture with &lt;code&gt;page.screenshot&lt;/code&gt; at device pixel ratio 3 instead, which gives true 1080-pixel frames at about 7.5 frames a second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frame 0 ignored the trim.&lt;/strong&gt; The rendered MP4's first frame showed the clip's own first frame — prices and an "Add to cart" button — although every browser snapshot looked right. &lt;strong&gt;Fix:&lt;/strong&gt; pre-trim clips with FFmpeg, and always decode frame 0 of the final file (B14 now does).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silence between sentences.&lt;/strong&gt; The first long-form had 3–5 second gaps from padded frames. Even a 2-second gap felt one second too long. &lt;strong&gt;Fix:&lt;/strong&gt; B16 and &lt;code&gt;gap_check.py&lt;/code&gt;, at most 0.7 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clipped words, voice speed and the rate limit.&lt;/strong&gt; All described above. The speed and the rate limit each cost 20 to 40 minutes on the night they were found.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smaller traps.&lt;/strong&gt; The machine cannot fetch Google Fonts, so the first-choice look for one video was abandoned; looks now use fonts already on disk. Background jobs die after ten minutes unless given a longer timeout. An earlier voice script reused stale audio. And when the server's timezone was switched from UTC to IST on 8 October, cron fired every job 5.5 hours early, the studio's slot had already passed, and no video was made that night; all 14 crontab entries were converted the next day. The HeyGen music search sometimes returns nothing, and two videos reused an earlier track.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What worked
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Real footage beat everything we drew.&lt;/strong&gt; The video I liked most was a blog explainer about Google's Gemini model taking on real jobs — "this is amazing. i loved this one." Its footage is the real source page: Google's announcement, captured with Puppeteer at 2×, with a green highlighter drawn on the exact sentences being discussed. The highlight coordinates were read from the page's DOM, not guessed. The claims in it follow Google's page rather than our own blog post. Where our post said a saving had "already" happened, Google's page said "once rolled out", and the video went with Google. That method is now the template for every blog video.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The simple style held up.&lt;/strong&gt; Flat navy, one green accent and motion that shows a state changing — a pointer moving, a cell filling — got praise on the first day and still works. The isometric look on the temperature Short got the "very good, I am impressed".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Putting gates in code, not in prose.&lt;/strong&gt; The rules that lived only in the skill's instructions were the ones bent under pressure: the third judge round and the final-file review above. No video has gone out without the lines and checks &lt;code&gt;deliver.py&lt;/code&gt; enforces. The PASS lines, the voice manifest and the look-check are three short pieces of code that changed the output more than any amount of prompt wording.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-sentence voicing.&lt;/strong&gt; It fixed the pace, made revisions cheap and, with the trim on real sound, fixed the clipped words. It is the single biggest improvement in how the videos sound.&lt;/p&gt;

&lt;p&gt;You can watch what the method produces: &lt;a href="https://youtu.be/Tuv_wF5Yhw4" rel="noopener noreferrer"&gt;Google gave its newest AI real jobs&lt;/a&gt;, &lt;a href="https://youtu.be/el5VAfAQASM" rel="noopener noreferrer"&gt;the Jev explainer&lt;/a&gt; and &lt;a href="https://youtu.be/sXXElHX-7SU" rel="noopener noreferrer"&gt;how OpenAI trained agents to run enterprise software&lt;/a&gt;, all on our &lt;a href="https://www.youtube.com/@cybiqonai" rel="noopener noreferrer"&gt;@cybiqonai&lt;/a&gt; channel. The audience for these is our &lt;a href="https://cybiqon.in/products/llmbytes" rel="noopener noreferrer"&gt;LLMBytes&lt;/a&gt; daily AI blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  How long a HyperFrames render takes
&lt;/h2&gt;

&lt;p&gt;All renders ran locally on one machine: Ubuntu 24.04, 4 CPUs and 30 GiB of RAM, with HyperFrames using one worker. These are the timings from the render logs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Video&lt;/th&gt;
&lt;th&gt;Frames&lt;/th&gt;
&lt;th&gt;Render time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI agents long-form (7:04)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12,722&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,168 s (19.5 min)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate-limiting long-form&lt;/td&gt;
&lt;td&gt;12,957&lt;/td&gt;
&lt;td&gt;791 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM temperature Short&lt;/td&gt;
&lt;td&gt;3,600&lt;/td&gt;
&lt;td&gt;526 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini blog explainer&lt;/td&gt;
&lt;td&gt;3,518&lt;/td&gt;
&lt;td&gt;427 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jev blog explainer&lt;/td&gt;
&lt;td&gt;3,547&lt;/td&gt;
&lt;td&gt;307 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snackly promo&lt;/td&gt;
&lt;td&gt;2,224&lt;/td&gt;
&lt;td&gt;188 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate-limiting companion Short&lt;/td&gt;
&lt;td&gt;2,669&lt;/td&gt;
&lt;td&gt;144–185 s across four renders&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Frame count is not the whole story: the rate-limiting long-form has more frames than the agents one and rendered in two-thirds of the time, because its scenes are simpler. The practical lesson is the one in the failure list. A render is cheap in money and expensive in time, so everything that can be checked before rendering — snapshots, the linter, the reviewers, the voice manifest — is checked before rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set up HyperFrames and HeyGen on your own machine
&lt;/h2&gt;

&lt;p&gt;The whole skill is now open source at &lt;a href="https://github.com/cybiqon-ai/faceless-video-studio" rel="noopener noreferrer"&gt;cybiqon-ai/faceless-video-studio&lt;/a&gt;: clone it, run &lt;code&gt;./install.sh ~/my-studio&lt;/code&gt;, and follow its &lt;a href="https://github.com/cybiqon-ai/faceless-video-studio/blob/main/docs/setup.md" rel="noopener noreferrer"&gt;setup guide&lt;/a&gt;. The steps below are what that guide automates, if you would rather build your own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install the basics.&lt;/strong&gt; HyperFrames needs Node.js 22 or newer and FFmpeg. We use Node 22 through nvm, Python 3.12 for the helper scripts, and FFmpeg 6.1 from apt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ffmpeg
node &lt;span class="nt"&gt;--version&lt;/span&gt;   &lt;span class="c"&gt;# 22 or newer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Install HyperFrames and its agent skills.&lt;/strong&gt; In Claude Code, add the plugin; for other agents, install the skills directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude plugin marketplace add heygen-com/hyperframes
claude plugin &lt;span class="nb"&gt;install &lt;/span&gt;hyperframes@hyperframes
&lt;span class="c"&gt;# or, for any agent that reads skills:&lt;/span&gt;
npx skills add heygen-com/hyperframes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Make one video by hand first.&lt;/strong&gt; Before automating anything, render one composition yourself, so you know what a good one looks like and how long your machine takes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hyperframes init my-video
&lt;span class="nb"&gt;cd &lt;/span&gt;my-video
npx hyperframes preview
npx hyperframes check &lt;span class="nt"&gt;--strict&lt;/span&gt;
npx hyperframes render &lt;span class="nt"&gt;--quality&lt;/span&gt; high &lt;span class="nt"&gt;--output&lt;/span&gt; renders/video.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Get a HeyGen API key and fund the wallet.&lt;/strong&gt; API usage is billed per operation from a wallet. Keep the key out of your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'HEYGEN_API_KEY=...'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .env &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;600 .env
&lt;span class="c"&gt;# check the key and your balance&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: &lt;/span&gt;&lt;span class="nv"&gt;$HEYGEN_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://api.heygen.com/v3/users/me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes &lt;code&gt;wallet.remaining_balance&lt;/code&gt;. The older &lt;code&gt;v2/user/remaining_quota&lt;/code&gt; endpoint still answers, but HeyGen marks it legacy and says it will be removed on 31 October 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Clone your voice once.&lt;/strong&gt; Record one expressive take of 90–120 seconds in a quiet room, as WAV, and submit the best 60–90 seconds. HeyGen's current instant-clone flow uploads the recording as an asset, creates the voice and polls it until it is active. Then voice a test paragraph and time it. If it is faster than about two words a second, don't just lower the speed — voice one sentence at a time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Trim on sound, not on timestamps.&lt;/strong&gt; Whatever TTS you use, find each take's first and last audible sound yourself and cut there, with a few milliseconds of padding and a short fade. Then listen to the first word of every sentence once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Normalise loudness for social platforms.&lt;/strong&gt; We use the same pass on every video:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; renders/video.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt;:v copy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="nv"&gt;loudnorm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;I&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-14&lt;/span&gt;:TP&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-1&lt;/span&gt;.5:LRA&lt;span class="o"&gt;=&lt;/span&gt;11 &lt;span class="nt"&gt;-ar&lt;/span&gt; 48000 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 192k &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart video.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8. Write your gates as code.&lt;/strong&gt; Start with three: a script linter that fails long sentences and undefined terms, a delivery script that refuses a video without the review lines you require, and a voice check that refuses audio it didn't build. Add one every time you catch a failure by eye.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Only then, schedule it.&lt;/strong&gt; Run your agent headless from cron with a lock, a timeout, a budget cap, a scoped tool list and no MCP servers, and judge success by counting a line your delivery script prints, never by the exit code. Expect the first unattended run to fail on something you never saw in an interactive session.&lt;/p&gt;

&lt;p&gt;If you would rather have this built for your business than build it, that is the kind of &lt;a href="https://cybiqon.in/services/ai-agents" rel="noopener noreferrer"&gt;AI agent and automation work&lt;/a&gt; we do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;The studio works. Twelve finished videos in four days, narrated in my voice, with the research, the script, the design and the delivery done by an agent while I slept, for $8.20 of voice. I would not have believed that number on 7 October.&lt;/p&gt;

&lt;p&gt;Three things I would carry to anyone building the same thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your gates will come from your failures, not your foresight.&lt;/strong&gt; Almost every one of the 37 checks was written the day a video failed that way. Plan for it. Make the first week about reviewing every video yourself and turning each complaint into a line of code that refuses the next bad video, rather than into another paragraph of instructions an agent can talk itself past.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure cost per video from the first run.&lt;/strong&gt; The wallet tells us what all the voicing cost; the run logs tell us what two runs cost. Nothing tells us what a Short costs, which is the one number a business deciding whether to do this would ask for first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The voice is the product.&lt;/strong&gt; Animation is where the agent impressed us fastest, and the voice is where viewers will decide whether to stay. Pace, clipped words and silence between sentences took more fixes than every visual problem combined, and they were all invisible in a snapshot.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All figures are first-party unless marked. Video counts come from the studio ledger and the YouTube upload ledger; run costs from the agent's own reports in the run logs, which are Claude Code's estimates rather than a bill; the HeyGen wallet from its API on 10 and 11 October 2026; the TTS cost per minute from two before-and-after balance checks on 10 October; render times from HyperFrames' render logs; quality gates from the skill's rubric. Owner feedback is quoted from the notes made the day it was given. HeyGen's plan prices, rate limits and clone requirements were read from heygen.com and developers.heygen.com on 11 October 2026; the claim that plan credits don't cover the API is from third-party write-ups and is marked as unconfirmed.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is HyperFrames?
&lt;/h3&gt;

&lt;p&gt;HyperFrames is an open-source framework from HeyGen, under the Apache 2.0 licence, that turns HTML, CSS, media and seekable animations into deterministic MP4 videos. A video is a web page whose elements declare their timing with data attributes. It renders locally with headless Chrome and FFmpeg, and needs Node.js 22 or newer. It ships agent skills so that coding agents such as Claude Code can write compositions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is HyperFrames free?
&lt;/h3&gt;

&lt;p&gt;Yes. The framework and its CLI are open source under Apache 2.0, and rendering on your own machine has no per-render charge; you pay only in machine time. Our renders on a 4-CPU machine took between about 2.5 and 19.5 minutes per video. HeyGen also offers hosted rendering, which we have not used. HeyGen's voice, avatar and music APIs are paid separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does a HeyGen voice clone cost per minute of audio?
&lt;/h3&gt;

&lt;p&gt;In our measurements on 10 October 2026, text-to-speech with an instant clone of our founder's voice cost about $0.14 per minute of finished audio, through HeyGen's pay-as-you-go API wallet: $0.05 for 22.8 seconds and $0.14 for 58.9 seconds. HeyGen's developer documentation lists professional-voice speech at 0.6 API credits per generated minute and does not publish a per-minute price for instant clones. HeyGen's documentation says creating an instant clone is free during its preview.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Claude Code make videos on its own?
&lt;/h3&gt;

&lt;p&gt;Yes, with a renderer and a voice. Our setup runs Claude Code headless from cron with a custom skill that researches a topic, writes and lints a script, voices it with a HeyGen clone, builds frames with parallel sub-agents, renders with HyperFrames and sends the MP4 to Telegram. It made twelve finished videos in four days. It still needed a human to review every video and to post them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the video studio open source?
&lt;/h3&gt;

&lt;p&gt;Yes. The &lt;code&gt;video-studio&lt;/code&gt; Claude Code skill is published at &lt;a href="https://github.com/cybiqon-ai/faceless-video-studio" rel="noopener noreferrer"&gt;github.com/cybiqon-ai/faceless-video-studio&lt;/a&gt; under the MIT licence, with an installer, a starter studio, a filled-in example brand, and docs for setup, voice cloning, scheduling and every quality gate. You bring a HeyGen API key and your own voice; the agent runs in Claude Code and renders locally with HyperFrames. Our own studio runs the same public code, with only its config, brand and plugins kept private.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does a HyperFrames render take?
&lt;/h3&gt;

&lt;p&gt;It depends on frame count and scene complexity. On a 4-CPU, 30 GiB Ubuntu machine with one render worker, a 74-second promo of 2,224 frames took 188 seconds and a 7-minute long-form of 12,722 frames took 1,168 seconds, about 19.5 minutes. Our other seven-minute video rendered in 791 seconds because its scenes were simpler.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the HeyGen API rate limit for text-to-speech?
&lt;/h3&gt;

&lt;p&gt;HeyGen's usage-limits documentation lists 30 requests per minute for POST /v3/voices/speech on pay-as-you-go and subscription accounts, and 120 on Enterprise; going over returns 429 Too Many Requests. Parallel voicing hits that limit quickly. One of our runs had 23 lines refused, so voice sentences sequentially or with a small pool, and retry with backoff and jitter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do you need a HeyGen avatar to make AI videos?
&lt;/h3&gt;

&lt;p&gt;No. We use no avatars at all. HeyGen supplies only the cloned voice and background music; every visual is an animated HTML scene, a real web page captured in a browser or real product footage, rendered by HyperFrames. Faceless explainers like these suit teaching content, and avoid the cost of avatar video.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long should a YouTube Short be?
&lt;/h3&gt;

&lt;p&gt;YouTube allows Shorts up to three minutes long. Our own teaching Shorts run 60 to 90 seconds at no more than two words a second, after our first 44-second Short was judged too fast to follow. Long-form explainers run four to eight minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/cybiqon-ai/faceless-video-studio" rel="noopener noreferrer"&gt;faceless-video-studio&lt;/a&gt; — the studio described here, open source (MIT), published 12 October 2026&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/heygen-com/hyperframes" rel="noopener noreferrer"&gt;HyperFrames on GitHub&lt;/a&gt; — HeyGen, Apache 2.0; README read 11 October 2026&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hyperframes.heygen.com/quickstart" rel="noopener noreferrer"&gt;HyperFrames quickstart&lt;/a&gt; — HyperFrames documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.heygen.com/docs/usage-limits.md" rel="noopener noreferrer"&gt;HeyGen API: Usage limits&lt;/a&gt; — HeyGen developer documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.heygen.com/docs/voices/heygen-voice-instant-clone.md" rel="noopener noreferrer"&gt;HeyGen API: Instant voice clone&lt;/a&gt; — HeyGen developer documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.heygen.com/docs/models/heygen-voice.md" rel="noopener noreferrer"&gt;HeyGen API: HeyGen Voice&lt;/a&gt; — HeyGen developer documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.heygen.com/reference/get-current-user.md" rel="noopener noreferrer"&gt;HeyGen API: Get current user&lt;/a&gt; — HeyGen developer documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.heygen.com/pricing" rel="noopener noreferrer"&gt;HeyGen pricing&lt;/a&gt; — read 11 October 2026&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.heygen.com/blog/how-to-perfect-your-ai-voice-clone" rel="noopener noreferrer"&gt;How to perfect your AI voice clone&lt;/a&gt; — HeyGen blog&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/cli-reference" rel="noopener noreferrer"&gt;Claude Code CLI reference&lt;/a&gt; — Anthropic&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://support.google.com/youtube/answer/15424877" rel="noopener noreferrer"&gt;YouTube Shorts length&lt;/a&gt; — YouTube Help&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/yihui-dev/awesome-opus5-5-videos" rel="noopener noreferrer"&gt;awesome-opus5-5-videos&lt;/a&gt; — videos made with Claude and their prompts, MIT&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ffmpeg.org/ffmpeg-filters.html#loudnorm" rel="noopener noreferrer"&gt;FFmpeg loudnorm filter&lt;/a&gt; — FFmpeg documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tech.ebu.ch/publications/r128" rel="noopener noreferrer"&gt;EBU R 128: Loudness normalisation&lt;/a&gt; — European Broadcasting Union&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cybiqon.in/lab/we-built-a-wiki-our-ai-agents-ignored-it" rel="noopener noreferrer"&gt;We built our AI agents a wiki. They went straight to grep.&lt;/a&gt; — how the agents find their way around this codebase&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cybiqon.in/lab/shipped-the-game-missed-the-deadline" rel="noopener noreferrer"&gt;We built the game in six days. The store review took fifteen.&lt;/a&gt; — the game whose footage appears in the studio's product Shorts&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cybiqon.in/lab/hyperframes-heygen-ai-video-pipeline?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;cybiqon.in&lt;/a&gt;. Cybiqon is a small Indian software studio: websites, apps and AI automation for growing businesses. &lt;a href="https://cybiqon.in/services?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;See what we build&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aivideo</category>
      <category>hyperframes</category>
      <category>heygen</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>We deleted the voice bill. Now the studio posts its own videos.</title>
      <dc:creator>Cybiqon</dc:creator>
      <pubDate>Sun, 18 Oct 2026 13:17:52 +0000</pubDate>
      <link>https://dev.to/cybiqon/we-deleted-the-voice-bill-now-the-studio-posts-its-own-videos-756</link>
      <guid>https://dev.to/cybiqon/we-deleted-the-voice-bill-now-the-studio-posts-its-own-videos-756</guid>
      <description>&lt;p&gt;Six days ago I wrote up &lt;a href="https://cybiqon.in/lab/hyperframes-heygen-ai-video-pipeline" rel="noopener noreferrer"&gt;the first version of our video studio&lt;/a&gt;: a Claude Code agent that researched, scripted, voiced and rendered a video every night while I slept. It had made twelve videos in four days. It used a paid &lt;a href="https://www.heygen.com/" rel="noopener noreferrer"&gt;HeyGen&lt;/a&gt; clone of my voice, and every video still waited for me to post it by hand.&lt;/p&gt;

&lt;p&gt;Since then the studio has made 28 more, 40 in ten days. It now clones my voice with open-source models that run on the same 4-core server that renders the frames, in English and in Hindi, so the voice line in our costs is $0. Three cron jobs run it every day: one for blog explainers, one for product Shorts and one for teaching videos. YouTube and X get their uploads from the scheduler at 18:30 IST, and the scheduler checks at 19:00 that they actually went public. Two new review gates make sure a topic is a problem real developers have, and that a second AI model from a different company has read the script before any voice is spent on it.&lt;/p&gt;

&lt;p&gt;That is the headline. The fine print is that Instagram and Facebook are still posted by me, from my phone, with a caption the studio writes. The first X post made by cron goes out tonight. And the most useful thing we learned this week cost nothing to build: the owner (me) watched a Short, liked the visuals, and wrote "I learned nothing from it."&lt;/p&gt;

&lt;p&gt;This post walks through the whole machine, stage by stage. It covers how a topic is chosen, how the script is reviewed, how the voice is cloned offline, how frames are built and rendered, and how a finished MP4 gets to YouTube and X. All of it is in &lt;a href="https://github.com/cybiqon-ai/faceless-video-studio" rel="noopener noreferrer"&gt;faceless-video-studio&lt;/a&gt;, MIT-licensed, which you can install into Claude Code with one command. The last sections show how, and where to plug in your own uploader.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;40 videos in ten days (8–17 October 2026), with no camera, no microphone session and no editor.&lt;/strong&gt; By the studio's own ledger, that is 18 teaching videos (three of them 6–7-minute long-forms), 12 blog explainers, 5 product and service promos, and 5 made in the first two days, before the tracks were split. The total runtime is 65 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The voice now costs $0.&lt;/strong&gt; HeyGen voice cloning was removed from the studio on 16 October. English is &lt;a href="https://github.com/kyutai-labs/pocket-tts" rel="noopener noreferrer"&gt;Pocket TTS&lt;/a&gt; by Kyutai, at about real time on 4 CPU cores. Hindi is &lt;a href="https://github.com/AI4Bharat/IndicF5" rel="noopener noreferrer"&gt;IndicF5&lt;/a&gt; by AI4Bharat, at about 30× real time. Both are cloned from my own recordings and run offline. The HeyGen voice had cost about $0.14 per minute of narration, and it stopped the pipeline the day its credit ran out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The only required cost is Claude Code.&lt;/strong&gt; The agent's runs cost $11–26 each by Claude Code's own estimate. The optional extras cost cents: $0.03 for the second-opinion script review and $0.015 per X post.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three tracks a day, on cron.&lt;/strong&gt; 12:30 blog explainer → YouTube, public, linked on the blog post. 15:15 product Short → YouTube scheduled for 18:30 plus X at 18:30, verified at 19:00. 23:00 teaching Short or long-form → Telegram for me to review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;40 pass/fail gates.&lt;/strong&gt; The newest two, A21 and A22, check the topic and the script before any voice is made. A21 means a teaching video must start from a problem developers are visibly asking about online, with at least two links as proof. A22 means a model from a different company reviews every script and writes its own version. A new mix check keeps the music at least 20 dB under the voice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Posting is the part that is still half-manual.&lt;/strong&gt; YouTube and X are automatic. Instagram and Facebook are not yet. The studio delivers the MP4 and a ready caption to Telegram, and I post them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can run all of it.&lt;/strong&gt; &lt;code&gt;claude plugin marketplace add cybiqon-ai/faceless-video-studio&lt;/code&gt; installs the studio. A 30-second recording of your voice makes it speak as you. Four small Python plugins connect it to your own calendar, chat and storage, and a &lt;code&gt;POST.json&lt;/code&gt; per video hands its titles and captions to your own uploader.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What changed since the first AI video pipeline
&lt;/h2&gt;

&lt;p&gt;The first write-up was a snapshot from 11 October. This is the same studio six days later.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;11 October (post #1)&lt;/th&gt;
&lt;th&gt;17 October (now)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Videos made&lt;/td&gt;
&lt;td&gt;12 in 4 days&lt;/td&gt;
&lt;td&gt;40 in 10 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice&lt;/td&gt;
&lt;td&gt;HeyGen instant clone, paid per minute&lt;/td&gt;
&lt;td&gt;Pocket TTS (English) + IndicF5 (Hindi), offline on CPU, $0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Languages&lt;/td&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;English, Hindi (IndicF5 covers 11 Indian languages)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality gates&lt;/td&gt;
&lt;td&gt;37, then 38&lt;/td&gt;
&lt;td&gt;40, plus a loudness check that blocks delivery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Topic choice&lt;/td&gt;
&lt;td&gt;rotation + "what's new this week"&lt;/td&gt;
&lt;td&gt;a problem scout that proves real developers have the problem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script review&lt;/td&gt;
&lt;td&gt;three Claude reviewer agents&lt;/td&gt;
&lt;td&gt;three Claude reviewer agents + a second model from another company&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Viewer&lt;/td&gt;
&lt;td&gt;"a smart 12-year-old"&lt;/td&gt;
&lt;td&gt;a developer who already codes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daily runs&lt;/td&gt;
&lt;td&gt;1 (plus promo three days a week)&lt;/td&gt;
&lt;td&gt;3: blog, product, teaching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Posting&lt;/td&gt;
&lt;td&gt;all by hand&lt;/td&gt;
&lt;td&gt;YouTube and X by cron; Instagram and Facebook by hand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Installation&lt;/td&gt;
&lt;td&gt;clone + &lt;code&gt;install.sh&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;one-command Claude Code plugin, or clone + &lt;code&gt;install.sh&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four of those rows are changes of mind, not new features. The viewer, the topic rule, the voice and the posting all changed because something went wrong in front of real viewers. The sections below say what.&lt;/p&gt;

&lt;h2&gt;
  
  
  The daily schedule: three tracks, one server
&lt;/h2&gt;

&lt;p&gt;Everything runs on one Ubuntu server with 4 CPU cores and no GPU, in IST.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://cybiqon.in/lab/open-source-ai-video-studio-claude-code" rel="noopener noreferrer"&gt;Diagram: One studio day. The blog and product tracks run without me; the teaching track still ends with my review. See it in the original post.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Each track is the same skill run in a different mode by the same runner: &lt;code&gt;bin/run.sh blog&lt;/code&gt;, &lt;code&gt;bin/run.sh promo&lt;/code&gt;, &lt;code&gt;bin/run.sh&lt;/code&gt; (daily). The tracks share one lock, so they never render at the same time on a 4-core machine. Each has its own time and spend ceiling: 2 hours and $20 for the blog, 75 minutes and $25 for the product Short, 3 hours and $25 for a teaching Short, 7 hours and $60 for a long-form night.&lt;/p&gt;

&lt;p&gt;The teaching track follows a weekly rotation, set in &lt;code&gt;config.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Pillar&lt;/th&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monday&lt;/td&gt;
&lt;td&gt;AI term&lt;/td&gt;
&lt;td&gt;Short, 35–60 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tuesday&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Short&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wednesday&lt;/td&gt;
&lt;td&gt;AI deep dive&lt;/td&gt;
&lt;td&gt;Long-form, 16:9, 4–8 min, plus a vertical companion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thursday&lt;/td&gt;
&lt;td&gt;Build a thing&lt;/td&gt;
&lt;td&gt;Short&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Friday&lt;/td&gt;
&lt;td&gt;Tech news explained&lt;/td&gt;
&lt;td&gt;Short&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Saturday&lt;/td&gt;
&lt;td&gt;AI term or Python&lt;/td&gt;
&lt;td&gt;Short&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sunday&lt;/td&gt;
&lt;td&gt;System design deep dive&lt;/td&gt;
&lt;td&gt;Long-form, plus companion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A long-form for Wednesday renders on Tuesday night with a 7-hour ceiling. A render of a 7-minute video is about 12,700 frames.&lt;/p&gt;

&lt;h2&gt;
  
  
  Topic research: the problem scout (gate A21)
&lt;/h2&gt;

&lt;p&gt;On 15 October I watched a Short the studio had made about stopping SQLite's &lt;code&gt;VACUUM INTO&lt;/code&gt; on a read-only connection for an AI agent. It was true. The code had been run, and the footage was real. I wrote: "There's no point making a video on a problem which no one is facing."&lt;/p&gt;

&lt;p&gt;The rotation had told the agent "AI term", and the agent had picked something that was new and interesting to &lt;em&gt;it&lt;/em&gt;. Nobody had asked. A hook can't make a viewer recognise a problem they've never had. We shelved the video and added a step before anything else is chosen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem scout is a fresh sub-agent with one job.&lt;/strong&gt; It finds problems working developers are hitting this quarter in the day's subject, proves they have them, and ranks them. Its instructions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At least &lt;strong&gt;12 web searches across at least 4 kinds of source&lt;/strong&gt;: Stack Overflow (the "frequent" and "votes" tabs), GitHub issues on popular tools, Hacker News "Ask HN" threads, Google's "People also ask", and official forums such as Python Discourse and the AI providers' developer forums. Reddit is on the list too.&lt;/li&gt;
&lt;li&gt;Every problem is quoted &lt;strong&gt;in the asker's own words&lt;/strong&gt; and proven with &lt;strong&gt;at least two links and their numbers&lt;/strong&gt;: views, votes, 👍 reactions, comment counts, dates. "Don't invent numbers: if a count isn't visible, write n/a and score lower."&lt;/li&gt;
&lt;li&gt;Each problem is scored 0–3 on five things: &lt;strong&gt;Frequency, Recency, Everyday, Showable in 35–60 s, and We can prove it&lt;/strong&gt;. That makes a maximum of 15.&lt;/li&gt;
&lt;li&gt;A problem needs &lt;strong&gt;9 or more&lt;/strong&gt;, and a 0 on Frequency or Everyday rules it out at any total. Interview questions are ruled out always. We don't make interview content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scout writes 8–15 ranked rows to &lt;code&gt;research/problems.md&lt;/code&gt;, and every row goes into a persistent &lt;code&gt;problem-bank.json&lt;/code&gt;. The next run reads the bank first: a high-scoring, unused problem from the last 30 days can be picked without searching again, as long as its evidence is re-checked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate A21 enforces it in code.&lt;/strong&gt; The plan must cite the chosen row, its words and its two links. Only then may the agent write &lt;code&gt;A21: PASS (real problem)&lt;/code&gt;. &lt;code&gt;deliver.py&lt;/code&gt; refuses to deliver a teaching video without that line and without &lt;code&gt;research/problems.md&lt;/code&gt;, however good the video is.&lt;/p&gt;

&lt;p&gt;The rule changed the hooks too. A hook now opens on the viewer's situation, using the asker's words, before it names any tool. The best hook is often the question title itself, tidied up.&lt;/p&gt;

&lt;p&gt;Two practical notes from running it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reddit blocks data-centre IPs.&lt;/strong&gt; From our server, Reddit returns 403, and the free mirrors rate-limit agents. The scout copes because it needs four kinds of source, not Reddit in particular. If Reddit matters to your niche, use its official API with your own key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A planned topic still gets scouted.&lt;/strong&gt; When I put a topic in the content calendar, the scout doesn't overrule me. It finds the real problems people have &lt;em&gt;with that topic&lt;/em&gt;, and if none scores 9, it says so in the delivery message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the scout, the agent does 5–10 more searches for the facts. &lt;code&gt;ledger.py check&lt;/code&gt; refuses any topic close to a video the studio has ever made. Everything that will appear on screen is saved in the video's &lt;code&gt;research/&lt;/code&gt; folder first, including the output of the code it ran. "Nothing goes on screen that is not in this folder."&lt;/p&gt;

&lt;h2&gt;
  
  
  Script review: a linter, three reviewer agents and a second AI model
&lt;/h2&gt;

&lt;p&gt;A script has to get through Gate A before a second of voice is made. Voice and frames are where the time goes, so this is the cheapest place to reject a video.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The viewer is fixed.&lt;/strong&gt; In the first week the viewer was "a smart 12-year-old who never coded". The scripts that came out of it were clear, and I called them childish. Four Instagram Reels got 120–150 views each, and &lt;strong&gt;67% of viewers swiped away in the first three seconds&lt;/strong&gt;. Since 14 October the viewer is a developer or CS student who already codes. Known terms are never defined. Every Short carries at least one artifact worth saving — a command, a config, a number — and at least three named specifics every 30 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The hook is written three times.&lt;/strong&gt; The agent drafts three hooks from a library of 20 templates taken from real tech Shorts, and scores each out of 12. Sentence one is at most 14 words and names the concrete subject plus a tension. The proof is on screen by 1.5 seconds, and something new happens every 10–15 seconds. A Short runs 35–60 seconds and ends on its payoff, with no recap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then the checks, in order:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;script_lint.py&lt;/code&gt; (A18)&lt;/strong&gt; checks the hook's length, banned openers ("In this video…"), sentences over 20 words, a jargon list, and the specifics-per-30-seconds count. It is 250 lines of plain Python, and it is the only reviewer that cannot be argued with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A second opinion from another model (A22).&lt;/strong&gt; &lt;code&gt;second_opinion.py&lt;/code&gt; sends the script, with nothing else, to a model from a different company through &lt;a href="https://openrouter.ai/" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt;. We use OpenAI's. It reviews the script and also writes its own version from the same facts. The Claude agent then decides point by point and records the outcome, e.g. &lt;code&gt;A22: second opinion adopted 6, rejected 5&lt;/code&gt;. A model reviewing its own family's writing shares its blind spots. On its first run, the second opinion caught a Python &lt;code&gt;datetime&lt;/code&gt; Short that said &lt;code&gt;default=encode&lt;/code&gt; without ever showing what &lt;code&gt;encode&lt;/code&gt; was. That had passed three Claude reviewers. It costs about $0.03 a script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three fresh Claude sub-agents&lt;/strong&gt;, each seeing only the script and its brief:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A16, new-to-me.&lt;/strong&gt; A mid-level developer must name one concrete thing they learned and who they would send the video to. A separate judge grades that answer, because a reviewer that grades itself passes everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A19, the expert.&lt;/strong&gt; It flags anything that has become false by being simplified.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A20, the cold viewer.&lt;/strong&gt; It plays a developer scrolling Reels and sees only the first three seconds. Would they stop?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A hard fail gets one rewrite. A second fail sends the run back to pick a new topic, not to polish the old one.&lt;/p&gt;

&lt;p&gt;None of this makes a script teach. On 15 October I watched a Short that had passed A16 and said I liked the visuals but learned nothing. A16 passing is a lower bound, not proof. The fix was the "artifact plus three specifics" rule above. The rules keep moving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloning your voice offline, in English and Hindi
&lt;/h2&gt;

&lt;p&gt;For the first week the studio used a HeyGen instant clone of my voice. It sounded good. It cost about $0.14 per minute of narration, allowed 30 requests a minute, and on 13 October its prepaid credit ran out mid-pipeline. I wanted the voice to be like the renderer: on our machine, with no meter.&lt;/p&gt;

&lt;p&gt;We tested the open-source cloning models that run on a CPU and are licensed for commercial use:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;XTTS-v2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clones well and speaks Hindi, but its licence (CPML) forbids commercial use, and its Hindi sounded flat to native listeners. Rejected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chatterbox, NeuTTS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3–6× slower than Pocket TTS on our CPU, and they add an inaudible watermark. Rejected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://github.com/kyutai-labs/pocket-tts" rel="noopener noreferrer"&gt;Pocket TTS&lt;/a&gt;&lt;/strong&gt; (Kyutai)&lt;/td&gt;
&lt;td&gt;English. A 100M-parameter model built for CPUs. Kyutai reports about 6× real time on a MacBook Air M4 using 2 cores; on our server it voices at about real time. Clones from a plain WAV of 20–30 seconds of you talking. Code MIT, model CC-BY-4.0. &lt;strong&gt;Chosen for English.&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://github.com/AI4Bharat/IndicF5" rel="noopener noreferrer"&gt;IndicF5&lt;/a&gt;&lt;/strong&gt; (AI4Bharat)&lt;/td&gt;
&lt;td&gt;Hindi and 10 other Indian languages. About 30× real time on 4 cores, so a 60-second Short takes about 30 minutes, overnight. Needs a 6–10 second reference clip and its exact words. MIT. &lt;strong&gt;Chosen for Hindi.&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both models are gated on Hugging Face: you accept their terms once with a free account. Both sets of terms say the same thing: clone only voices you have explicit permission to clone. Here, that's my own. &lt;code&gt;install_voice.sh pocket&lt;/code&gt; or &lt;code&gt;install_voice.sh indicf5&lt;/code&gt; puts each one in its own virtualenv with a CPU build of PyTorch, because their dependencies conflict. &lt;code&gt;tts_engine.py&lt;/code&gt; is the one interface the rest of the studio sees: an engine class with &lt;code&gt;say(text) → samples&lt;/code&gt;. Adding a third engine is a class and an install recipe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How a voice track is built&lt;/strong&gt; (&lt;code&gt;voice_build.py&lt;/code&gt;, 292 lines):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The script is split into sentences, including on the Devanagari full stop &lt;code&gt;।&lt;/code&gt; for Hindi.&lt;/li&gt;
&lt;li&gt;The engine loads once and voices &lt;strong&gt;each sentence as its own take&lt;/strong&gt;. Takes are cached by a hash of the text, engine, reference and speed. So when I ask for one line to change, only that line is re-voiced.&lt;/li&gt;
&lt;li&gt;Spoken spellings come from &lt;code&gt;hf/say.json&lt;/code&gt;. The engine says "timeout equals three point oh five, ten" while the caption keeps &lt;code&gt;timeout=(3.05, 10)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The speed is set after synthesis with FFmpeg's &lt;code&gt;atempo&lt;/code&gt;, which keeps the pitch. My English clone at 1.0 was too fast for anyone learning. 0.64 sounded drawn out, and &lt;strong&gt;0.82&lt;/strong&gt; stuck. IndicF5 speaks Hindi at a comfortable pace already, so it stays at 1.0.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/SYSTRAN/faster-whisper" rel="noopener noreferrer"&gt;faster-whisper&lt;/a&gt; transcribes each take for word timings, which drive the captions. With &lt;code&gt;best_of: 3&lt;/code&gt;, each sentence is voiced three times and the take Whisper hears closest to the script wins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each take is trimmed on the real sound, never on timestamps.&lt;/strong&gt; The cut is at the first and last 10 ms window louder than 3% of the take's peak, with a little padding and 8 ms fades. Our first trim used word timestamps, which run about 0.2 seconds late, and it cut the start off a third of all sentences: "The" became "e". On one promo, 12 of 44 sentences were damaged.&lt;/li&gt;
&lt;li&gt;The takes are joined with a configurable breath between sentences. I wanted about a second of silence after every sentence, so our studio uses 0.82 s.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;voice_build.json&lt;/code&gt; records a hash of every voice file. Gate B17 refuses a video whose voice wasn't made by this script, or was changed after it, or has a take that starts or ends on speech.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Recording the reference matters more than the model.&lt;/strong&gt; Some of what we learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The clone copies the room. Pick a quiet room over a good microphone. A phone voice note 15 cm from your mouth is enough.&lt;/li&gt;
&lt;li&gt;Read expressively. A flat reading gives a flat clone.&lt;/li&gt;
&lt;li&gt;Record in the language the clone will speak. A Hindi voice cloned from English sounds like an English speaker reading Hindi.&lt;/li&gt;
&lt;li&gt;Background hiss is cloned too. FFmpeg's RNNoise filter (&lt;code&gt;arnndn&lt;/code&gt;) removes it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For IndicF5, keep the reference short.&lt;/strong&gt; F5 cuts a reference longer than about 15 seconds but keeps &lt;em&gt;all&lt;/em&gt; of its transcript, so it thinks you speak faster than you do and chops the ends off words. Our first Hindi takes had exactly this bug. A 6.7-second clip fixed it, and the same lines came out 21.7 seconds long instead of 15.7. &lt;code&gt;tts_engine.py&lt;/code&gt; now refuses a reference over 12 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write Hindi the way people talk.&lt;/strong&gt; Hinglish — "तो हुआ ये कि…", "अब सोचो…" — sounds alive. Textbook Hindi, voiced correctly, still sounded flat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A second voice is a variant in &lt;code&gt;config.json&lt;/code&gt;, chosen per video with &lt;code&gt;voice_build.py --voice hi&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"voice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pocket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reference"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"voice/sample.wav"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"english_2026-04"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"speed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lufs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"variants"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"engine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indicf5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"reference"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"voice/hindi.wav"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"reference_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"यार, आज एक मज़ेदार चीज़ हुई। …"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"asr_language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"speed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Hindi voice is wired in and tested, but no Hindi video has been published yet. That is next on the list, not something we've proven with viewers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loudness: voice at −16 LUFS, music 20 dB under, and a mix gate
&lt;/h2&gt;

&lt;p&gt;On 16 October I wrote: "the voiceover is still low." Pocket's takes came out 2–3 dB quieter than HeyGen's had been, and one music bed at a volume of 0.16 was only 13 dB under the voice. Both were fixed with a rule — never raise the music to compensate, only bring the voice up — and with code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every voice line is levelled to −16 LUFS&lt;/strong&gt; with a gentle compressor, a static gain and a −1.5 dBFS limiter. We don't run FFmpeg's &lt;code&gt;loudnorm&lt;/code&gt; on each line, because it pumps on a 5-second clip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mix_check.py&lt;/code&gt;&lt;/strong&gt; computes each music bed's effective loudness: its file LUFS plus 20·log10 of its volume in the composition. The mean voice must be at least 20 dB louder than the loudest bed. &lt;code&gt;--fix&lt;/code&gt; rewrites the bed volume, and &lt;code&gt;deliver.py&lt;/code&gt; refuses a video that fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The final file&lt;/strong&gt; goes through one &lt;code&gt;loudnorm&lt;/code&gt; pass to −14 LUFS integrated, −1.5 dB true peak. Spotify documents −14 LUFS as its normalisation target, and YouTube's is widely reported to be the same, so the platforms shouldn't turn us down or up much. (Broadcast, under EBU R 128, is −23.) The video stream is copied untouched, and the audio is AAC at 192 kb/s, 48 kHz.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frames: 22 looks, a signature beat, and real footage
&lt;/h2&gt;

&lt;p&gt;Viewers judge the voice first and the look second. In the first week I told the agent two videos looked so alike that "it will soon be noticed as AI generated". Since then the look is a choice the agent has to justify, and a gate it has to pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;22 look families.&lt;/strong&gt; Blueprint, whiteboard, documentary, split-screen, isometric, editorial paper, collage-scrapbook, and seven hand-drawn styles: pixel-era, riso-poster, ink-machine, math-board, iso-hairline, cut-paper and sketchbook. The 40 videos so far have used 22 different looks between them. &lt;code&gt;ledger.py look-check&lt;/code&gt; refuses a family used in the last six videos. Only the brand core stays fixed: the voice, one green accent and a two-second end mark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One signature beat per video.&lt;/strong&gt; Every video has one hero moment that the last five videos didn't have. It might be a registry block from HyperFrames, a Three.js or Lottie scene, a hand-drawn animate moment or a Manim transform. It is recorded in the ledger so it can't repeat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three engines.&lt;/strong&gt; &lt;a href="https://github.com/heygen-com/hyperframes" rel="noopener noreferrer"&gt;HyperFrames&lt;/a&gt; is the main one: every frame is HTML, CSS and a paused GSAP timeline, rendered deterministically by headless Chrome and FFmpeg. The seven hand-drawn styles come from &lt;a href="https://github.com/cth9191/animate" rel="noopener noreferrer"&gt;animate&lt;/a&gt; by cth9191, vendored at v0.4.0, and may cover at most about 70% of a video. &lt;a href="https://www.manim.community/" rel="noopener noreferrer"&gt;Manim&lt;/a&gt; is optional, for maths and diagrams, rendered to transparent WebM and laid over a HyperFrames scene.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One sub-agent per frame, in parallel.&lt;/strong&gt; The plan breaks the script into beats. Each beat gets a fresh Claude sub-agent that builds that frame's HTML from the plan, the look's design file and the voice timings. The 85-second Short in the first post had 11; a long-form has 25–45 and is built in waves of 8. A script assembles them into one composition. This is where most of the agent's spend goes: on the first version's 85-second Short, about $10 of $18.35 was the 11 frame workers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real footage beats anything drawn.&lt;/strong&gt; The videos I've liked most show the real thing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blog explainers&lt;/strong&gt; capture the source page itself at 2× in a headless browser and draw a highlighter on the exact sentences being discussed. The rectangles are read from the page's DOM, not guessed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App and game Shorts&lt;/strong&gt; use the real app. For our Flutter games, a &lt;code&gt;flutter_test&lt;/code&gt; harness drives the real level screen with the solver's own moves and saves 1080×1920 frames. Lumina's Short showed a mechanic, then a solver-proven number, and I loved it. The rule in the skill: "Never mock gameplay or UI."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web footage&lt;/strong&gt; is captured with &lt;code&gt;page.screenshot&lt;/code&gt; at device pixel ratio 3 in a loop. Chrome's screencast API on our server delivers 360×640 whatever size you ask for, so every screen recording before 10 October was a 3× upscale.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rendering video with HyperFrames on a CPU
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;npx hyperframes render --quality high&lt;/code&gt; turns the composition into an MP4 on the same 4-core server, in headless Chrome. Shorts are 1080×1920 and long-forms 1920×1080, both at 30 fps. Renders have taken from 2.5 minutes for a short promo to 19.5 minutes for a 7-minute long-form of 12,722 frames. There is no GPU and no cloud render.&lt;/p&gt;

&lt;p&gt;Before rendering, &lt;code&gt;mix_check.py --fix&lt;/code&gt; sets the music. After rendering, the final loudness pass runs. Then Gate B looks at what was actually made:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hyperframes check --strict&lt;/code&gt; passes (B1).&lt;/li&gt;
&lt;li&gt;Safe zones, font sizes, one accent per frame, and no "AI slop" tells such as gradient text, neon, Inter/Roboto/Poppins, stock people or emoji (B2–B5).&lt;/li&gt;
&lt;li&gt;Footage fills at least 80% of the frame for at least half the runtime (B11). A fresh agent compares this video's contact sheet with the last three and fails it if they look like one template (B13).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A judge reviews at least six frames decoded from the final MP4, including frame 0&lt;/strong&gt; (B14). Browser snapshots once looked perfect while the rendered first frame showed a clip's untrimmed opening, with prices and an "Add to cart" button.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gap_check.py&lt;/code&gt; caps the silence between lines (B16). &lt;code&gt;fill_check.py&lt;/code&gt; fails a Short whose bottom 40% is empty in more than a quarter of sampled frames (B18).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A frame that fails goes back to its sub-agent. After two loops the run delivers with the failure noted, rather than burning the night.&lt;/p&gt;

&lt;h2&gt;
  
  
  40 quality gates, and the failures behind them
&lt;/h2&gt;

&lt;p&gt;Twenty-two gates check the plan (A1–A22) and eighteen check the build (B1–B18). Eight are enforced in code — A16, A18, A19, A20, A21, A22, B17 and B18 — and &lt;code&gt;deliver.py&lt;/code&gt; will not deliver a video without them. The rest are rubric rules that agents check and record. Version 1.1.0 of the repo, released with this post, documents all 40.&lt;/p&gt;

&lt;p&gt;Almost none were in the first version. Each was added on the day a video failed that way:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What went wrong&lt;/th&gt;
&lt;th&gt;Gate it produced&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The first Short: 97 words in 44 s, "too fast, too much content"&lt;/td&gt;
&lt;td&gt;A8: 35–60 s, pauses kept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A script that "considers the viewer as an expert", then a rewrite called "childish"&lt;/td&gt;
&lt;td&gt;A14–A16, A18: fixed developer viewer, linter, new-to-me review with a separate judge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Three simplifications that had become untrue&lt;/td&gt;
&lt;td&gt;A19: expert review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"The hook doesn't make sense"; 67% swiping away in 3 s&lt;/td&gt;
&lt;td&gt;A2, A20: subject + tension in ≤ 14 words, cold-viewer test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A true, well-made video about a problem nobody has&lt;/td&gt;
&lt;td&gt;A21: problem scout, ≥ 2 links, ≥ 9/15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A script that named a function it never showed&lt;/td&gt;
&lt;td&gt;A22: second opinion from another model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two videos so alike "it will soon be noticed as AI generated"&lt;/td&gt;
&lt;td&gt;B11, B13, the look rotation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A rendered first frame that differed from every snapshot&lt;/td&gt;
&lt;td&gt;B14 on the final file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3–5 s silences from padded frames&lt;/td&gt;
&lt;td&gt;B16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A third of sentences losing their first syllable&lt;/td&gt;
&lt;td&gt;B17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The bottom 40% of every Short left empty&lt;/td&gt;
&lt;td&gt;B18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"The voiceover is still low"&lt;/td&gt;
&lt;td&gt;Voice levelling + &lt;code&gt;mix_check.py&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The lesson hasn't changed since the first post, but we've seen it more often: &lt;strong&gt;rules written in prose get bent under pressure; rules checked in code don't.&lt;/strong&gt; Every gate that &lt;code&gt;deliver.py&lt;/code&gt; checks has held. The ones only described in the skill's instructions are the ones an agent eventually talks itself past at 2 a.m.&lt;/p&gt;

&lt;h2&gt;
  
  
  Posting to YouTube and X automatically, and why Instagram isn't yet
&lt;/h2&gt;

&lt;p&gt;The open-source studio stops at the MP4. It delivers the video and a post kit and never posts by itself. That is on purpose: you should decide what goes out under your name, and how. Our own posting layer is about 700 lines of standard-library Python in our private ops repo. It reads exactly what the studio leaves behind, so here is how it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the studio hands over.&lt;/strong&gt; Every delivered video has a &lt;code&gt;POST.json&lt;/code&gt; that &lt;code&gt;deliver.py&lt;/code&gt; validates against a schema: three title options, a YouTube description, a caption with no URLs, hashtags, a pinned comment, a &lt;code&gt;thumbnail_at&lt;/code&gt; timestamp, sources, notes, the cost, and the look used. Tracked links are built in code with &lt;code&gt;utm_source&lt;/code&gt;, &lt;code&gt;utm_medium=video&lt;/code&gt; and &lt;code&gt;utm_campaign=&amp;lt;date&amp;gt;-&amp;lt;slug&amp;gt;&lt;/code&gt;. &lt;code&gt;deliver.py&lt;/code&gt; sends the MP4 and a readable version of the kit through the &lt;code&gt;notify&lt;/code&gt; plugin (Telegram by default) and prints one line a scheduler can grep:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rendered: orbitone-one-tap 51s 20.4MB msg 1769 parts=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;YouTube&lt;/strong&gt; goes through the &lt;a href="https://developers.google.com/youtube/v3/docs/videos/insert" rel="noopener noreferrer"&gt;YouTube Data API v3&lt;/a&gt; with a resumable upload:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;blog explainer&lt;/strong&gt; uploads public into its playlist. The description is &lt;code&gt;POST.json&lt;/code&gt;'s plus a "Read the full post" link. The upload's video ID is then written onto the blog post, so the post and the app show the video. If the site hasn't rebuilt since the post went up, the script asks GitHub to redeploy it.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;product Short&lt;/strong&gt; uploads private with &lt;code&gt;status.publishAt&lt;/code&gt; set to 18:30 IST, into a playlist per product. The playlist is created the first time a product appears. If the render finished after 18:30, it goes public at once.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;19:00 job&lt;/strong&gt; reads the video back, checks it is really public, marks it posted on our content calendar, and sends me an alert if not.&lt;/li&gt;
&lt;li&gt;An upload ledger keyed by slug makes every call idempotent. The 14:00 retry re-runs the whole blog job, and a video that already went up is never uploaded twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two notes for anyone building the same thing. First, our scripts were written for a rule from 2020: uploads from an API project that hadn't passed Google's audit were locked to private. Google's current reference now says videos from unverified projects "are not restricted to private viewing mode", and our public uploads since 16 October have stayed public. Either way, the script reads back the privacy YouTube actually applied rather than trusting the one it asked for. Second, quota isn't a problem at this scale. Since June 2026 uploads have their own bucket of 100 &lt;code&gt;videos.insert&lt;/code&gt; calls a day, and we use three.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;X&lt;/strong&gt; gets the same MP4 at 18:30 through the &lt;a href="https://docs.x.com/x-api/media/quickstart/media-upload-chunked" rel="noopener noreferrer"&gt;X API v2&lt;/a&gt; chunked media upload, then a post. The script signs requests with OAuth 1.0a using only Python's standard library: &lt;code&gt;hmac&lt;/code&gt;, &lt;code&gt;hashlib&lt;/code&gt; and &lt;code&gt;urllib&lt;/code&gt;, with no Twitter SDK. X's pay-per-use pricing made one design decision for us. A post costs &lt;strong&gt;$0.015&lt;/strong&gt;, but &lt;strong&gt;$0.20 if its text contains a URL&lt;/strong&gt;, and X turns a bare &lt;code&gt;cybiqon.in&lt;/code&gt; into a link too. So the post text is the caption's first and last paragraphs plus two hashtags, at most 280 characters, and the script refuses to post if any link survives. The link lives in the bio. One post a day is about $0.45 a month. Tonight, 17 October, is the first post made by cron.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Telegram&lt;/strong&gt; gets every video, from all three tracks, with the post kit. That is where I review the teaching videos. The bot is two-way: it is a Claude Code session in tmux with the Telegram plugin, so "make the hook shorter and re-render" from my phone becomes a &lt;code&gt;revise&lt;/code&gt; run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Drive&lt;/strong&gt; gets every final render through an &lt;code&gt;archive&lt;/code&gt; plugin, because the server deletes heavy media after delivery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instagram and Facebook are still posted by hand.&lt;/strong&gt; Instagram does have a content-publishing API, and Reels are supported. To use it we'd need an Instagram professional account, a Meta app with the content-publishing permission at advanced access, and every video hosted at a public URL for Meta to fetch when we publish. Through Facebook Login the account must also be connected to a Facebook Page. The limit, 100 API-published posts in 24 hours, is no obstacle. None of that is hard, but it hasn't been done. Until it is, the studio writes the Instagram caption and I post from my phone. That takes about a minute, and it is honestly where the pipeline leaks. Of 40 videos made, only the YouTube and X uploads are posted without me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Claude Code unattended, safely enough
&lt;/h2&gt;

&lt;p&gt;Cron calls &lt;code&gt;bin/run.sh&lt;/code&gt;, which is 140 lines of bash. The important line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flock &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WAIT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; 75 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nb"&gt;timeout&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LIMIT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"/video-studio &lt;/span&gt;&lt;span class="nv"&gt;$MODE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--permission-mode&lt;/span&gt; acceptEdits &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowedTools&lt;/span&gt; &lt;span class="s2"&gt;"Read,Write,Edit,Glob,Grep,WebSearch,WebFetch,Skill,Task,TodoWrite,Bash"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disallowedTools&lt;/span&gt; &lt;span class="s2"&gt;"Bash(git push:*),Bash(rm -rf:*),Bash(curl:*),Bash(wget:*),Bash(sudo:*),Bash(crontab:*),Bash(ssh:*),Bash(scp:*)"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--strict-mcp-config&lt;/span&gt; &lt;span class="nt"&gt;--mcp-config&lt;/span&gt; &lt;span class="s1"&gt;'{"mcpServers":{}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--max-budget-usd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BUDGET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each part is for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;flock&lt;/code&gt;&lt;/strong&gt; stops two tracks from rendering at once. A second run waits up to two hours for the lock.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;timeout&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;--max-budget-usd&lt;/code&gt;&lt;/strong&gt; cap time and spend per run. A long-form night gets 7 hours and $60. The runner reads the day's format from the ledger before it starts. The budget is Claude Code's own estimate and a run can go a little past it, so set it below what you can afford.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allow Bash, deny the dangerous commands by name.&lt;/strong&gt; The first unattended run stalled at a permission prompt for &lt;code&gt;node&lt;/code&gt; that nobody was awake to answer. A headless run can't answer prompts, so a per-command allowlist only stalls it. The deny list is the safety: no pushing, no downloads with curl or wget, no sudo, ssh or crontab, no &lt;code&gt;rm -rf&lt;/code&gt;. There is no &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No MCP servers&lt;/strong&gt; load in a studio run. The agent gets exactly the tools above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Success is a count, not a status.&lt;/strong&gt; The run must print &lt;code&gt;deliver.py&lt;/code&gt;'s &lt;code&gt;Rendered:&lt;/code&gt; line. If it doesn't, the runner sends an alert saying why: killed by the time limit, lock held, Claude logged out, credits or usage limit reached, or "exited 0 but nothing was delivered". We learned this one the hard way on other jobs. An exit code of 0 had meant "did nothing" for 33 nights in a row.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What an AI video studio costs to run
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Voice (Pocket TTS, IndicF5)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$0&lt;/strong&gt;, CPU time: ~1× real time English, ~30× Hindi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rendering (HyperFrames)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$0&lt;/strong&gt;, CPU time: 2.5–19.5 min a video on 4 cores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Captions (faster-whisper)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;the 4-core Linux machine you already have&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt;, per run&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$11–26&lt;/strong&gt; by Claude Code's own estimate. Short runs logged $11.0–20.5; the first long-form plus companion was about $26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;em&gt;Optional:&lt;/em&gt; second-opinion review (A22, OpenRouter)&lt;/td&gt;
&lt;td&gt;about $0.03 a script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;em&gt;Optional:&lt;/em&gt; X posting&lt;/td&gt;
&lt;td&gt;$0.015 a post with no link, $0.20 with one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;em&gt;Optional:&lt;/em&gt; HyperFrames music and sound-effect search&lt;/td&gt;
&lt;td&gt;uses a HeyGen API key if you set one; the voice never does&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Claude Code is the only cost the studio needs. The figures above are what Claude Code reports at API prices. On a Claude subscription, the runs use your plan's usage instead of adding a bill.&lt;/p&gt;

&lt;p&gt;We still don't know the full cost per video. Only 6 of the 40 ledger entries have &lt;code&gt;cost_usd&lt;/code&gt; filled in. In the first post I said that was the first thing I'd do differently. It's still the first thing. Fill it in from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What didn't work
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The "smart 12-year-old" viewer.&lt;/strong&gt; It fixed scripts that assumed an expert, then overcorrected into scripts I called childish, and 67% of Instagram viewers left in three seconds. &lt;strong&gt;Fix:&lt;/strong&gt; the viewer is a developer who codes, with an artifact and three specifics every 30 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interesting topics nobody needed.&lt;/strong&gt; A correct video about a SQLite edge case that no one asks about. &lt;strong&gt;Fix:&lt;/strong&gt; the problem scout and A21.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gates that pass a script that doesn't teach.&lt;/strong&gt; A16 passed a Short I learned nothing from. &lt;strong&gt;Fix, partial:&lt;/strong&gt; a model from another company reviews and rewrites every script (A22). The honest version is that review agents raise the floor, and a viewer still has to tell you if it teaches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A voice on a meter.&lt;/strong&gt; HeyGen credit ran out on 13 October, in the middle of a run. &lt;strong&gt;Fix:&lt;/strong&gt; open-source voices on our own CPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A long Hindi reference chopped every word.&lt;/strong&gt; F5 cut the reference at 15 seconds but kept its whole transcript. &lt;strong&gt;Fix:&lt;/strong&gt; a 6–10 second clip, and code that refuses anything over 12.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quoted phrases disappeared.&lt;/strong&gt; Pocket dropped "tool query" from &lt;code&gt;sees "Error executing tool query"&lt;/code&gt; in 5 of 5 takes. &lt;strong&gt;Fix:&lt;/strong&gt; no quotes in spoken text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The voice got quieter when we switched engines.&lt;/strong&gt; &lt;strong&gt;Fix:&lt;/strong&gt; levelling to −16 LUFS and a mix gate, never louder music.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reddit blocks the server.&lt;/strong&gt; &lt;strong&gt;Fix:&lt;/strong&gt; the scout needs four kinds of source, not one site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracking.&lt;/strong&gt; Most ledger entries have no cost and only two are marked posted. Posting by hand meant the "posted" count was always behind. &lt;strong&gt;Fix, partial:&lt;/strong&gt; automated posts mark themselves.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What worked
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Real footage, again.&lt;/strong&gt; The blog explainers built from the source page, and the game Shorts built from the real game, are still the videos I like best. The studio's rule now is to show the real thing whenever one exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Moving the voice onto our own machine.&lt;/strong&gt; It removed a bill, a rate limit and an outage in one change. Revisions became free: re-voicing one sentence costs a few seconds of CPU.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A second opinion from a different model.&lt;/strong&gt; For three cents it caught a gap that three reviewers from the same family missed. Not all its advice is good — on one script the agent adopted 6 points and rejected 5 — but having the decision recorded is the useful part.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Letting a calendar choose some topics.&lt;/strong&gt; The &lt;code&gt;planner&lt;/code&gt; plugin lets me put a topic on our content calendar and have the studio make it, mark it made, and still prove a real problem behind it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Posting product Shorts with no human step.&lt;/strong&gt; Since 16 October a product Short is made at 15:15, scheduled, published at 18:30 and verified at 19:00 every day, without my input. It's the first track where "made" and "posted" are the same number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install it in one command and plug it into your pipeline
&lt;/h2&gt;

&lt;p&gt;You need Linux (macOS works with GNU &lt;code&gt;timeout&lt;/code&gt; and &lt;code&gt;flock&lt;/code&gt; from Homebrew), Node.js 22+, Python 3.10+, FFmpeg, &lt;a href="https://code.claude.com" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, a free Hugging Face account and about 5 GB of disk. No GPU.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install HyperFrames and the studio as Claude Code plugins:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude plugin marketplace add heygen-com/hyperframes
claude plugin &lt;span class="nb"&gt;install &lt;/span&gt;hyperframes@hyperframes

claude plugin marketplace add cybiqon-ai/faceless-video-studio
claude plugin &lt;span class="nb"&gt;install &lt;/span&gt;faceless-video-studio@faceless-video-studio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a plugin, the command is &lt;code&gt;/faceless-video-studio:video-studio&lt;/code&gt;, and it updates itself. If you'd rather own the files, clone the repo and run &lt;code&gt;./install.sh ~/my-studio&lt;/code&gt;. That checks your tools, links the skill into &lt;code&gt;~/.claude/skills/video-studio&lt;/code&gt; and creates a studio folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Install a voice and record yourself.&lt;/strong&gt; Accept the model terms on Hugging Face, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; huggingface_hub &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; hf auth login
&lt;span class="nv"&gt;STUDIO_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/my-studio skill/video-studio/scripts/install_voice.sh pocket    &lt;span class="c"&gt;# English&lt;/span&gt;
&lt;span class="nv"&gt;STUDIO_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/my-studio skill/video-studio/scripts/install_voice.sh indicf5   &lt;span class="c"&gt;# Hindi, optional&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Record 20–30 seconds of yourself speaking naturally to &lt;code&gt;~/my-studio/voice/sample.wav&lt;/code&gt;. The repo's &lt;a href="https://github.com/cybiqon-ai/faceless-video-studio/blob/main/docs/voice.md" rel="noopener noreferrer"&gt;voice guide&lt;/a&gt; has an English and a Hindi script to read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Set up and make a video:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/my-studio &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; claude
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /video-studio setup                          &lt;span class="c"&gt;# checks tools, voices two test lines for you to approve&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /video-studio plan &lt;span class="s2"&gt;"why my requests call hangs forever"&lt;/span&gt;   &lt;span class="c"&gt;# research + plan + Gate A, no voice spend&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /video-studio daily                          &lt;span class="c"&gt;# the full run: today's pillar from your rotation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit &lt;code&gt;brand/brand-core.md&lt;/code&gt; (your accent colour and handle), &lt;code&gt;brand/pillars.md&lt;/code&gt; (your audience and products) and &lt;code&gt;brand/products.json&lt;/code&gt; (where promos link) to make it yours. The repo's &lt;code&gt;examples/cybiqon/&lt;/code&gt; is our real, filled-in brand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Put it on a schedule&lt;/strong&gt; once a few manual runs look right:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 23 * * *  STUDIO_ROOT=/home/you/my-studio /home/you/faceless-video-studio/bin/run.sh
30 12 * * * STUDIO_ROOT=/home/you/my-studio STUDIO_LIMIT=2h STUDIO_BUDGET=20 /home/you/faceless-video-studio/bin/run.sh blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cron has a short &lt;code&gt;PATH&lt;/code&gt;, so set it to wherever &lt;code&gt;claude&lt;/code&gt;, &lt;code&gt;node&lt;/code&gt; and &lt;code&gt;npx&lt;/code&gt; live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Plug it into your own systems.&lt;/strong&gt; Four optional plugins, each a small Python file named in &lt;code&gt;config.json&lt;/code&gt;, connect the studio to private systems without forking it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Our use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;planner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;topic(day, track, pillar, slot)&lt;/code&gt; lets a content calendar choose topics; &lt;code&gt;mark_made()&lt;/code&gt; marks them done&lt;/td&gt;
&lt;td&gt;our content calendar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;send()&lt;/code&gt; and &lt;code&gt;send_video()&lt;/code&gt; deliver the MP4 and post kit&lt;/td&gt;
&lt;td&gt;Telegram (built in); Slack, email or your own bot work the same way&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;links&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;builds tracked links for each product&lt;/td&gt;
&lt;td&gt;UTM links to our apps and sites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;archive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;store(video, post, revision)&lt;/code&gt; keeps the final render elsewhere&lt;/td&gt;
&lt;td&gt;Google Drive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;For automatic posting&lt;/strong&gt;, run your own uploader after the run, the way ours does. Wait for the &lt;code&gt;Rendered:&lt;/code&gt; line, read the video's &lt;code&gt;POST.json&lt;/code&gt; for the title, description and caption, and upload &lt;code&gt;hf/renders/video.mp4&lt;/code&gt;. Make it idempotent with a ledger keyed by slug, print a count line, and verify the result separately.&lt;/p&gt;

&lt;p&gt;If you'd rather have this set up for your business than build it yourself, &lt;a href="https://cybiqon.in/services/ai-agents" rel="noopener noreferrer"&gt;this is the kind of thing we build for clients&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Ten days in, the studio makes three videos a day on a machine with no GPU. The only bill it needs is Claude Code. The voice is mine, made offline, in two languages. Half of the output reaches viewers without me touching it.&lt;/p&gt;

&lt;p&gt;Three things I'd tell anyone building the same thing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Own the slow, metered parts.&lt;/strong&gt; The voice was the one stage that could fail for reasons outside our machine, and it did. Moving it to open-source models removed the cost, the rate limit and the outage. It also made revisions free, which changes how picky you can afford to be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make the agent prove the topic, not just the facts.&lt;/strong&gt; We had built gates for true facts, clear hooks and good frames, and still made a correct video nobody needed. The cheapest gate in the studio is the one that runs first: show me two people asking about this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automate the last step, or count it honestly.&lt;/strong&gt; For a week the studio made videos faster than I posted them, and the ledger's "posted" column said so. Whatever you automate, measure the number that reaches viewers, not the number rendered.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All figures are first-party unless marked, as of 17 October 2026. Video counts, durations, look families and per-run costs come from the studio's ledger (&lt;code&gt;cost_usd&lt;/code&gt;, filled for 6 of 40 entries) and run logs. Run costs are Claude Code's own estimates, not a bill. YouTube uploads come from the upload ledger; X prices from our X API account and X's pricing page; the second-opinion cost from OpenRouter usage. Voice speeds, reference limits and the rejected models come from the studio's voice tests between 13 and 16 October. Render times come from HyperFrames' render logs. Gates come from the skill's rubric. Owner feedback is quoted from notes made the day it was given. Instagram view figures are from four Reels, as reported on 14 October.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is the Faceless Video Studio free?
&lt;/h3&gt;

&lt;p&gt;Yes. The studio is open source under the MIT licence at github.com/cybiqon-ai/faceless-video-studio. Its voice (Pocket TTS and IndicF5), captions (faster-whisper) and renderer (HyperFrames) are free and run on your own CPU. The only required cost is Claude Code, which runs the agent. Our runs cost $11–26 each at API prices, or come out of a Claude subscription's usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I clone my voice for free, offline?
&lt;/h3&gt;

&lt;p&gt;Yes. Pocket TTS by Kyutai clones an English voice from 20–30 seconds of recording and runs at about real time on 4 CPU cores. IndicF5 by AI4Bharat clones Hindi and 10 other Indian languages from a 6–10 second clip and its transcript, at about 30 times real time. Both are free to download from Hugging Face after accepting their terms, and nothing is uploaded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a GPU to make AI videos with Claude Code?
&lt;/h3&gt;

&lt;p&gt;No. Our studio runs on a 4-core Linux server with no GPU. Voice cloning, captioning and HyperFrames rendering all run on the CPU. A Short renders in a few minutes, and a 7-minute long-form took 19.5 minutes. The Hindi voice is the slowest step, at about 30 minutes for a 60-second Short, which a nightly schedule hides.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the studio post to YouTube, Instagram and X automatically?
&lt;/h3&gt;

&lt;p&gt;The open-source studio delivers an MP4 and a post kit, and does not post by itself. Our own uploader posts the product Shorts to YouTube and X and the blog explainers to YouTube automatically, using the YouTube Data API and the X API. Instagram and Facebook are still posted by hand: Meta's publishing API needs an Instagram professional account, a Meta app with the publishing permission, and each video at a public URL, and we haven't set that up yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it still need HeyGen?
&lt;/h3&gt;

&lt;p&gt;No. HeyGen voice cloning was removed on 16 October 2026 and replaced with Pocket TTS and IndicF5. HyperFrames, the renderer, is an open-source project from HeyGen under Apache 2.0, and needs no HeyGen account. An optional HeyGen API key only enables HyperFrames' own music and sound-effect search.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which languages can the studio speak?
&lt;/h3&gt;

&lt;p&gt;English through Pocket TTS, which also supports French, German, Spanish, Italian, Portuguese and Dutch. Through IndicF5 it speaks Hindi, Bengali, Gujarati, Marathi, Punjabi, Odia, Assamese, Tamil, Telugu, Kannada and Malayalam. Each language is a voice variant in the studio's config, chosen per video.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does the studio choose video topics?
&lt;/h3&gt;

&lt;p&gt;For teaching videos, a problem-scout agent searches Stack Overflow, GitHub issues, Hacker News, Google's "People also ask" and developer forums for problems working developers are asking about. Each problem needs at least two links with their numbers and a score of 9 or more out of 15. The studio refuses to deliver a teaching video without that proof.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does one video take to make?
&lt;/h3&gt;

&lt;p&gt;A product Short takes about 75 minutes from start to delivery, most of it research, review agents and parallel frame building. The render itself takes 2.5–19.5 minutes. The runner caps a Short at 3 hours and a long-form at 7.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/cybiqon-ai/faceless-video-studio" rel="noopener noreferrer"&gt;faceless-video-studio&lt;/a&gt; — the studio described here, MIT&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/cybiqon-ai/faceless-video-studio/blob/main/docs/voice.md" rel="noopener noreferrer"&gt;faceless-video-studio: cloning your voice&lt;/a&gt; — the voice method and measurements&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/cybiqon-ai/faceless-video-studio/blob/main/docs/gates.md" rel="noopener noreferrer"&gt;faceless-video-studio: all gates&lt;/a&gt; — every gate and the failure behind it&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/heygen-com/hyperframes" rel="noopener noreferrer"&gt;HyperFrames on GitHub&lt;/a&gt; — HeyGen, Apache 2.0&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kyutai-labs/pocket-tts" rel="noopener noreferrer"&gt;Pocket TTS on GitHub&lt;/a&gt; — Kyutai&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://huggingface.co/kyutai/pocket-tts" rel="noopener noreferrer"&gt;Pocket TTS on Hugging Face&lt;/a&gt; — model card and licence&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/AI4Bharat/IndicF5" rel="noopener noreferrer"&gt;IndicF5 on GitHub&lt;/a&gt; — AI4Bharat&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://huggingface.co/ai4bharat/IndicF5" rel="noopener noreferrer"&gt;IndicF5 on Hugging Face&lt;/a&gt; — model card&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/SYSTRAN/faster-whisper" rel="noopener noreferrer"&gt;faster-whisper&lt;/a&gt; — SYSTRAN, MIT&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/cth9191/animate" rel="noopener noreferrer"&gt;animate&lt;/a&gt; — cth9191, MIT&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.manim.community/" rel="noopener noreferrer"&gt;Manim Community&lt;/a&gt; — MIT&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hyperframes.heygen.com/introduction" rel="noopener noreferrer"&gt;HyperFrames documentation&lt;/a&gt; — HeyGen&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/youtube/v3/docs/videos/insert" rel="noopener noreferrer"&gt;YouTube Data API: Videos: insert&lt;/a&gt; — Google; upload quota and the &lt;code&gt;status.privacyStatus&lt;/code&gt; note&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/youtube/v3/docs/videos" rel="noopener noreferrer"&gt;YouTube Data API: Videos resource&lt;/a&gt; — &lt;code&gt;status.publishAt&lt;/code&gt; can be set only on private videos&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/youtube/v3/revision_history" rel="noopener noreferrer"&gt;YouTube Data API: revision history&lt;/a&gt; — the 2020 private-lock rule and the 2026 quota buckets&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.x.com/x-api/getting-started/pricing" rel="noopener noreferrer"&gt;X API pricing&lt;/a&gt; — $0.015 per post, $0.200 per post with a URL&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.x.com/x-api/media/quickstart/media-upload-chunked" rel="noopener noreferrer"&gt;X API: chunked media upload&lt;/a&gt; — X developer documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.facebook.com/docs/instagram-platform/content-publishing" rel="noopener noreferrer"&gt;Instagram Platform: content publishing&lt;/a&gt; — Meta for Developers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/headless" rel="noopener noreferrer"&gt;Claude Code: run programmatically&lt;/a&gt; — Anthropic, &lt;code&gt;claude -p&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/cli-reference" rel="noopener noreferrer"&gt;Claude Code CLI reference&lt;/a&gt; — Anthropic&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/plugins/cli-reference" rel="noopener noreferrer"&gt;Claude Code plugin commands&lt;/a&gt; — Anthropic&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ffmpeg.org/ffmpeg-filters.html#loudnorm" rel="noopener noreferrer"&gt;FFmpeg loudnorm filter&lt;/a&gt; — FFmpeg documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://support.spotify.com/us/artists/article/loudness-normalization/" rel="noopener noreferrer"&gt;Spotify: loudness normalisation&lt;/a&gt; — the −14 LUFS target&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tech.ebu.ch/publications/r128" rel="noopener noreferrer"&gt;EBU R 128: Loudness normalisation&lt;/a&gt; — European Broadcasting Union&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cybiqon.in/lab/hyperframes-heygen-ai-video-pipeline" rel="noopener noreferrer"&gt;Twelve videos in four days. Nobody picked up a camera.&lt;/a&gt; — the first version of this studio&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cybiqon.in/lab/shipped-the-game-missed-the-deadline" rel="noopener noreferrer"&gt;We built the game in six days. The store review took fifteen.&lt;/a&gt; — one of the games whose real footage appears in the product Shorts&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cybiqon.in/lab/eight-bots-played-all-500-levels" rel="noopener noreferrer"&gt;Eight bots played all 500 levels&lt;/a&gt; — the solver behind the "solver-proven" numbers in the game Shorts&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cybiqon.in/lab/open-source-ai-video-studio-claude-code?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;cybiqon.in&lt;/a&gt;. Cybiqon is a small Indian software studio: websites, apps and AI automation for growing businesses. &lt;a href="https://cybiqon.in/services?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;See what we build&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aivideo</category>
      <category>claudecode</category>
      <category>hyperframes</category>
      <category>voicecloning</category>
    </item>
  </channel>
</rss>
