A link straight to the file cannot be counted — the recipient fetches bytes
from the file host and we never hear about it. The short link runs through our
own domain, so every open is a page load, and it is 33 characters against 74.
The host serves this file for any extensionless path it does not recognise, so
/v/<code> lands here rather than at v/index.html. Four lines at the top of the
head hand it on before anything renders.
Deliberately the first thing in the head and wrapped in a try, so a share link
resolves before any paint and a failure here can never take the homepage down
with it.
A link straight to the file is untrackable — the recipient fetches bytes from
the file host and we never hear about it. Routing through paytolaugh.com makes
every open a page load that can be counted, and gives somewhere to put a real
player and a preview card.
The code is the first eight hex characters of the project id. Those are already
unique across every project and the space is 4.3 billion, so there is no
mapping table to build or keep in sync — the server resolves by prefix and
checks for ambiguity. 33 characters against 74 for the raw file.
Sharing stays deliberate: the page only resolves a project whose owner has
pressed Share.
Sharing from here is the common case — you are looking at the thing you want to
send. Uses the native share sheet where there is one, otherwise copies, and the
link carries a filename so it ends in .mp4 for clients that read the URL rather
than the headers.
The file URL ends in /public with no filename, which is fine for anything that
reads headers — it already serves Content-Type video/mp4 and a
Content-Disposition of final.mp4 — but clients that sniff the extension out of
the URL string get it wrong. A filename query is accepted and ignored by the
file service, so the link can end in .mp4 at a cost of a dozen characters.
The button uses the native share sheet where there is one, which on a phone is
the shortest path into a text message, and falls back to copying.
Sending the video itself by MMS puts it through carrier transcoding and it
arrives looking far worse than it left. So the picture carries the hook and a
link carries the video at full quality.
The still is 720x1280 and lands around 90KB at near-maximum quality — carrier
limits are typically 600KB and up, so there is no reason to compress hard. The
frame is chosen by sampling several moments and keeping the one that compresses
largest, which in practice means eyes open and in focus rather than mid-blink.
The play button sits at two thirds height so it never covers the face.
"I am not" and "extraordinarily unimpressed" are both three words and nowhere
near the same length, so any word budget is a guess about something we can
measure. Three attempts at tuning one were wrong in both directions.
Speaking the monologue once returns the start and end of every character, so
word boundaries are exact. This cuts each shot at the word whose end sits
nearest the boundary, preferring a sentence ending when one is close. On the
first real test it produced a 20 word shot landing at 7.8s and cutting on a
full stop, where the budget had said 18.
The run-through is spoken by ElevenLabs, but the finished audio is Veo's
performance re-voiced, and Veo enunciates more slowly - it was asked to, to
make the mouth movement readable. Measured on a real clip: 17 words ran 7.02s
filmed (about 2.4 words a second) against roughly 3.0 words a second spoken.
So the raw previz number understates the filmed length by about a quarter, and
the dead-air warning was correspondingly overstated - it claimed 2.4 seconds of
silence per shot where the filmed figure is nearer one. Show both numbers and
name the factor rather than quietly applying it.
The catalogue was 21 polished professional voices — podcast hosts and
narrators. None of them sounds like ninety pounds of jowly bulldog, which is
the actual complaint. Seven deep, gravelly, weathered voices are added, and
they go first because a talking animal usually wants weight rather than polish.
Without a group of their own they would not have appeared at all: the dropdown
only built male, female and neutral.
The first real measurement was the opposite of what was expected: four lines
spoke in 21.7s against 32s of video, so every eight second shot carries about
2.4 seconds of silence. That is almost certainly where the sound effects and
echo come from, since the model fills a gap it was given nothing to do with.
So flag short as loudly as long, and say what it means.
Filming is $0.40 a shot. Speaking the same words is a few hundredths of a cent.
So the narrative — does it flow, is the joke in the right place, does it drag —
can now be judged for about one percent of the cost, using the opening stills
as placeholders and the pet's real voice.
It also replaces a guess with a measurement. Line length was estimated from a
word count and hoped to fit eight seconds; speaking it says exactly how long it
takes, and a line that runs over is flagged before anything is filmed rather
than after.
"locked" used to mean "the script is approved" and now means "not on the layout
stage", so the price card read Approved on every stage but one — including on a
script still waiting to be read. It should follow whether the words are still
open, which is what canEditScript says.
The page showed the script, every shot's directing controls, your pictures,
filming, the joins and the cut simultaneously. That is four different jobs on
one screen, and the first of them - reading the words and getting them right -
is the one that matters most and was the easiest to skip past.
It is now four stages with a stepper: the words, the layout, filming, the cut.
Each shows only what belongs to it, and you can step back to look at an earlier
one at any time.
This also fixes something that was simply wrong: approving the script used to
hide every shot control, so the setting, camera and cutaways became uneditable
at exactly the moment you were meant to be arranging them. Directing now lives
in its own stage, after the words are settled and before anything is filmed.
The "$'" substitution token injected a second copy of everything after the
match point. The first copy was repaired into the real bindPost; this deletes
the leftover fragment and the duplicate that followed it, so the file ends at
its only </html> again.
The previous commit's replacement text contained the sequence "$'", which the
patch tool treats as a substitution token meaning "everything after the match".
It injected a duplicate copy of the rest of the file and left script.html with
a syntax error, so the page showed only "Loading...".
This closes the broken string and reopens bindPost so the duplicated tail that
follows becomes its legitimate body. A second patch removes the leftover.
Filming again used to overwrite the video outright. Models drift between
generations, so re-filming to fix one thing can make another worse, and there
was no way back.
Every take is now kept and listed under the cut, newest first, with what it
cost and which voice it used. Restoring one keeps the take you are leaving, so
going back is reversible too.
This deliberately lands before the "remake this shot" button, because that is
what will make people re-film often enough to need it.
The first and last ruler labels were centred on the track edges, so "0s" and
"32s" were half cut off.
More importantly the page had become busy: a finished video showed the cut, the
timeline, the joins and the shots all at once. The timeline is now behind an
"Open the video editor" button next to the video, so the default view is the
finished thing and the editing bay is somewhere you choose to go.
Playing to the end left the button reading "Pause" forever, because nothing
listened for the video ending. It now resets on end and on pause, shows the
current time against the total, and the track can be clicked or the playhead
dragged to reach a moment directly. Clicking a shot marker jumps to that shot.
Fixes and additions from watching it in use:
- the play button stayed on "Pause" after the video ran to the end
- there was no time readout and no way to reach a particular moment
- a cutaway was an anonymous blue rectangle, when the useful thing to show is
the picture it will actually put on screen
- nothing marked where one shot ended and the next began, which is the whole
reason the cutaways sit where they do
Shots are now marked above the bar with their opening frame, cutaways carry a
thumbnail of the image itself, and the playhead can be dragged or the track
clicked to move through the piece.
The cut could be measured and executed but never seen or adjusted. This adds a
track view of where each cutaway sits and a player that switches sources at
each boundary, so the edit can be judged before spending anything on a render.
The audio is deliberately never touched: the talking head plays underneath
throughout and only the picture changes, which is what makes a cutaway hide a
join instead of interrupting the piece.
Dragging a block moves that cutaway; the handle on its right edge changes how
long it runs. Nothing is saved until Save, and Reset returns to the placement
the system chose.
Matching pictures to the script is a model call and cannot answer inside the
few seconds the API gateway allows, so it returned "Function timed out". It now
starts in the background and this waits for it, the same way frames and joins
already do.
Step one creates the project on hold and reveals the collateral panel; step two
writes the script. A held project can be reopened with ?id= so a half-finished
setup is not lost.
The owner's pictures could only be attached after the script existed, so the
writer planned a piece in ignorance and the pictures were fitted to finished
words afterwards. That is backwards.
Making a video is now two steps. The project is created on hold, collateral is
attached, and only then does the writer run — knowing it has a photograph of
the actual product and eleven seconds of pool footage to write toward.
Footage is a link plus an in and out point, set by scrubbing the video itself.
The renderer already trimmed arbitrary sources with in/out, so this needed no
change to the cut at all, only somewhere to put the information.
A finished video was a dead end from this page: you could watch it or download
it, and the only way back into the shots, cutaways and cut was to know the
script URL. Edit goes to the detail screen, which is now where re-filming and
the cut live.
Three things were unclear or missing:
- "Make the frame" reads as though it might film the shot. It draws one still,
the opening frame, and films nothing. Say so.
- Once a project was ready there was no way to film it again, so editing a
shot afterwards led nowhere. The film card now appears for a finished video
as "Film it again".
- Nothing explained that these per-shot buttons are optional. Filming draws
any missing frame and makes any missing cutaway itself, so they are there to
preview, not to complete.
A shot that continues the previous one seeds from that shot's real last frame
and correctly has no opening still of its own. Flagging it as a lost likeness
was wrong and would send someone chasing a problem that is not there.
Also distinguish a frame drawn after the fact from the one actually used at
film time, so the audit trail does not imply a seed that was never sent.
A video was filmed where shot 1 had a photo selected but no opening frame. The
seed was therefore empty and Veo was handed the written description alone, so
it invented a plausible bulldog: no collar, wrong eye colour. Nothing on this
page suggested anything was missing.
The worker now draws any missing frame before filming rather than proceeding
without a likeness, so this says that is going to happen and why it takes
longer.
The thumbnails are 110px squares cropped square, which is too small to check
the thing they exist to record — the collar, the eye colour, the markings that
have to survive into the video.
A video came out with the wrong dog — no collar, hazel eyes instead of black —
and there was no way to tell from this page whether a reference photograph had
been used at all. It had not: the shot had a photo selected but no opening
frame, so Veo was given the written description only and invented a plausible
bulldog.
Show both images side by side, and say loudly when there is no opening frame,
because that is the exact signature of a likeness that silently went missing.
Cost was a single number, which now hides two very different things: about
$0.40 a shot of video generation against roughly $0.015 of voice conversion.
Per shot, show which voice was actually dubbed in, so a conversion that failed
and silently fell back to Veo's own audio is visible rather than invisible.
The reason was already being stored on the project, but the page showed a bare
"could not finish" and a Start again button. A dead link is the most common
cause and it is the reader's to fix, so it needs saying out loud.
The old field asked owners to describe a voice in prose, and that prose went
straight into the Veo prompt. Nobody can reliably describe a voice, and the
description matched no actual voice, so nothing held the character steady
between episodes.
Now the owner picks an ElevenLabs voice and can listen to it first. That single
choice drives the Veo prompt, the writer's sense of the character, the word
budget per shot, and the voice the finished clip is redubbed into. The prose
box stays as an optional note that reaches the writer only.
The old field asked owners to describe a voice in prose, and that prose went
straight into the Veo prompt. Nobody can reliably describe a voice, and the
description matched no actual voice, so there was nothing holding the character
steady between episodes.
Now the owner picks an ElevenLabs voice and can listen to it first. That single
choice drives the Veo prompt, the writer's sense of the character, the word
budget for each shot, and the voice the finished clip is redubbed into. The
prose box stays as an optional note that reaches the writer only.