From fafaebb051907a848a9406f9da19669c81a83a3b Mon Sep 17 00:00:00 2001 From: Elvis Claros Castro Date: Sat, 26 Sep 2026 20:50:41 -0300 Subject: Translate code, comments and logs to English; English README; configurable paths and env vars Identifiers, docstrings, comments and console messages are now in English. Narration, subtitles and on-screen text stay in Spanish (they are the video content). The Blender <-> Godot physics protocol uses English keys and body prefixes chosen to keep the original creation order, so cached simulations and renders stay bit-identical. The old Spanish environment variable names are still accepted. --- README.md | 317 +++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 181 insertions(+), 136 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index bcced52..31cb19d 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,49 @@ -# 24 videos verticales para TikTok — 100% generados localmente +# 24 vertical TikTok videos — generated 100% locally -Todo corre offline: **Manim** para la animación 2D, **Blender** (EEVEE, headless) para -las escenas 3D, **Godot** (motor de física Jolt, headless) para la física de -rígidos, **qwentts.cpp** (voz clonada rioplatense) para la locución y las -muestras de piano de `keysound` para la música. +[Español](README.es.md) -## Los videos (`out/`) +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. -| Archivo | Tema | Duración | Gancho | +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` | Problema de Monty Hall | 39,5 s | genera debate en comentarios | -| `nueves.mp4` | 0,999… = 1 | 38,8 s | genera debate en comentarios | -| `cumple.mp4` | Paradoja del cumpleaños | 38,6 s | dato que sorprende | -| `luna.mp4` | Doblar un papel hasta la Luna | 38,4 s | dato que sorprende | -| `japones.mp4` | Multiplicar con líneas | 38,8 s | útil, se guarda y se comparte | -| `benford.mp4` | Ley de Benford (detectar fraude) | 42,8 s | dato que sorprende + útil | -| `hilbert.mp4` | El hotel infinito | 37,1 s | genera debate en comentarios | -| `reuleaux.mp4` | Por qué las tapas son redondas | 36,0 s | satisfactorio de mirar | -| `regla72.mp4` | La regla del 72 | 35,5 s | útil, se guarda | -| `buffon.mp4` | Sacar π tirando agujas | 36,7 s | belleza matemática | -| `collatz.mp4` | Conjetura de Collatz (3n+1) | 46,2 s | misterio sin resolver | -| `simpson.mp4` | Paradoja de Simpson | 53,0 s | genera debate + útil | -| `cuerda.mp4` | Una cuerda alrededor de la Tierra | 45,4 s | dato que sorprende | -| `gabriel.mp4` | El cuerno de Gabriel | 40,5 s | belleza matemática | -| `mobius.mp4` | Cinta de Möbius | 46,3 s | para hacer en casa | -| `bayes.mp4` | Falso positivo (test médico) | 46,9 s | genera debate + útil | -| `cicloide.mp4` | Braquistócrona: la curva más rápida | 47,6 s | satisfactorio de mirar | -| `dados.mp4` | Dados no transitivos | 50,6 s | genera debate en comentarios | -| `bolapeluda.mp4` | Teorema de la bola peluda (3D) | 41,9 s | satisfactorio de mirar | -| `caos.mp4` | Tres péndulos dobles y 1 mm (3D) | 40,6 s | hipnótico, se mira entero | -| `diferencial.mp4` | Cómo funciona el diferencial (3D) | 44,1 s | útil, se guarda y se comparte | -| `galton.mp4` | Tablero de Galton: 500 bolitas forman la campana (Blender + Godot) | 45,7 s | satisfactorio + pregunta en comentarios | -| `bloques.mp4` | Pila armónica: bloques que sobresalen de la mesa (Blender + Godot) | 48,0 s | para hacer en casa + dato que sorprende | -| `domino.mp4` | Dominó ×1,5: de 5 mm al Aconcagua (Blender + Godot) | 66,7 s | exponencial + pregunta en comentarios (más de 1 min) | - -Formato: 1080×1920, 30 fps, H.264 + AAC 48 kHz estéreo, **−13,8 a −15,3 LUFS** -medidos (el objetivo de TikTok es −14, así no te lo bajan ni te lo suben), -pico real por debajo de −1 dBTP. -Subtítulos quemados: el 80 % mira sin audio. - -## Textos sugeridos para publicar +| `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` @@ -113,140 +117,181 @@ Subtítulos quemados: el 80 % mira sin audio. **domino** — Una ficha de 5 mm puede tirar un edificio. ¿En qué ficha se cae tu casa? 🏠 `#domino #fisica #exponencial #satisfying #sabiasque` -Subí uno por día, mismo horario, y alterná los tres tipos: +Post one a day at the same time, and rotate the three kinds: -- **debate** (comentarios, que es lo que más empuja el alcance): montyhall, nueves, - hilbert, simpson, cuerda, dados -- **útil** (se guarda y se comparte): japones, regla72, benford, bayes, diferencial, bloques -- **espectáculo** (se mira hasta el final): reuleaux, buffon, mobius, gabriel, collatz, +- **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 -Los tres últimos (`bolapeluda`, `caos`, `diferencial`) están hechos en Blender: sirven -para cortar la monotonía visual de Manim sin cambiar el estilo del canal, porque la -capa de interfaz (chip, barra y subtítulos) es la misma. +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` es el único con llamado a la acción físico ("hacelo con una hoja"): -esos suelen traer videos-respuesta, que valen doble. +`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`) +### Blender + Godot (`galton`, `bloques`, `domino`) -Blender diseña y Godot calcula la física: +Blender designs and Godot computes the physics: -1. `MODO=sim blender -b -P blender/.py` arma la escena y exporta a glTF solo - los objetos físicos. El prefijo del nombre dice qué es cada uno para Godot - (`fijo_` malla estática, `caja_` caja estática, `compuerta_` estática que se saca - con un evento, `bola_` esfera rígida, `bloque_` caja rígida). Después corre - `godot --headless --fixed-fps 240 -- godot/trabajo/.json`. -2. `godot/sim.gd` carga el glb en tiempo de ejecución, crea los cuerpos, aplica los - eventos del guion (soltar, sacar la compuerta) en el segundo exacto del timeline - y graba la pose de cada cuerpo a 30 fps en coordenadas de Blender - (`godot/trabajo/.bin`). -3. `blender -b -P blender/.py` lee esas poses y renderiza con los materiales - de verdad (madera procedural, latón, acero). +1. `MODE=sim blender -b -P blender/.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/.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/.bin`). +3. `blender -b -P blender/.py` reads those poses back and renders with the + real materials (procedural wood, brass, steel). -Nada del movimiento está animado a mano. Algunos números que salen de la simulación: +None of the motion is animated by hand. Some numbers that come out of the +simulation: -- **galton**: casilleros `[0 1 7 29 70 104 96 83 65 35 10 0 0]` (desvío de 1,82 - casilleros; el binomial ideal de 12 filas da 1,73). La columna del medio tiene - **96**: es la respuesta a la pregunta del final. -- **bloques**: la pila de 5 con corrimientos armónicos ×0,95 se sostiene con el de - arriba entero en el aire (8,5 % de bloque). La torre de 16 no se mueve hasta que - se le apoya un bloque más en la punta izquierda de arriba: se caen 10 de 17. +- **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. -Dos ajustes que hicieron falta en Godot: +Two tweaks Godot needed: -- **galton**: una amortiguación lineal de 6/s, que hace de roce contra el vidrio. - Sin ella las bolitas toman demasiada velocidad lateral y el reparto sale plano. -- **bloques**: física a 1920 Hz y mundo ×5 (`"hz"` y `"escala"` en el config). Jolt - tolera 2 cm de penetración y a 240 Hz la torre de 16 se hundía y se volcaba sola. +- **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. -Ojo: con la notebook Optimus, EEVEE usa la Intel salvo que se lance con -`__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia`, y así es 3,6 veces -más rápido (1,5 s/frame contra 5,5 en galton). +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_`). -## Cómo regenerar +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 ```bash -conda run -n manim python scripts/gen_audio.py montyhall # voz + verificación +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 # música + mezcla + mux -conda run -n manim python scripts/verify_final.py montyhall # QA sobre el MP4 final +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` antes del render baja a 540×960 para iterar rápido. +`DRAFT=1` before rendering drops to 540×960 to iterate fast. -## Cómo está armado +## Layout ``` -scripts/narration.py guiones: (texto que se locuta, subtítulo en pantalla) -scripts/gen_audio.py sintetiza cada frase y la verifica transcribiéndola con Whisper -scripts/numspell.py pasa "99,9%" a "noventa y nueve coma nueve por ciento" - para poder comparar la transcripción contra el guion -scripts/regen.py regenera una frase probando 5 semillas -scripts/repick.py reelige la mejor toma ya sintetizada -scripts/verify.py QA: transcribe todos los WAV y marca los dudosos -scripts/verify_final.py QA final: saca el audio del MP4 ya mezclado y lo transcribe, - así se prueba que la voz se entiende por encima del piano -scripts/music.py cama de piano (progresión propia) + ducking bajo la voz -scripts/build_video.py arma la pista siguiendo el timeline y la pega al video -scenes/tiktok.py base: 9:16, subtítulos, barra de progreso, "beats" -scenes/v*.py una escena por video (Manim) -scenes/overlay.py capa de interfaz para los videos 3D: la misma barra, el mismo - chip y los mismos subtítulos, renderizados con alfa -blender/base.py base de las escenas 3D: paleta, cámara, primitivas, tiempo -blender/*.py una escena por video 3D -scripts/build_blender.py compone fondo + 3D + overlay + audio en una sola pasada +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) ``` -### Los videos 3D +### The 3D videos -Se renderizan headless con Blender 5.2 y EEVEE: +Rendered headless with Blender 5.2 and EEVEE: ```bash -conda run -n manim manim -s --disable_caching -o fondo scenes/overlay.py Fondo # trama de puntos +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 # capa + timeline -blender -b --factory-startup --python blender/caos.py # secuencia PNG con alfa -conda run -n manim python scripts/build_blender.py caos # composicion + audio + --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 ``` -Dos decisiones que se pagaron solas: +Decisions that paid for themselves: -- **Sin keyframes.** El render recorre los frames uno por uno y antes de cada uno - llama a `actualizar(f)`, que coloca todo. Así la física se calcula exacta en cada - cuadro, se pueden renderizar frames salteados para revisar el encuadre, y no hay - que pelear con la API de acciones por capas de Blender 5.x. -- **El fondo no está en la escena 3D.** Blender renderiza con `film_transparent` y el - fondo punteado se compone abajo con ffmpeg, así es pixel por pixel el mismo que el - de los 18 videos de Manim. -- **El metal necesita algo que reflejar.** `mundo_estudio()` pone un gradiente de - horizonte en el mundo: no se ve (el film es transparente) pero es lo único que - separa el acero del plástico gris. Sin eso, un material metálico queda plano. +- **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` genera las piezas mecánicas por código: +`blender/base.py` generates the mechanical parts in code: -| Función | Qué hace | +| Function | What it does | |---|---| -| `engranaje_conico` | Engranaje cónico con semiángulo de cono y dientes en espiral. Dos engranan a 90° cuando sus γ suman 90: 45+45 para los del diferencial, 72+18 para corona y piñón (y ahí la relación de dientes queda fijada por sin72/sin18 = 3,07, que es por qué son 43 y 14) | -| `rueda_completa` | Neumático con tacos y canales + llanta de aleación con radios + disco de freno | -| `sector_anillo` | Pared curva: con dos de estas y dos bridas sale una caja de diferencial con ventanas, que es lo que deja ver los engranajes de adentro | -| `anillo_plano`, `tornillos` | Bridas y coronas de bulones | +| `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//` (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/_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 -Los PNG intermedios quedan en `render//` (unos 4 GB los tres). Sirven para -recomponer con otro audio o subtítulos sin volver a renderizar, que son entre 30 y -55 minutos por video; si no hacen falta, se borran. +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. -`MUESTRAS=8` baja las muestras de EEVEE (16 por defecto, 64 de fábrica: eso son -10 s/frame en una GTX 1050 y 1,6 s/frame con 8). `SALTO=55` renderiza un frame de -cada 55 para armar la hoja de contacto sin esperar el render entero. +## About the music -La sincronía no se estima: la escena escribe `out/_timeline.json` con el -tiempo real de cada bloque y el audio se coloca en esas marcas exactas. +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. -## Sobre la música +## License -Las progresiones (Am–F–C–G, Em–C–G–D, Am–Em–Dm–E, Cm–G#–A#–Gm, A–E–F#m–D, etc.) son propias y están tocadas con muestras -de piano sueltas, así que el audio es tuyo y no te lo va a marcar el Content ID. -Si preferís un audio en tendencia de TikTok, subilo igual y agregalo desde la app: -la voz ya está mezclada a −14 LUFS y se sigue entendiendo por encima. +MIT -- cgit v1.2.3