¿Qué hace stickman-explainer?
Convierte un guion breve en un archivo mp4 completo de 1080×1920. Escribes un guion gráfico JSON; scripts/render.py dibuja cada fotograma (líneas de tinta temblorosas, efecto de línea hirviente a 8 fps, revelados con fundido/barrido/pop) y lo codifica con ffmpeg.
assets/example_storyboard.json es solo un ejemplo de sintaxis (3 escenas); no copies su diseño ni los accesorios de los personajes como plantilla.
Valores predeterminados del estilo visual
- Usa un lienzo 9:16 (1080×1920, 30 fps). Mantén cada escena en una sola idea, normalmente de 2,5 a 4 segundos, con subtítulos cortos dentro de las áreas seguras de la plataforma.
- Usa papel cálido (
#f3ebdd), tinta oscura (#2b2723), y acento rojo ladrillo semántico (#b8372d). El dorado sol (#d89d48) es opcional solo para el motivo solar. Mantén el resto plano: sin degradados, sombras ni efectos 3D. - Dibuja figuras simples de tinta negra con rostros claros y abundante espacio negativo. No agregues cascos, coronas, capas, ropa roja u otros accesorios a menos que la historia o el usuario lo pida explícitamente. No infieras vestimenta a partir de un nombre histórico.
- Mantén las escenas legibles: como máximo 2 personajes y 5 íconos pequeños. Haz que el sujeto principal domine; evita la superposición de subtítulos y accesorios. Usa no más de 2 líneas de subtítulos.
- Haz reconocibles los accesorios complejos. Dibuja un nudo con bucles de cruce distintos, un centro apretado y extremos de cuerda visibles. Dale a una espada un mango claro, una guarda y una hoja puntiaguda. Después de un corte, deja un espacio visible entre los dos extremos de la cuerda.
- Cuando se disponga de temporizaciones de narración, haz que el énfasis rojo del subtítulo siga la palabra que se está hablando en ese momento a lo largo de toda la narración. Usa un intervalo
karaokepara cada palabra del subtítulo separada por espacios en blanco; las palabras no activas mantienen el color de texto normal. - Prefiere diseños asimétricos y deja espacio negativo deliberadamente. Las líneas de suelo, soles, tarjetas de título, burbujas de pensamiento y otros motivos son opcionales; elígelos solo cuando apoyen la escena.
Flujo de trabajo
- Guion → escenas. Divide la historia en escenas de una oración (≤ 6 palabras de subtítulo, 2,5–4 s cada una). Patrón: tarjeta de título → gancho/conflicto → revelación concreta. Cada escena agrega exactamente una nueva información. Cuando se proporciona narración, alinea los límites de las escenas con las oraciones habladas.
- Elige un elemento visual para cada oración. Una metáfora por escena: personaje + accesorio, burbuja de pensamiento o una fila de íconos (X = "no tiene"). Mantén ≤ 2 personajes y ≤ 5 íconos.
- Escribe el guion gráfico (esquema a continuación). Con temporizaciones de narración a nivel de palabra, agrega un intervalo
karaokepor cada palabra del subtítulo separada por espacios en blanco para que el acento rojo siga la palabra que se está hablando. Usa**palabra**solo para un resaltado estático cuando no se disponga de temporización de palabra; no combines marcas estáticas conkaraoke. - Revisa primero los fotogramas fijos (rápido, ~2 s/escena):
python scripts/render.py story.json --stills out/stills→ abre cadascene_NN.png, corrige superposición/ubicación, repite. - Video borrador:
python scripts/render.py story.json out/draft.mp4 --draft(sin supersampling, 12 fps). - Final:
python scripts/render.py story.json out/final.mp4 [--audio voiceover.mp3]. Las duraciones de las escenas deben coincidir con las longitudes de las oraciones de la voz en off; mide el audio conffprobey establecedurationen consecuencia. Los intervalos de palabras son segundos desde el inicio de su escena y deben provenir de la alineación de la narración o de una escucha cuidadosa. Verifica que cada palabra roja aparezca con su audio hablado, no antes ni después.
Mensajes de error y validación
render.py valida el guion gráfico antes de dibujar: type/pose/face/kind/font desconocido, campo obligatorio faltante, id repetido en la misma escena, duration <= 0, ruta lines de un solo punto, resolución impar (no par) e id de carry inexistente se rechazan con un mensaje claro, no con traceback. Se emite una advertencia para elementos que nunca serán visibles (at después del final de la escena). Si el archivo de audio es más corto que el video, el final se rellena con silencio; si es más largo, se recorta; la duración del video nunca cambia.
Esquema del guion gráfico
{"meta": {"width":1080,"height":1920,"fps":30,"boil_fps":8,"transition":0.18,
"palette":{"accent":"#b8372d"}, "audio":null, "font_dirs":[]},
"scenes":[{"duration":3.4, "caption":"Büyük **İskender**'di.",
"caption_at":0.5, "caption_y":null, "elements":[ ... ]}]}
Para un énfasis de palabra que sigue el habla, proporciona un array karaoke con un objeto { "start": segundos, "end": segundos } por cada palabra separada por espacios en blanco en caption. Los tiempos son locales a la escena; el énfasis rojo cambia en cada intervalo. El subtítulo no debe contener marcas **...** cuando karaoke esté presente.
{"duration":2.8, "caption":"Düğümü tek hamlede kesti.", "caption_at":0.35,
"karaoke":[{"start":0.35,"end":0.76},{"start":0.76,"end":1.02},
{"start":1.02,"end":1.53},{"start":1.53,"end":2.15}],
"elements":[ ... ]}
Cada elemento: type, id opcional (para carry), at (s, tiempo de aparición), dur (s de revelación), anim = fade | wipe | rise | pop, until/out_dur (desvanecimiento).
Las posiciones x,x2,y,ground ≤ 2 se leen como fracciones de ancho/alto, de lo contrario píxeles. Los tamaños son siempre píxeles.
| tipo | campos clave |
|---|---|
ground | y (obligatorio); x, x2 opcional, si no se da, ~%2–%98 del lienzo — línea de base temblorosa |
sun | x,y,r,rays |
text | text (marcas **accent**), x,y,size,font = sans/sans_bold/sans_light/serif, tracking, caps, color, max_w, max_lines (predeterminado 3), rules (doble línea para tarjeta de título; ancho y espacio automáticos según el texto) |
figure | x,ground,h,head_r, pose = stand/arms_open/point/wave/think/shrug/sit, dir (±1), face = neutral/smile/sad/surprised/angry, accessory opcional = helmet/crown/hat/glasses, cape opcional, look |
prop | kind = barrel/bowl/bucket/house/book/box/triangle/coins/tree, x,y (esquina superior izquierda), w,h, cross (X de acento), flip |
thought | x,y,w,h, tail_to [px,py] (posición de la cabeza), inner: lista de elementos en px local de la burbuja |
lines | paths (lista de listas de puntos, px local), closed, smooth, color, width — válvula de escape para cualquier forma personalizada |
Continuidad entre escenas (carry)
Para llevar un elemento de una escena a la siguiente, asigna un id al elemento y llámalo con carry en la siguiente escena. El elemento llevado comienza con la misma vibración de línea (misma semilla), por lo que aparece como "el mismo dibujo" aunque la escena cambie; la transición de nueva escena por defecto es un corte (sin crossfade).
"carry": [
"ground",
{"id": "sun", "set": {"r": 122}},
{"id": "alex", "set": {"x": 120, "face": "smile"}, "tween": {"at": 0.1, "dur": 0.9}},
{"id": "diog", "until": 0.1, "out_dur": 0.5}
]
Las líneas se procesan en orden: "ground" se lleva tal cual · sun se lleva con campo modificado (el cambio es instantáneo al inicio de la escena) · alex cambia de campo y además desliza su posición durante un tiempo (tween) · diog se lleva pero se desvanece dentro de la escena.
"carry": "all"lleva todo ("carry_except": ["id", ...]excluye). Los subtítulos nunca se llevan.- Un elemento no llevado desaparece instantáneamente al inicio de la escena; para una desaparición suave, usa
until/out_dur. Un elemento que se desvanece conuntilno puede llevarse a la siguiente escena. tweensolo anima posición (x y y/ground); los cambios de tamaño (r,h,w) y apariencia (face,pose) se aplican instantáneamente al inicio de la escena.- Los elementos llevados se dibujan debajo de los nuevos elementos. Los
iddeben ser únicos entre escenas para los elementos que se llevarán. - Un elemento con el mismo
iden cada escena usa la misma semilla; para un elemento nuevo específico de una escena, no le asignesid.
Agrega nuevos accesorios/poses extendiendo PROPS / hands en render.py; mantén el mismo estilo de tinta (usa stroke(), nunca líneas simples).
Diseño: decide de nuevo para cada video
No hay coordenadas/plantillas fijas. Como el tema de cada video es diferente, deriva el diseño del contenido de la escena:
- Primero elige la idea visual única de esa oración (comparación, lista, sueño, viaje, número…), luego construye el diseño que se ajuste. El diseño puede cambiar entre escenas del mismo video.
- Deja espacio para el elemento importante: que el sujeto principal sea el más grande/más prominente; los elementos secundarios deben ser pequeños. Deja espacio vacío (espacio negativo) de forma deliberada.
- El tamaño, posición y número varían según el contenido (¿1 personaje o 2? ¿hilera de íconos, objeto grande único, mapa/línea de tiempo?). Si es necesario, dibuja formas personalizadas con
lines. - No agregues automáticamente vestimenta/tocado a figuras históricas. Las figuras se dibujan sin accesorios por defecto; solo agrega casco, corona, sombrero o capa si la narrativa o el usuario lo requiere explícitamente. No extiendas el color de acento como decoración en la ropa del personaje.
- En escenas de nudo y corte, configura el objeto de forma legible: dibuja la cuerda con varios bucles que se crucen claramente, muestra el nudo apretado en el centro y distingue los extremos. En una espada, el mango, la guarda y la hoja puntiaguda deben ser distinguibles. Después del momento del corte, deja los dos extremos de la cuerda cortada espaciados y claramente visibles; la espada y el nudo no deben quedar ocultos por los subtítulos. Dibuja formas especiales de cuerda/espada con
lines. - Motivos como línea de suelo, sol, tarjeta de título son opcionales; no los uses si no encajan con el tema (ej. interior, noche, concepto abstracto).
- Coloca los subtítulos en un lugar legible y alejado de la interfaz de la plataforma; si hay conflicto, ajusta con
caption_y. Verifica visualmente superposiciones y desbordes en los fotogramas fijos, no confíes solo en números. - Las coordenadas de dibujo se dan en píxeles (o proporción 0–2) en el lienzo de 1080×1920; planifica los valores desde cero para cada escena.
Errores comunes (Gotchas)
- Siempre revisa los fotogramas fijos antes de codificar; los elementos superpuestos y los rayos de sol recortados son los defectos habituales. Usa
--draftpara borrador y renderizado completo para detalle: los fotogramas que no cambian no se redibujan, un video de 10 s tarda aproximadamente 20 s en calidad completa. - El subtítulo ajusta líneas hasta el 86 % del ancho (máximo 3 líneas), pero si una palabra no cabe o excede 3 líneas, se reduce. Divide oraciones de más de 2 líneas en dos escenas. La posición predeterminada del subtítulo es solo un respaldo; si es necesario, proporciona
caption_yen la escena. - Fuentes: instala Poppins y Playfair Display (o Lora), o proporciona
--font-dir. Si no se encuentra ninguna fuente TrueType, el renderizador emite una advertencia y los caracteres turcos (İ ş ğ â) pueden faltar; verifica en los fotogramas fijos. Se escanean las carpetas de fuentes de Linux, macOS y Windows. - Mantén el color de acento semántico (sujeto, palabra clave, negación). No agregues más colores.
- Cuando
karaokeesté presente, el énfasis rojo del subtítulo sigue la narración palabra por palabra usando los intervalos proporcionados. No dejes una palabra clave roja fija en pantalla; renderiza las palabras no activas en el color de texto del subtítulo.