Este pipeline coge una grabación de Minecraft de 13 GB y devuelve un vídeo de 30 minutos listo para subir: historia escrita por un LLM, narrada en español con voz elegida según el protagonista, subtítulos clavados palabra por palabra, intro animada, miniatura y decenas de shorts verticales. Sin que nadie toque nada. Y ahí está el problema: el modo de fallo de un sistema autónomo no es que pete, es que entregue un MP4 que parece terminado y esté roto de una forma que nadie ve. Nadie mira 30 minutos de salida. Empecé sin saber programar; lo levanté solo, con IA.
This pipeline takes a 13 GB Minecraft recording and returns a 30-minute video ready to upload: an LLM-written story, narrated in Spanish with the voice picked from the protagonist's gender, subtitles locked word by word, animated intro, thumbnail and dozens of vertical shorts. With nobody touching anything. And that's the problem: an autonomous system's failure mode isn't crashing: it's shipping an MP4 that looks finished and is broken in a way nobody sees. Nobody watches 30 minutes of output. I started not knowing how to code; I built it solo, with AI.
Límite honesto: el uploader está construido —OAuth, subida reanudable y miniatura, con su pestaña en el dashboard— pero no se ha ejecutado nunca: necesita unas credenciales de Google Cloud propias que no están creadas. Por diseño sube en privado, solo el vídeo largo, y se dispara a mano desde una cola con el veredicto del auditor delante; nunca automáticamente al terminar una corrida. Lo digo aquí antes de que lo preguntes.Honest limit: the uploader is built —OAuth, resumable upload and thumbnail, with its own dashboard tab— but it has never been run: it needs its own Google Cloud credentials, which don't exist yet. By design it uploads privately, long-form video only, and is fired by hand from a queue with the auditor's verdict in front of you; never automatically at the end of a run. Saying it up front.
El producto es un vídeo, así que la prueba también. Una corrida real: ingesta → historia → voz → alineación → subtítulos → intro animada → miniatura → shorts verticales. Lo que se ve en pantalla es lo fácil de enseñar; lo que sostiene la calidad son dos parámetros que solo aparecen cuando mides la salida.
The product is a video, so is the proof. A real run: ingest → story → voice → alignment → subtitles → animated intro → thumbnail → vertical shorts. What you see on screen is the easy part to show; what holds the quality up are two parameters that only surface when you measure the output.
Guion sugerido: input/ con la grabación cruda → detección de hotbar tirando pausa y escritorio → pool/ (1.120 MB → 296 MB en la validación E2E) → historia por bloques → voz + alineación forzada → el ASS sobre el gameplay → intro con rebote elástico y woosh → miniatura → los shorts verticales, cada uno con historia distinta.Suggested script: input/ with the raw recording → hotbar detection dropping pause and desktop → pool/ (1,120 MB → 296 MB in the E2E validation) → story in blocks → voice + forced alignment → the ASS over the gameplay → intro with elastic bounce and woosh → thumbnail → the vertical shorts, each with a different story.
Posición y PlayRes son un par indivisible. Cambiar uno sin el otro no rompe nada visible en el log: produce un vídeo entero con el texto estirado. Lo aprendí en la versión vertical, que es exactamente la que nadie revisa.
Position and PlayRes are one indivisible pair. Changing one without the other breaks nothing visible in the log: it produces a whole video with stretched text. I learned that on the vertical variant, precisely the one nobody reviews.
Por eso los invariantes que viven en dos sitios están escritos como tales en las reglas del repo, y tocar uno obliga a revisar su gemelo.
That's why invariants living in two places are written down as such in the repo's rules, and touching one forces you to review its twin.
Durante meses los subtítulos iban por detrás de la voz. No lo bastante para que saltara a la vista, lo suficiente para que el vídeo se sintiera mal sin saber por qué. El pipeline no daba ningún error: producía MP4 perfectos. Dale al play y mira las dos pistas de abajo: la roja es el algoritmo anterior.
For months the subtitles ran behind the voice. Not enough to jump out at you, enough for the video to feel wrong without knowing why. The pipeline threw no error: it produced perfect MP4s. Hit play and watch the two tracks below: the red one is the old algorithm.
Los dos son medidas reales de objetos distintos, y publico las dos: 0,153 s es la primera producción real de 30 minutos; 0,072 s, el fixture de 3 minutos con el que corre el gate. Todo contra una transcripción independiente del audio, no contra el propio texto. Esa medición, la que me deja peor, es la que destapó el bug de anclaje, ya corregido: el fixture post-fix da 0,043 s. Un solo número aquí habría sido el bonito.Both are real measurements of different objects, and I publish both: 0.153 s is the first real 30-minute production; 0.072 s, the 3-minute fixture the gate runs on. All against an independent transcript of the audio, not the source text. That measurement, the one that makes me look worse, is what exposed the anchoring bug, now fixed: the post-fix fixture reads 0.043 s. A single number here would have been the pretty one.
El máximo importa más que la media: es el fotograma en el que el espectador nota que algo va mal. Y es el único umbral que el baseline del 10-ago no cumple (objetivo ≤0,30 s): son 0,400 s en 1 palabra de 422, el monosílabo «Y». Está escrito en el propio data/eval/2026-08-10.json.The max matters more than the mean: it's the frame where the viewer notices something is off. It is also the one threshold the 10-Aug baseline fails (target ≤0.30 s): 0.400 s on 1 word out of 422, the monosyllable "Y". It is written in data/eval/2026-08-10.json itself.
El signo es el objetivo real: el subtítulo debe ir delante de la voz, nunca detrás.The sign is the real target: the subtitle must run ahead of the voice, never behind.
Durante meses se afinaron parámetros asumiendo que el reparto proporcional era correcto. No lo era, y ninguna constante iba a arreglarlo: había que cambiar el mecanismo.
La lección que me llevo no es sobre subtítulos. Es que antes de optimizar un enfoque hay que comprobar que el enfoque es el correcto. Medir dónde está el problema es más barato que optimizar donde no está.
Detalle que cuesta un vídeo entero: al mover start hay que preservar la duración y recalcular end. Si no, el subtítulo se congela en pantalla: otro fallo perfectamente silencioso.
El vídeo se reproducía bien. Lo que estaba roto era invisible salvo midiendo.
For months parameters were tuned assuming the proportional split was correct. It wasn't, and no constant was going to fix it: the mechanism had to change.
The lesson isn't about subtitles. It's that before optimizing an approach you must check the approach is the right one. Measuring where the problem is costs less than optimizing where it isn't.
A detail that costs a whole video: when moving start you must preserve the duration and recompute end. Otherwise the subtitle freezes on screen: another perfectly silent failure.
The video played fine. What was broken was invisible unless you measured it.
Esta es la regla más cara del proyecto, y la pagué tres veces: una garantía escrita en prosa dentro de un prompt no está garantizada. El modelo la cumple casi siempre, y «casi siempre» en un sistema autónomo significa que el fallo llega a la salida sin que nadie lo vea. Lo que importa no es que lo diga el prompt: es quién lo comprueba después.
This is the project's most expensive rule, and I paid for it three times: a guarantee written in prose inside a prompt is not guaranteed. The model complies almost always, and "almost always" in an autonomous system means the failure reaches the output unseen. What matters isn't that the prompt says it: it's who checks afterwards.
Sin comas, el TTS se inventa dónde respirar: pausas de 0,44 s en mitad de una idea.Without commas the TTS invents where to breathe: 0.44 s pauses mid-idea.
La intro animada dura exactamente lo que el narrador tarda en decir el título. Si no coincide, la intro se corta a media frase.The animated intro lasts exactly as long as the narrator takes to say the title. If it doesn't match, the intro cuts mid-sentence.
Cada short es una llamada independiente con el mismo prompt. El modelo no podía saber cuáles eran «las otras».Each short is an independent call with the same prompt. The model could not know which "the others" were.
El corolario práctico, y es transferible a cualquier producto con un LLM dentro: toda salida de modelo que alimente algo irreversible pasa por un validador. Aquí «irreversible» es la miniatura y la intro de un vídeo que se sube.
Y su reverso, que también aplico: cuando una decisión exige un juicio que el modelo no puede dar de forma fiable, no le pido su mejor intento, porque su mejor intento produce ruido plausible, que es peor que un hueco: nadie lo detecta.
Una garantía prometida en prosa no está garantizada hasta que un if la fuerza.
The practical corollary, transferable to any product with an LLM inside: every model output feeding something irreversible goes through a validator. Here "irreversible" is the thumbnail and intro of a video that gets uploaded.
And its flip side, which I also apply: when a decision needs judgment the model cannot reliably provide, I don't ask for its best attempt, because its best attempt produces plausible noise, which is worse than a gap: nobody detects it.
A guarantee promised in prose isn't guaranteed until an if enforces it.
Los cuatro bugs graves de este repo produjeron vídeos completos y reproducibles. Ninguno se habría cazado leyendo el código, y ninguno disparó una alarma. Lo que los delató fue medir la salida. Esta tabla es, para mí, el activo del proyecto: la diferencia entre la señal fácil y el dato.
All four serious bugs in this repo produced complete, playable videos. None would have been caught by reading the code, and none raised an alarm. What exposed them was measuring the output. This table is, to me, the project's real asset: the gap between the easy signal and the datum.
| El falloThe failure | Lo que decía la señal fácilWhat the easy signal said | Lo que dijo la mediciónWhat the measurement said | La defensa que existe hoyThe defense that exists today |
|---|---|---|---|
| Demuxer concatconcat demuxerrutas relativas en la lista, resueltas respecto al directorio del fichero y no al cwdrelative paths in the list, resolved against the file's directory, not the cwd | «la ingesta terminó»"ingest finished" | nunca funcionó. La única corrida real era 95% gameplay y tomaba el atajo de -ss/-toit never worked. The only real run was 95% gameplay and took the -ss/-to shortcut | rutas absolutasabsolute paths+ un segmento que falla ya no entra en la lista+ a failed segment no longer enters the list |
| Subtítulos por detrásSubtitles running latereparto proporcional sobre una ventana que incluye los silenciosproportional split over a window that includes the silences | «los subtítulos salen»"the subtitles show up" | +0,435 s de sesgo medio · 1,064 s de máximomean bias · 1.064 s max | anclas durashard anchorsy el sincronismo es la métrica primaria del gateand sync is the gate's primary metric |
| Shorts clonadosCloned shortsN llamadas independientes con un prompt idénticoN independent calls with an identical prompt | «se generaron 4 shorts»"4 shorts were generated" | los 4 con el mismo argumento; solo cambiaba el finalall 4 with the same plot; only the ending changed | avoid[]+ prohibido validar con --no-shorts+ validating with --no-shorts is banned |
| Comas del promptThe prompt's commasuna regla de estilo que el modelo cumplía a ratosa style rule the model followed sometimes | «la regla está en el prompt»"the rule is in the prompt" | 167 · 129 · 0 · 0 en 4 generacionesacross 4 generations | impuesto en códigoenforced in codecon un invariante: no cambia el nº de palabraswith an invariant: it never changes the word count |
| Y una trampa de mediciónAnd a measurement trapun fixture de sincronismo construido repitiendo el mismo párrafo 4 vecesa sync fixture built by repeating the same paragraph 4 times | «2,525 s de error, está roto»"2.525 s of error, it's broken" | el mismo valor exacto en 3 de 5 repeticiones → el emparejador enganchaba la copia equivocadathe exact same value in 3 of 5 repeats → the matcher latched onto the wrong copy | fixtures no repetidosnon-repeating fixturesun valor idéntico entre corridas es la firma del artefactoan identical value across runs is the artifact's signature |
La última fila es la que más me enseñó: desconfiar de mi propia medición. Un número que sale idéntico tres veces no es precisión, es un artefacto del test. Si la herramienta puede mentir, el dato tampoco vale.The last row taught me the most: distrust my own measurement. A number that comes out identical three times isn't precision, it's a test artifact. If the tool can lie, the datum is worthless too.
El proyecto corre a 0 €, y por eso mismo el recurso escaso es otro: 1.000 peticiones al día en OpenRouter y la cuota diaria de la YouTube Data API. Eso no es un detalle de facturación: es una restricción de arquitectura. Durante meses mi propia documentación dijo «50/día», y tres auditorías adversariales repitieron el dato falso porque estaba escrito en un .md, hasta que se consultó la API. Está en mi ledger como [DOC-01]: la prosa no es prueba, ni siquiera la mía. Mueve el deslizador: la fórmula es la misma que corre en main.py.
The project runs at €0, and precisely because of that the scarce resource is different: 1,000 requests per day on OpenRouter and the daily quota of the YouTube Data API. That isn't a billing detail: it's an architectural constraint. For months my own documentation said «50/day», and three adversarial audits repeated the false figure because it was written in a .md, until someone queried the API. It's in my ledger as [DOC-01]: prose isn't proof, not even mine. Move the slider: the formula is the one running in main.py.
Los últimos vídeos de un canal se leen por su playlist de subidas, no con search.list. El presupuesto de búsquedas es el recurso más escaso de la API, y así medir 40 canales no gasta ni una.A channel's latest videos are read via its uploads playlist, not search.list. The search budget is the API's scarcest resource, and this way measuring 40 channels spends none of it.
De las cuales 400 son las 4 búsquedas de descubrimiento: el gasto dominante. Por eso las keywords rotan entre escaneos y el contador se corta antes de que Google devuelva un 403.Of which 400 are the 4 discovery searches: the dominant cost. That's why keywords rotate between scans and the counter cuts off before Google returns a 403.
Si la cuota se acaba a mitad, el canal se deja intacto para la próxima corrida. Antes quedaba marcado «sin vídeos recientes» de forma permanente y la lista se vaciaba sola.If quota runs out mid-scan, the channel is left untouched for the next run. It used to be permanently flagged "no recent videos" and the list emptied itself.
No todas las etapas merecen la misma ceremonia. Las marcadas como superficie sensible son aquellas donde un cambio puede romper la salida sin romper la ejecución, y esas no se cierran sin medir. Pasa el ratón por una etapa.
Not every stage deserves the same ceremony. The ones marked sensitive surface are those where a change can break the output without breaking the run, and those don't close without measuring. Hover a stage.
Los shorts verticales comparten con el vídeo largo la intro animada, el woosh, la alineación y los subtítulos, pero con PlayRes, posición y velocidad distintas. Un arreglo en uno casi siempre tiene gemelo en el otro, y el de los shorts es el que nadie revisa: ahí vivieron los 4 shorts idénticos. Por eso la regla del repo no dice «arregla la función», dice «enumera las dos etapas», y correr con --no-shorts está prohibido para validar un cambio.
Vertical shorts share the animated intro, the woosh, the alignment and the subtitles with the long video, but with different PlayRes, position and speed. A fix in one almost always has a twin in the other, and the shorts' one is what nobody reviews: that's where the 4 identical shorts lived. That's why the repo's rule doesn't say "fix the function", it says "enumerate both stages", and running with --no-shorts is banned for validating a change.
No le pido a la IA una respuesta. Un orquestador delega la implementación en agentes en paralelo, y después un panel escéptico ataca lo construido: bug-hunter localiza la causa raíz sin contexto previo, output-audit intenta demostrar que el vídeo está roto midiendo sus artefactos, y /eval corre la cadena entera contra un clip fijo y compara con el baseline. El orquestador re-deriva cada veredicto ejecutando, nunca por informe. Yo juzgo lo que ninguna capa puede juzgar.
I don't ask the AI for an answer. An orchestrator delegates implementation to parallel agents, then a skeptical panel attacks what was built: bug-hunter finds the root cause with no prior context, output-audit tries to prove the video is broken by measuring its artifacts, and /eval runs the whole chain against a fixed clip and compares to the baseline. The orchestrator re-derives every verdict by executing, never by report. I judge what no layer can judge.
Pellizca para acercar y leer el detalle ↔Pinch to zoom in and read the detail ↔
El gate no avisa: bloquea. Un cambio en superficie sensible que empeore el sincronismo respecto al baseline no se cierra: no es una advertencia, es un no. Y la última capa siempre soy yo: ninguna medición dice si una historia engancha, y fingir lo contrario sería el mismo error que confiar en que el modelo obedezca el prompt.
The gate doesn't warn: it blocks. A sensitive-surface change that worsens sync against the baseline does not close: it isn't a warning, it's a no. And the last layer is always me: no measurement tells you whether a story hooks, and pretending otherwise would be the same mistake as trusting the model to obey the prompt.
Ningún «el subagente reportó que OK» cierra nada. Corro yo el ffprobe, la medición de alineación y el /eval, y pego la salida real en el siguiente paso. Un informe no es evidencia; una salida sí.No "the subagent reported OK" closes anything. I run ffprobe, the alignment measurement and /eval myself, and paste the real output into the next step. A report isn't evidence; output is.
Un gate nuevo es superficie nueva: antes de darlo por bueno le paso el conjunto vacío, el dato ausente y el valor desconocido. Y verifico sobre el caso que el módulo existe para cubrir: una grabación CON pausas, no el clip fácil que toma el atajo y pasa siempre.A new gate is new surface: before trusting it I feed it the empty set, the missing datum and the unknown value. And I verify against the case the module exists to cover: a recording WITH pauses, not the easy clip that takes the shortcut and always passes.
El resto del método —la refutación asimétrica, el ledger que solo asciende a regla con freno— es el mismo en los cinco sistemas y está contado una vez: en el índice.The rest of the method —asymmetric refutation, the ledger that only promotes to a rule with a brake— is the same across all five systems and is told once: on the index.
Si buscas a alguien para applied-AI, automatización o forward-deployed engineering: que construya sistemas autónomos y las medidas que impiden que se engañen a sí mismos: hablemos.
If you're looking for someone in applied-AI, automation or forward-deployed engineering: someone who builds autonomous systems and the measurements that stop them fooling themselves: let's talk.