Video is code: a composition with time and deterministic render
In motion graphics as code, the video is a page that knows how to draw any instant. A headless browser asks for frame 0, then 1, then 2, and FFmpeg stitches them into an MP4. If the same input always gives the same frames, you can redo, fix, and version the video like code. In this lesson you prove it on your own machine.
1The four pieces: composition, clock, headless browser, and encoder
2Compare two renders frame by frame with one command
3Why Math.random and Date.now break the video
4Seeded random: variety without losing repeatability
Goal: prove on your own machine that the same composition gives the same frames, and find what breaks that.
01 · From code to MP4
Tap each piece. HyperFrames (HTML) and Remotion (React) follow the same design.
same input → same frames
Across different machines, GPU and fonts can change a few pixels; to repeat it on another machine, the 0.8.176 render has --docker ("deterministic render") and --no-browser-gpu.
Deterministic = run it twice in the same environment and get the same frames. That's what lets you fix one detail without redoing the rest.
02 · What breaks repeatability
Three sources of chance that the HyperFrames lint looks for.
1. Random draw
Each render draws again: the same 12 points show up in different places.
The fix is a seeded draw (mulberry32): the same seed gives the same numbers, every time.
Fixconst sorteio = mulberry32(42); then sorteio() in place of Math.random().
2. Machine clock
The current time changes with every render and every frame.
Everything that animates has to come from the time the engine provides, never from the computer's clock.
3. Network at render time
Fetching data from the internet during the render can bring back a different answer or fail midway.
Fetch it beforehand, save it to a file in the folder, and read it from there (lesson 7 does this with the captions).
03 · The test run: two renders, compared frame by frame
Two nearly identical compositions: one uses Math.random, the other a seeded draw. Each one is rendered twice and compared by the hash of each frame.
That's 90 frames (3 s at 30 fps). With Math.random only the first one matches; with the seed, all 90 do. The first render downloads hyperframes@0.8.176 and the browser, so it takes longer.
Comparing the MP4 with md5sum can come out different even when the frames are the same (file metadata). The comparar-quadros.sh script decodes the file and compares the image of each frame with ffmpeg -f framemd5.
04 · The lint finds the chance before the render
The same pair of compositions run through the HyperFrames lint. It points to the line and suggests the seed.
Terminal · lint of both compositions
K=~/projetos/formacao-cinema-ia-motion-v72/kit
export HYPERFRAMES_NO_TELEMETRY=1 NO_COLOR=1
for ex in a1-sorteio a1-semente; do
cd "$K/exemplos/$ex"
npx -y hyperframes@0.8.176 lint; echo "saída: $?"
done
Example from the repository
◆ Linting a1-sorteio/index.html
✗ [~/projetos/formacao-cinema-ia-motion-v72/kit/exemplos/a1-sorteio/index.html:24:24] non_deterministic_code: Script contains `Math.random()` which produces non-deterministic output. Renders may differ between frames or runs.
Fix: Use a seeded PRNG (e.g. a simple mulberry32) so renders are deterministic across frames.
◇ 1 error(s), 0 warning(s)
saída: 1
◆ Linting a1-semente/index.html
◇ 0 errors, 0 warnings
saída: 0
The lint exits with code 1 when it finds an error: you can use it as a gate before any render.
05 · Practice
Test run, lint, and your seed.
E1 · See the chance and the seed
10 min
Clone the course into ~/projetos and run the "two renders of each and the comparison" block.
Done when: a1-sorteio ends with DIFEREM: 89 de 90 quadros and saída: 1; a1-semente with IGUAIS: 90/90 quadros and saída: 0. (On your computer the number of differing frames may vary; what can't happen is the seed giving a different result.)
E2 · The lint as a gate
5 min
Run the lint block.
Done when: a1-sorteio shows non_deterministic_code and saída: 1; a1-semente shows 0 errors, 0 warnings and saída: 0.
E3 · Another seed, same guarantee
10 min
In /tmp/ensaio-motion/a1/a1-semente, replace mulberry32(42) with mulberry32(7), render twice (render-3.mp4 and render-4.mp4, using the same command from the block), and compare: 3 with 4, then 1 with 3.
Done when:bash ~/projetos/formacao-cinema-ia-motion-v72/kit/comparar-quadros.sh render-3.mp4 render-4.mp4 → IGUAIS: 90/90 quadros; the same with render-1.mp4 render-3.mp4 → starts with DIFEREM:.
06 · Lesson quiz
The explanation appears after you answer.
Question 1 of 4
Who decides which instant the composition draws?
Correct
The engine asks for the moment (2.5 s, frame 75) and the composition draws that moment. That's why speed doesn't change the result: in the same environment, a slow machine takes longer, but gives the same frames.
Not this one
The engine asks for the moment (2.5 s, frame 75) and the composition draws that moment. That's why speed doesn't change the result: in the same environment, a slow machine takes longer, but gives the same frames.Review: From code to MP4
Question 2 of 4
Two renders of the same composition came out with 89 of 90 frames different. What should you look for first?
Correct
Randomness inside the composition is the most common cause, and the lint points to Math.random by line. Both renders used the same hyperframes@0.8.176 and the same FFmpeg: what changed was the input.
Not this one
Randomness inside the composition is the most common cause, and the lint points to Math.random by line. Both renders used the same hyperframes@0.8.176 and the same FFmpeg: what changed was the input.Review: What breaks repeatability
Question 3 of 4
What is a seeded random draw for?
Correct
The seed fixes the sequence of numbers: the points are still scattered, but always in the same places. Changing the seed changes the pattern on purpose, and again in a repeatable way.
Not this one
The seed fixes the sequence of numbers: the points are still scattered, but always in the same places. Changing the seed changes the pattern on purpose, and again in a repeatable way.Review: The test run: two renders, compared frame by frame
Question 4 of 4
Why compare frame by frame and not the whole MP4?
Correct
What matters is the image. framemd5 decodes each frame and compares the pixels; the hash of the whole file also changes with metadata that never shows on screen.