Serie de Cortometrajes de Queja en Boceto Blanco y Negro
Un video = un guion + un storyboard + una pista de audio + una portada + un conjunto de materiales de publicación. Todo se dibuja con el mismo conjunto de primitivas gráficas y fuentes, así que cada episodio de la serie tiene un estilo consistente y la portada coincide perfectamente con el video.
Contrato de Producción
Cada episodio debe entregar estos 5 grupos de archivos, sin excepción:
| Producto | Archivo | Especificaciones |
|---|---|---|
| Video con subtítulos | out/<título>_boceto_queja_con_subtítulos.mp4 | 1080×1440, 30 fps, H.264+AAC, ~68 s |
| Video sin subtítulos | out/<título>_boceto_queja_sin_subtítulos.mp4 | Idem, sin capa de subtítulos |
| Archivo de subtítulos | out/<título>.srt | Línea única ≤16 caracteres, tiempos ascendentes sin solapamiento |
| Portada 3:4 | out/<título>_portada.png | 1080×1440, mismo juego de líneas y fuentes que el video |
| Materiales para WeChat Channels | project_*/publish.md + publish.json | 3 títulos, 1 descripción, 5–8 hashtags, 1 comentario fijado |
Además, se entrega out/<título>_vista_previa.png (tira de 4 fotogramas clave) para que el usuario pueda ver un episodio completo de un vistazo.
Entorno
Python 3.10+, Pillow, NumPy, ffmpeg/ffprobe. Primero crear un entorno virtual, no tocar el Python del sistema:
python3 -m venv .venv && .venv/bin/pip install -r "$skill_dir/scripts/requirements.txt"
En macOS, ffmpeg a menudo no está en el PATH predeterminado; primero ejecuta export PATH="/opt/homebrew/bin:$PATH".
La locución utiliza mcode-tools (MiniMax TTS/ASR); se necesita que el skill mcode-tools-master esté en el PATH.
Si python3 da error You have not agreed to the Xcode license agreements (código de salida 69),
significa que el Python del sistema no está configurado. Usa otro intérprete para crear el entorno, por ejemplo
/opt/homebrew/bin/python3.13 -m venv .venv. No modifiques la licencia de Xcode.
Flujo de Trabajo
1. Definir el Tema y la Orientación de la Serie
Episodio único: basta con dar una dirección de humor. Múltiples episodios: primero asigna a cada episodio un enfoque de humor mutuamente excluyente; no uses el mismo tipo de broma con diferente apariencia.
- Corriente Autocrítica Absurda (cada vez se vuelve más disparatado)
- Giro en el Diálogo (dos personas conversan, la última frase lo cambia todo)
- Observación Fría (queja desde la distancia, humor contenido)
- Premisa Absurda (tomar en serio una premisa ridícula)
En una misma serie, al menos dos tipos diferentes de humor deben estar cerca. El lenguaje visual puede compartirse, pero el ritmo narrativo no debe ser idéntico.
2. Escribir el Guion
Lee [references/writing-style.md]. Requisitos estrictos:
- Una frase por plano, aproximadamente 40–45 frases, 480–520 caracteres, duración ~68 s (caracteres ÷ 7.4 ≈ segundos).
- Cada remate debe materializarse con una acción visible o un cambio de atrezo; no puede depender solo de los subtítulos.
- El final debe invertir la relación de los personajes, dejando una frase que se pueda capturar sola.
- Escribir en JSON:
{"title": ..., "target_seconds": 68, "segments": [{"scene": ..., "text": ..., "red": [...]}]}scenees el ID de la imagen,redson las palabras a resaltar en rojo (deben aparecer exactamente igual entext).
3. Dividir en Storyboard
"$python_bin" "$skill_dir/scripts/build_segments.py" --story story.json --out project_ep2
Los subtítulos se dividen según signos de puntuación en líneas ≤16 caracteres; el renderizador no soporta más de 17 caracteres por línea. El punto de división se elige cerca de los 10 caracteres, evitando partir palabras ("gran anticipo" no debe cortarse en "gran anti" ni "anticipo"); las frases cortas entre comillas no se dividen; no se generarán líneas que comiencen con 」. El script verifica que las palabras rojas estén en los subtítulos, que no se pierdan caracteres al dividir, que no aparezcan subtítulos vacíos (solo puntuación), e imprime la duración estimada: caracteres ÷ 7.4 ≈ segundos. Si se desvía del objetivo en ±10%, se mostrará una advertencia: ajusta añadiendo o quitando frases según la advertencia, no modificando la velocidad de locución.
4. Dibujar el Storyboard
En project_ep2/scenes.py, escribe la función scene_<id>(c, t, d) y al final del archivo redefine el diccionario SCENES (solo con los IDs de este episodio). Las primitivas de dibujo están en scripts/render/doodles.py.
person(c,x,y,s,expr,boss,pose,t,arms,legs): centro de la cabeza en (x,y), cuerpo hasta y+307*s.exprpuede serdead/sad/happy/angry/shocked/sleep.posepuede serstand/shrug/point/phone/pockets/walk.woman(c,x,y,s,expr,t): personaje femenino (moño + vestido).speech(c,x,y,w,h,tail,size): burbuja de diálogo.c.text((x,y),'texto',size)usa anclaje centrado.c.curvesolo acepta 4 puntos de control;c.textno soporta contorno ni múltiples líneas.
Después de editar, pasa dos filtros:
"$python_bin" "$skill_dir/scripts/check_overlap.py" project_ep2/scenes.py # Auditoría de diseño estática
"$python_bin" "$skill_dir/scripts/check_project.py" project_ep2 # Inspección de tabla de contactos
check_overlap.py detecta texto sobre personajes, texto superpuesto y elementos fuera de la zona segura. Ya reconoce dos situaciones que "parecen superposición pero no lo son": sustituciones de palabras mutuamente excluyentes en if/else y bloques de color blanco que tapan elementos antiguos. Por lo tanto, lo que reporta suelen ser problemas reales: si el código de salida no es 0, corrígelo antes de renderizar.
check_project.py primero verifica los subtítulos y luego genera la tabla de contactos: los subtítulos deben ser la misma frase que el diálogo (se permite puntuación refinada manualmente, pero no caracteres extra, faltantes o palabras cambiadas); no se permiten subtítulos vacíos sin texto; si no pasa, se sale directamente, sin perder tiempo renderizando la tabla.
Si realmente se quiere mantener una superposición (por ejemplo, una insignia dibujada sobre un personaje), regístrala explícitamente en scenes.py:
OVERLAP_ALLOW = {"three_years", "me_too"}
La tabla de contactos debe revisarse plano por plano: ¿la imagen coincide con la frase? ¿El atrezo está claro?
5. Locución
"$python_bin" "$skill_dir/scripts/build_voice.py" --project project_ep2 --speed 1.5 --gap 0.10
La síntesis frase por frase es clave en este flujo: cada frase se sintetiza individualmente con TTS, se recorta el silencio inicial y final, y se concatenan con un intervalo fijo. Así, los tiempos de inicio y fin de cada frase son conocidos; asr.json se construye directamente con estos tiempos, logrando una alineación de subtítulos sin error y sin necesidad de ASR a nivel de palabra.
- Voz predeterminada:
lengdan_xiongzhang(compañero frío). Si el usuario especifica otra, se usa esa. - TTS tiene limitación de frecuencia (HTTP 429). El script reintenta con un intervalo fijo de 10 segundos (el intervalo fijo es mucho más efectivo que la backoff exponencial).
- Un episodio tiene aproximadamente 40–45 frases; el proceso completo dura unos 15 minutos y se ejecuta en segundo plano.
- Después de la síntesis, verifica con
scripts/tts.py asr --file project_ep2/voice.wav. - Si hay errores de reconocimiento de sonidos cercanos (por ejemplo, "banshi/banshishi"), solo corrige los caracteres en el resultado ASR; no modifiques el guion ni regrabes.
6. Línea de Tiempo, Renderizado y Verificación
"$python_bin" "$skill_dir/scripts/deliver.py" project_ep2 <título> --target 68
"$python_bin" "$skill_dir/scripts/verify.py" project_ep2
deliver.py calcula el atempo inverso según la duración objetivo (speed = pista original / (objetivo - silencio final)), genera la línea de tiempo, subtítulos, mezcla de audio, renderiza las versiones con y sin subtítulos, y archiva el video y SRT en out/.
verify.py comprueba: parámetros de codificación, decodificación completa del video, temporización y longitud de los subtítulos, y escucha de ASR. Solo se considera terminado si la verificación es completamente verde; si falla la decodificación o hay problemas de temporización en los subtítulos, hay que corregir y renderizar de nuevo.
7. Portada
La portada no usa imágenes generadas por IA. Se redibuja con el mismo conjunto de primitivas gráficas y fuentes del video, asegurando cero desviación de estilo.
En project_ep2/cover.py, escribe la función illustration(c) (las primitivas de dibujo ya están inyectadas en el espacio de nombres). En project_ep2/cover.json, escribe las líneas de texto:
{"lines": [
{"text": "En la cita a ciegas", "size": 146, "y": 170, "red": []},
{"text": "Mi mamá me apuntó a siete", "size": 96, "y": 312, "red": ["siete"]},
{"text": "Conocer a los padres y a la abuela", "size": 64, "y": 1196, "red": []},
{"text": "Con 87 años y no presiona para tener hijos", "size": 64, "y": 1280, "red": ["presiona"]}
]}
"$python_bin" "$skill_dir/scripts/make_cover.py" project_ep2
Las reglas de diseño están en [references/cover-design.md]. La portada debe revisarse visualmente antes de entregar.
8. Materiales de Publicación para WeChat Channels
Primero recopila los materiales, luego escribe los textos:
"$python_bin" "$skill_dir/scripts/make_metadata.py" project_ep2
El script imprime el guion completo, las palabras en rojo, las palabras frecuentes y las últimas tres frases. A partir de ahí, escribe:
- 3 títulos, con gancho emoji. Cada título ≤22 caracteres es ideal (se puede exceder, límite máximo 100).
- 1 descripción definitiva, de 2–3 líneas. La primera línea es el gancho; los hashtags van en línea con
#hashtag. - 5–8 hashtags, todos deben aparecer en la descripción (el script lo verificará).
- 1 comentario fijado, para ampliar información o autocrítica.
Después de escribir:
"$python_bin" "$skill_dir/scripts/make_metadata.py" project_ep2 \
--titles "Título uno" "Título dos" "Título tres" \
--desc "Texto de la descripción #hashtag1 #hashtag2" \
--tags "hashtag1" "hashtag2" \
--comment "Comentario fijado"
Los detalles de las reglas están en [references/publish-channels.md]. Los archivos resultantes son publish.md / publish.json.
Restricciones Estrictas
- Lienzo 1080×1440 (3:4), 30 fps, fondo blanco puro, contorno 4–6 px negro, cabeza grande y cuerpo delgado.
- El sujeto principal se concentra en y=400–1050, dejando espacio en blanco arriba; la banda de subtítulos comienza en y=1084, tamaño de fuente mínimo 54.
- Subtítulos en fuente Xingkai (cursiva), texto blanco con sombra suave oscura, palabras clave del remate en rojo.
- Cada línea de subtítulo ≤16 caracteres; el renderizador no puede manejar más.
- No quemes el texto en el cuadro; los subtítulos se renderizan como una capa separada.
- La portada debe usar la misma fuente y líneas que el video; no se permite cambiar de estilo.
- Conserva los videos ya terminados. Si modificas el guion, usa un nuevo directorio de proyecto; no sobrescribas episodios ya entregados.
Manejo de Fallos
- TTS sigue dando 429: El script reintenta con intervalo fijo 40 veces. Si aún falla, reduce la velocidad a 1.4 o ejecuta en lotes (
--limit Npara continuar, el caché omitirá las frases ya sintetizadas). prepare.pyreporta inconsistencia en ASR: Significa queasr.jsonno coincide con el guion, generalmente porque se modificó el guion sin regrabar la locución. Vuelve a ejecutarbuild_voice.py; no uses--allow-asr-mismatchpara saltarlo.render.pyreporta subtítulo demasiado largo: Algún caption supera los 16 caracteres. Vuelve abuild_segments.pyy ajusta las reglas de división.check_overlap.pydevuelve código de salida distinto de 0: Corrige las coordenadas una por una según el ID de escena reportado, hasta que el código de salida sea 0.- No digas que la verificación de escucha ha pasado sin haber escuchado todo el audio; no digas que has visto el video completo sin haberlo visto por completo. En el registro de entrega, solo escribe las verificaciones que realmente realizaste.
Referencias
- references/writing-style.md — Escritura del guion y diseño de remates
- references/series-plan.md — Selección de temas y diferenciación de la serie
- references/cover-design.md — Reglas de diseño de la portada
- references/publish-channels.md — Reglas de materiales de publicación para WeChat Channels
Notas para la plataforma Windows (win32)
Equivalencias en Windows:
- Entorno virtual:
py -3 -m venv .venv. Luego usa.venv\Scripts\python.exeen lugar de$python_bin. - Dependencias:
.venv\Scripts\pip.exe install -r "<skill_dir>\scripts\requirements.txt". - ffmpeg/ffprobe: instálalos directamente en el PATH (
winget install Gyan.FFmpeg). No necesitas añadir/opt/homebrew/bin. Sin embargo, las rutas de ffmpeg enscripts/deliver.py,scripts/make_preview.pyyscripts/verify.pyson rutas absolutas de macOS. Cámbialas affmpeg/ffprobepara que funcionen en todas las plataformas. scripts/build_voice.pydepende de quemcode-toolsesté en el PATH y decurl. Windows 10 1803+ incluye curl; si falta, puedes usarInvoke-WebRequest -OutFile <ruta> <url>como alternativa.- Separador de rutas: todos los scripts usan
pathlib. Al pasar argumentos, envuelve las rutas entre comillas.