24 vertical TikTok videos — generated 100% locally
Everything runs offline: Manim for 2D animation, Blender (EEVEE, headless)
for the 3D scenes, Godot (Jolt physics engine, headless) for rigid-body
physics, qwentts.cpp (a cloned Rioplatense voice) for the narration and the
keysound piano samples for the music.
The videos are in Spanish: narration, subtitles and on-screen text are Spanish on purpose. The code is in English.
The videos (out/)
| File | Topic | Length | Hook |
|---|---|---|---|
montyhall.mp4 |
The Monty Hall problem | 39.5 s | sparks debate in the comments |
nueves.mp4 |
0,999… = 1 | 38.8 s | sparks debate in the comments |
cumple.mp4 |
The birthday paradox | 38.6 s | surprising fact |
luna.mp4 |
Folding paper to the Moon | 38.4 s | surprising fact |
japones.mp4 |
Multiplying with lines | 38.8 s | useful, gets saved and shared |
benford.mp4 |
Benford's law (spotting fraud) | 42.8 s | surprising fact + useful |
hilbert.mp4 |
The infinite hotel | 37.1 s | sparks debate in the comments |
reuleaux.mp4 |
Why manhole covers are round | 36.0 s | satisfying to watch |
regla72.mp4 |
The rule of 72 | 35.5 s | useful, gets saved |
buffon.mp4 |
Getting π by dropping needles | 36.7 s | mathematical beauty |
collatz.mp4 |
The Collatz conjecture (3n+1) | 46.2 s | unsolved mystery |
simpson.mp4 |
Simpson's paradox | 53.0 s | debate + useful |
cuerda.mp4 |
A rope around the Earth | 45.4 s | surprising fact |
gabriel.mp4 |
Gabriel's horn | 40.5 s | mathematical beauty |
mobius.mp4 |
Möbius strip | 46.3 s | try it at home |
bayes.mp4 |
False positive (medical test) | 46.9 s | debate + useful |
cicloide.mp4 |
Brachistochrone: the fastest curve | 47.6 s | satisfying to watch |
dados.mp4 |
Non-transitive dice | 50.6 s | sparks debate in the comments |
bolapeluda.mp4 |
Hairy ball theorem (3D) | 41.9 s | satisfying to watch |
caos.mp4 |
Three double pendulums and 1 mm (3D) | 40.6 s | hypnotic, watched to the end |
diferencial.mp4 |
How a differential works (3D) | 44.1 s | useful, gets saved and shared |
galton.mp4 |
Galton board: 500 balls draw the bell curve (Blender + Godot) | 45.7 s | satisfying + question in the comments |
bloques.mp4 |
Harmonic stack: blocks overhanging the table (Blender + Godot) | 48.0 s | try it at home + surprising fact |
domino.mp4 |
Dominoes ×1.5: from 5 mm to the Aconcagua (Blender + Godot) | 66.7 s | exponential + question in the comments (over 1 min) |
Format: 1080×1920, 30 fps, H.264 + AAC 48 kHz stereo, −13.8 to −15.3 LUFS measured (TikTok's target is −14, so it neither lowers nor raises them), true peak below −1 dBTP. Burned-in subtitles: 80 % watch without sound.
Suggested post texts (in Spanish, as published)
montyhall — ¿Te quedás o cambiás? El 87% se equivoca 🚪
#matematica #montyhall #datocurioso #probabilidad #argentina
nueves — 0,999… es EXACTAMENTE 1. Peleame en los comentarios 🧮
#matematica #mindblown #datocurioso #numeros #viral
cumple — Con 23 personas ya hay 50% de que dos cumplan el mismo día 🎂
#paradoja #datocurioso #matematica #probabilidad #sabiasque
luna — Doblá un papel 42 veces y llegás a la Luna 🌙
#exponencial #datocurioso #ciencia #mindblown #sabiasque
japones — Así multiplican en Japón, sin tablas ✏️
#trucosmatematicos #japon #escuela #tarea #aprendeentiktok
benford — Los números inventados se notan. Así agarran fraudes 🕵️
#benford #fraude #estadistica #datocurioso #sabiasque
hilbert — Un hotel lleno con infinitas habitaciones… y entran infinitos más 🏨
#infinito #matematica #paradoja #mindblown #hilbert
reuleaux — Por qué las tapas de alcantarilla son redondas 🛞
#datocurioso #geometria #ingenieria #sabiasque #reuleaux
regla72 — La cuenta que te dice cuándo se duplica tu plata 💸
#finanzaspersonales #inversiones #interescompuesto #inflacion #argentina
buffon — Tiro agujas al piso y sale π. Sin ningún círculo 🎲
#pi #matematica #azar #datocurioso #mindblown
collatz — Elegí cualquier número. Nadie pudo demostrar por qué siempre cae al 1 🌀
#collatz #matematica #misterio #datocurioso #mindblown
simpson — Gana en los dos grupos y pierde en el total. Así te miente un promedio 📊
#estadistica #paradoja #datos #sabiasque #matematica
cuerda — Le agrego 1 metro a una cuerda que rodea la Tierra. ¿Cuánto se levanta? 🌍
#datocurioso #matematica #mindblown #sabiasque #ciencia
gabriel — Lo podés llenar de pintura, pero no lo podés pintar 🎺
#infinito #matematica #paradoja #calculo #mindblown
mobius — Cortá esta cinta al medio y NO van a salir dos ✂️📄
#mobius #topologia #experimento #hacelovos #datocurioso
bayes — El test acierta el 99% y te dio positivo. La chance de estar enfermo es 9% 🧪
#probabilidad #estadistica #salud #sabiasque #bayes
cicloide — La recta es el camino más corto… y pierde la carrera 🎢
#fisica #matematica #satisfying #mindblown #cicloide
dados — Elegí el dado que quieras. Elijo después y te gano igual 🎲
#probabilidad #dados #juegos #datocurioso #matematica
bolapeluda — No podés peinar una esfera sin dejar un remolino. Es un teorema 🌀
#topologia #matematica #3d #datocurioso #mindblown
caos — Tres péndulos idénticos salvo 1 milímetro. A los 5 segundos no se parecen en nada 🎲
#caos #fisica #simulacion #mindblown #efectomariposa
diferencial — Por qué tu auto puede doblar sin que una rueda patine ⚙️
#mecanica #autos #ingenieria #sabiasque #comofunciona
galton — 500 bolitas, cada una al azar… y todas juntas dibujan una campana 🔔
#probabilidad #estadistica #galton #satisfying #matematica
bloques — Apilé bloques hasta que el de arriba quedó en el aire. ¿Hasta dónde llegás vos? 📚
#fisica #experimento #matematica #paraHacerEnCasa #sabiasque
domino — Una ficha de 5 mm puede tirar un edificio. ¿En qué ficha se cae tu casa? 🏠
#domino #fisica #exponencial #satisfying #sabiasque
Post one a day at the same time, and rotate the three kinds:
- debate (comments, which push reach the most): montyhall, nueves, hilbert, simpson, cuerda, dados
- useful (saved and shared): japones, regla72, benford, bayes, diferencial, bloques
- spectacle (watched to the end): reuleaux, buffon, mobius, gabriel, collatz, luna, cumple, cicloide, bolapeluda, caos, galton, domino
The 3D ones (bolapeluda, caos, diferencial, galton, bloques, domino)
are made in Blender: they break the visual monotony of Manim without changing the
channel style, because the UI layer (chip, bar and subtitles) is the same.
mobius is the only one with a physical call to action ("do it with a sheet of
paper"): those tend to bring response videos, which count double.
Blender + Godot (galton, bloques, domino)
Blender designs and Godot computes the physics:
MODE=sim blender -b -P blender/<name>.pybuilds the scene and exports only the physical objects to glTF. The name prefix tells Godot what each one is (static_static mesh,box_static box,gate_static and removed by an event,ball_rigid sphere,block_rigid box). Then it runsgodot --headless --fixed-fps 240 -- godot/work/<name>.json.godot/sim.gdloads the glb at runtime, creates the bodies, applies the script events (release, remove the gate) at the exact second of the timeline and records each body's pose at 30 fps in Blender coordinates (godot/work/<name>.bin).blender -b -P blender/<name>.pyreads those poses back and renders with the real materials (procedural wood, brass, steel).
None of the motion is animated by hand. Some numbers that come out of the simulation:
- galton: bins
[0 1 7 29 70 104 96 83 65 35 10 0 0](standard deviation of 1.82 bins; the ideal binomial for 12 rows gives 1.73). The middle column holds 96: that is the answer to the closing question. - bloques: the stack of 5 with harmonic shifts ×0.95 holds with the top block entirely in the air (8.5 % of a block). The tower of 16 does not move until one more block is put on its top left end: 10 of 17 fall.
Two tweaks Godot needed:
- galton: linear damping of 6/s, acting as friction against the glass. Without it the balls pick up too much sideways speed and the distribution comes out flat.
- bloques: physics at 1920 Hz and the world ×5 (
"hz"and"scale"in the config). Jolt tolerates 2 cm of penetration and at 240 Hz the tower of 16 sank and toppled on its own.
The prefixes also set the order Godot creates the bodies in (Blender exports
alphabetically), and in a chaotic scene that order changes the result: keep new
prefixes in the same alphabetical order (ball_ < box_ < gate_ < static_).
On an Optimus laptop EEVEE uses the Intel GPU unless started with
__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia, which is 3.6 times
faster (1.5 s/frame versus 5.5 on galton).
Regenerating
conda run -n manim python scripts/gen_audio.py montyhall # voice + check
conda run -n manim manim render -v ERROR --disable_caching \
--format=mp4 -o montyhall scenes/v1_montyhall.py MontyHall
conda run -n manim python scripts/build_video.py montyhall # music + mix + mux
conda run -n manim python scripts/verify_final.py montyhall # QA on the final MP4
DRAFT=1 before rendering drops to 540×960 to iterate fast.
Layout
scripts/narration.py scripts: (text that is spoken, on-screen subtitle)
scripts/gen_audio.py synthesizes each sentence and checks it by transcribing with Whisper
scripts/numspell.py turns "99,9%" into "noventa y nueve coma nueve por ciento"
so the transcription can be compared with the script
scripts/regen.py regenerates one sentence trying 5 seeds
scripts/repick.py picks the best take already synthesized again
scripts/verify.py QA: transcribes every WAV and flags doubtful ones
scripts/verify_final.py final QA: extracts the audio of the mixed MP4 and transcribes it,
which proves the voice is intelligible over the piano
scripts/music.py piano bed (original progression) + ducking under the voice
scripts/build_video.py builds the track following the timeline and muxes it onto the video
scenes/tiktok.py base: 9:16, subtitles, progress bar, "beats"
scenes/v*.py one scene per video (Manim)
scenes/overlay.py UI layer for the 3D videos: the same bar, chip and subtitles,
rendered with alpha
blender/base.py base of the 3D scenes: palette, camera, primitives, timing
blender/*.py one scene per 3D video
godot/sim.gd generic rigid-body simulator fed by Blender
scripts/build_blender.py composites background + 3D + overlay + audio in one pass
brand/ channel logo and avatar (make_logo.py)
The 3D videos
Rendered headless with Blender 5.2 and EEVEE:
conda run -n manim manim -s --disable_caching -o fondo scenes/overlay.py Backdrop # dot pattern
NAME=caos CHIP=Caos CHIPCOL=rosa conda run -n manim manim --transparent \
--disable_caching -o caos scenes/overlay.py Overlay # UI layer + timeline
/path/to/blender -b --factory-startup --python blender/caos.py # PNG sequence with alpha
conda run -n manim python scripts/build_blender.py caos # compositing + audio
Decisions that paid for themselves:
- No keyframes. The render walks the frames one by one and calls
refresh(f)before each one, which places everything. That way physics is computed exactly on every frame, skipped frames can be rendered to check the framing, and there is no fighting with Blender 5.x's layered actions API. - The background is not in the 3D scene. Blender renders with
film_transparentand the dotted background is composited underneath with ffmpeg, so it is pixel for pixel the same as in the 18 Manim videos. - Metal needs something to reflect.
studio_world()puts a horizon gradient in the world: it is not visible (the film is transparent) but it is the only thing that separates steel from grey plastic. Without it, a metallic material looks flat.
blender/base.py generates the mechanical parts in code:
| Function | What it does |
|---|---|
bevel_gear |
Bevel gear with a cone half-angle and spiral teeth. Two mesh at 90° when their γ add up to 90: 45+45 for the differential ones, 72+18 for ring gear and pinion (and there the tooth ratio is fixed by sin72/sin18 = 3.07, which is why they are 43 and 14) |
full_wheel |
Tire with lugs and grooves + alloy rim with spokes + brake disc |
ring_sector |
Curved wall: two of these and two flanges make a differential housing with windows, which is what lets you see the gears inside |
flat_ring, screws |
Flanges and bolt circles |
The intermediate PNGs stay in render/<name>/ (about 4 GB for three videos). They
allow recompositing with other audio or subtitles without rendering again, which
takes 30 to 55 minutes per video; if they are not needed, delete them.
SAMPLES=8 lowers the EEVEE samples (16 by default, 64 stock: that is 10 s/frame
on a GTX 1050 and 1.6 s/frame with 8). EVERY=55 renders one frame out of every
55 to build a contact sheet without waiting for the whole render.
Sync is not estimated: the scene writes out/<name>_timeline.json with the real
time of each block and the audio is placed at those exact marks.
Environment variables
| Variable | What for |
|---|---|
PROJECT |
project root (defaults to the repository directory) |
MODE=sim |
in the Godot scenes: recompute the physics instead of rendering |
SAMPLES, PCT |
EEVEE samples and resolution percentage |
FROM_FRAME, TO_FRAME, EVERY, RESUME |
which frames to render |
QWENTTS_DIR, VOICE_REF |
qwentts.cpp and the reference voice for the narration |
PIANO_DIR |
keysound piano samples |
The older Spanish names (PROY, MODO, MUESTRAS, DESDE, HASTA, SALTO,
SEGUIR, PRUEBA) are still accepted.
Voice
The repository does not include the cloned voice. VOICE_REF is the path prefix
of its three files (.spk, .rvq and .txt), extracted from a recording with
qwen-codec --talker.
What is not in git
Everything generated: render/ (intermediate PNGs), media/ (Manim output),
audio/ (narration), out/ (final videos and timelines), tmp/ and
godot/work/ (simulations). They are regenerated with the steps above.
About the music
The progressions (Am–F–C–G, Em–C–G–D, Am–Em–Dm–E, Cm–G#–A#–Gm, A–E–F#m–D, etc.) are original and played with loose piano samples, so the audio is yours and Content ID will not flag it. If you prefer a trending TikTok sound, upload anyway and add it from the app: the voice is already mixed at −14 LUFS and stays intelligible over it.
License
MIT