aboutsummaryrefslogtreecommitdiffstats

24 vertical TikTok videos — generated 100% locally

Español

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:

  1. MODE=sim blender -b -P blender/<name>.py builds 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 runs godot --headless --fixed-fps 240 -- godot/work/<name>.json.
  2. godot/sim.gd loads 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).
  3. blender -b -P blender/<name>.py reads 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_transparent and 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