Habilidad de Creación de Video
Para gráficos animados en movimiento hechos con código — tipografía cinética, videos explicativos, lanzamientos, gráficos animados, terceros inferiores con una elección de estilos visuales — usa la habilidad
motion-graphics. Esta habilidad es para footage, fotos, audio y edición con ffmpeg.
Construye videos de forma programática con ffmpeg (motor principal) y Python (Pillow para frames/gráficos, MoviePy para edición de alto nivel). Siempre planifica primero, construye después, verifica al final.
1. Planificar antes de construir
Resuelve esto antes de escribir cualquier comando:
| Decisión | Por defecto | Notas |
|---|---|---|
| Relación de aspecto | 16:9 (1920x1080) | 9:16 (1080x1920) para Reels/Shorts/TikTok, 1:1 (1080x1080) para feeds |
| Fotogramas por segundo | 30 fps | 24 para cinematográfico, 60 para grabaciones de pantalla/juegos |
| Duración | Según lo solicitado | Reels/Shorts ≤ 60–90 s |
| Formato | MP4 (H.264 + AAC) | Reproduce en todas partes; WebM solo si se solicita; GIF solo para bucles cortos |
| Audio | Opcional | Música, voz en off o silencio |
Escribe una lista breve de tomas: cada escena → duración, visual, texto, transición, audio.
2. Verificar las herramientas
which ffmpeg ffprobe || (apt-get update && apt-get install -y ffmpeg)
python3 -c "import PIL, moviepy" 2>/dev/null || pip install pillow moviepy --break-system-packages
fc-list | head # ver qué fuentes están disponibles para texto
3. Configuración de codificación estándar
Usa estas opciones para cada render final a menos que el usuario indique lo contrario:
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -movflags +faststart \
-c:a aac -b:a 192k
-pix_fmt yuv420p— requerido para reproducción en teléfonos, QuickTime y navegadores.-movflags +faststart— permite que el video comience a reproducirse antes de descargarse por completo.-crf 18= casi sin pérdida (archivo más grande),-crf 28= archivo más pequeño, menor calidad.-preset veryfastmantiene los renders rápidos; usaslowsolo cuando se solicite la mejor calidad.- El ancho y el alto deben ser números pares para H.264.
3b. Renderizar rápido — la persona está esperando
Un render completo es el paso más lento, así que planifica un render completo por solicitud:
- Mira antes de codificar. Extrae o dibuja algunos fotogramas al tamaño final — los momentos más ocupados, el primer y último fotograma — y verifica el encuadre, el tamaño del texto y los colores. Para videos de fotos:
scripts/slideshow.py OUT.mp4 fotos… --previewcrea una hoja de contactos en un segundo. - Escenas, no un gráfico gigante. Codifica cada escena en su propio archivo con tamaño, fps y configuración de codificador idénticos, únelos con el demuxer concat y
-c copy, y agrega el sonido al final con-c:v copy. Arreglar una escena más tarde implica recodificar solo esa escena. - Las transiciones viven dentro de un archivo de escena (fundido de entrada/salida en sus bordes). Fundir cruzadamente dos archivos terminados fuerza una segunda codificación completa.
- Mantén un fotograma fijo de forma económica. Decodifica la imagen una vez y repítela con el filtro
loop, en lugar de-loop 1(que decodifica la imagen de nuevo para cada fotograma):ffmpeg -framerate 30 -i imagen.png -vf "format=yuv420p,loop=-1:1,trim=duration=4,setpts=PTS-STARTPTS" -c:v libx264 -preset veryfast -crf 20 -pix_fmt yuv420p imagen.mp4 - Nunca dejes un archivo a medio terminar con el nombre real. Escribe en
nombre.part.mp4y renombra solo cuando ffmpeg salga con 0 — un render interrumpido aún puede reproducirse, solo que cortado. - Dibuja los fotogramas al tamaño de salida y 30 fps a menos que se solicite lo contrario. Al capturar una página web fotograma a fotograma, guarda JPEG (calidad ~90), no PNG — mucho más rápido de escribir y leer.
Medido en 10 fotos → 1080p: el antiguo gráfico único -loop 1 + xfade tomó 13.2 s; slideshow.py --crossfade toma 6.4 s, la ruta de archivos de escena 8.9 s incluyendo música.
Cambiar un video terminado:
| Cambio | Haz |
|---|---|
| Contenido de una escena | re-renderiza ese archivo de escena, vuelve a unir con -c copy |
| Nueva música / recortar el final | -c:v copy (nueva música: -map 0:v -map 1:a) |
| Quemar subtítulos, hacer una copia más pequeña | recodifica el archivo terminado una vez |
| Algo que cambia en cada fotograma | un render completo es inevitable |
4. Recetas
4.0 Presentación de fotos en un solo comando
python3 scripts/slideshow.py salida.mp4 fotos/*.jpg --seconds 3 --fade 0.5 --music cancion.mp3 # rápido, fundido a negro
python3 scripts/slideshow.py salida.mp4 fotos/*.jpg --crossfade # fundidos cruzados reales
python3 scripts/slideshow.py salida.mp4 fotos/*.jpg --zoom --size 1080x1920 # Ken Burns, vertical
4.1 Presentación desde imágenes (con transiciones de fundido), manualmente
# Cada imagen mostrada durante 3 s, fundido cruzado de 0.5 s, redimensionada/rellenada a 1080p
# Cada fotograma fijo se decodifica una vez y se repite con el filtro loop (2x más rápido que -loop 1)
F="scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1,format=yuv420p,loop=-1:1,trim=duration=3,setpts=PTS-STARTPTS,fps=30"
ffmpeg -framerate 30 -i img1.jpg -framerate 30 -i img2.jpg -framerate 30 -i img3.jpg \
-filter_complex "\
[0]$F[a];[1]$F[b];[2]$F[c];\
[a][b]xfade=transition=fundido:duration=0.5:offset=2.5[ab];\
[ab][c]xfade=transition=fundido:duration=0.5:offset=5[out]" \
-map "[out]" -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -movflags +faststart presentacion.mp4
Desplazamiento para el clip n = (suma de duraciones anteriores) − (n × duración del fundido).
4.2 Ken Burns (zoom lento) en una imagen fija
# d = número de fotogramas de salida (150 a 30 fps = 5 s); no se necesita -loop, zoompan genera los fotogramas
ffmpeg -i foto.jpg -vf "scale=3840:-2,zoompan=z='min(zoom+0.0008,1.2)':d=150:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=1920x1080:fps=30" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p zoom.mp4
Escalar a 3840 primero evita el movimiento tembloroso que muestra zoompan en imágenes pequeñas.
4.3 Superposición de texto / tarjeta de título
ffmpeg -i entrada.mp4 -vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:\
text='Mi Título':fontsize=72:fontcolor=white:box=1:[email protected]:boxborderw=20:\
x=(w-text_w)/2:y=h-150:enable='between(t,0,4)'" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a copy titulado.mp4
Para texto con apóstrofos, emojis o escrituras no latinas (Hindi, etc.), renderiza el texto a un PNG con Pillow usando una fuente que soporte la escritura, luego superponlo:
ffmpeg -i entrada.mp4 -i titulo.png -filter_complex "overlay=(W-w)/2:H-h-100:enable='between(t,0,4)'" ...
4.4 Tarjeta de título de color sólido desde cero
ffmpeg -f lavfi -i color=c=0x111827:s=1920x1080:d=3:r=30 \
-vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:text='Capítulo 1':fontsize=96:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p tarjeta.mp4
4.5 Recortar un clip
# Recorte preciso (recodifica): desde 00:00:05 durante 10 segundos
ffmpeg -ss 00:00:05 -i entrada.mp4 -t 10 -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a aac recortado.mp4
# Recorte rápido (sin recodificar, corta en el fotograma clave más cercano)
ffmpeg -ss 00:00:05 -i entrada.mp4 -t 10 -c copy recortado_rapido.mp4
4.6 Unir clips
Mismo códec/resolución/fps → demuxer concat (sin recodificar):
printf "file 'a.mp4'\nfile 'b.mp4'\nfile 'c.mp4'\n" > lista.txt
ffmpeg -f concat -safe 0 -i lista.txt -c copy unido.mp4
Fuentes diferentes → normalizar primero (escala, fps, tasa de audio) o usar el filtro concat:
ffmpeg -i a.mp4 -i b.mp4 -filter_complex \
"[0:v]scale=1920:1080,setsar=1,fps=30[v0];[1:v]scale=1920:1080,setsar=1,fps=30[v1];\
[v0][0:a][v1][1:a]concat=n=2:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a aac unido.mp4
(Si un clip no tiene pista de audio, agrega silencio: -f lavfi -i anullsrc=r=48000:cl=stereo y usa -shortest.)
4.7 Añadir música de fondo
# Música debajo del video, fundido de salida al final, se detiene cuando termina el video
# st = duración del video menos duración del fundido (aquí un video de 30 s, fundido de 3 s) — obtén la duración con ffprobe
ffmpeg -i video.mp4 -i musica.mp3 -filter_complex \
"[1:a]volume=0.3,afade=t=out:st=27:d=3[m]" \
-map 0:v -map "[m]" -c:v copy -c:a aac -shortest con_musica.mp4
# Mezclar música debajo de una voz existente
ffmpeg -i video.mp4 -i musica.mp3 -filter_complex \
"[1:a]volume=0.15[m];[0:a][m]amix=inputs=2:duration=first[a]" \
-map 0:v -map "[a]" -c:v copy -c:a aac mezclado.mp4
4.8 Subtítulos / leyendas
Escribe un archivo .srt:
1
00:00:00,000 --> 00:00:03,000
Bienvenido al video.
2
00:00:03,000 --> 00:00:06,500
Esto es lo que cubriremos.
Quémalos:
ffmpeg -i entrada.mp4 -vf "subtitles=subs.srt:force_style='FontSize=24,PrimaryColour=&HFFFFFF,OutlineColour=&H000000,BorderStyle=1,Outline=2'" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a copy subtitulado.mp4
4.9 Redimensionar para redes sociales (vertical 9:16)
# Fondo de relleno borroso en lugar de barras negras
ffmpeg -i paisaje.mp4 -filter_complex \
"[0]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=20[bg];\
[0]scale=1080:-2[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a copy vertical.mp4
4.10 Acelerar / ralentizar
# Velocidad 2x (video + audio)
ffmpeg -i entrada.mp4 -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]" rapido.mp4
4.11 Video a GIF (alta calidad)
ffmpeg -i entrada.mp4 -vf "fps=15,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 salida.gif
4.12 Renderizar fotogramas desde código (animaciones, gráficos, gráficos en movimiento)
Cuando los filtros de ffmpeg no son suficientes, dibuja cada fotograma con Python y combínalos:
from PIL import Image, ImageDraw, ImageFont
import os, subprocess
ANCHO, ALTO, FPS, SEGUNDOS = 1920, 1080, 30, 4
os.makedirs("frames", exist_ok=True)
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 96)
for i in range(FPS * SEGUNDOS):
t = i / (FPS * SEGUNDOS)
img = Image.new("RGB", (ANCHO, ALTO), (17, 24, 39))
d = ImageDraw.Draw(img)
x = int(-600 + t * (ANCHO + 600)) # el texto se desliza a través
d.text((x, ALTO // 2 - 48), "¡Hola!", font=font, fill=(255, 255, 255))
img.save(f"frames/{i:05d}.png")
subprocess.run(["ffmpeg", "-y", "-framerate", str(FPS), "-i", "frames/%05d.png",
"-c:v", "libx264", "-preset", "veryfast", "-crf", "23",
"-pix_fmt", "yuv420p", "-movflags", "+faststart", "animation.mp4"], check=True)
Usa easing (p.ej. t*t*(3-2*t)) para un movimiento más suave.
4.13 MoviePy (ediciones rápidas de alto nivel)
from moviepy import VideoFileClip, TextClip, CompositeVideoClip, concatenate_videoclips, AudioFileClip
clip1 = VideoFileClip("a.mp4").subclipped(0, 5)
clip2 = VideoFileClip("b.mp4").subclipped(2, 8)
video = concatenate_videoclips([clip1, clip2], method="compose")
musica = AudioFileClip("musica.mp3")
video = video.with_audio(musica.subclipped(0, min(video.duration, musica.duration))) # la música puede ser más corta
video.write_videofile("salida.mp4", fps=30, codec="libx264", audio_codec="aac", preset="veryfast")
(API de MoviePy 2.x mostrada; 1.x usa subclip/set_audio. Verifica con pip show moviepy.)
5. Verificar antes de entregar
Nunca entregues un video sin verificarlo:
# Duración, resolución, códecs, fps
ffprobe -v error -show_entries format=duration:stream=codec_name,width,height,r_frame_rate -of default=nw=1 salida.mp4
# Extraer fotogramas fijos en momentos clave y mirarlos
ffmpeg -ss 1 -i salida.mp4 -frames:v 1 verificar_1s.png
ffmpeg -i salida.mp4 -vf "fps=1/3,scale=480:-1,tile=4x2" -frames:v 1 hoja_contacto.png
Lista de verificación:
- La duración coincide con el plan
- La resolución y la relación de aspecto son correctas; sin barras negras no deseadas ni estiramientos
- El texto es legible, dentro de los márgenes de seguridad (mantén ~5% de los bordes; para 9:16, evita el ~15% inferior donde se encuentra la interfaz de la aplicación)
- Las transiciones aterrizan donde se pretendía
- El audio está presente (si se espera), no se recorta, termina limpiamente
- Se reproduce en teléfonos: H.264,
yuv420p, dimensiones pares
6. Errores comunes
| Problema | Solución |
|---|---|
| El video no se reproduce en iPhone/QuickTime | Agrega -pix_fmt yuv420p |
| "width not divisible by 2" | Usa scale=1280:-2 en lugar de -1 |
Fotogramas negros al inicio después de recorte con -c copy | Recodifica, o pon -ss antes de -i y recodifica |
| Audio desincronizado después de concat | Normaliza fps y frecuencia de muestreo de audio (-ar 48000) antes de unir |
| Error xfade "inputs differ" | Todas las entradas necesitan el mismo tamaño, fps, setsar=1 y formato de píxel |
drawtext se rompe con ' o : | Escápalos, o usa textfile= / superposición de PNG renderizada con Pillow |
| Texto no inglés muestra cuadros | Usa una fuente que soporte la escritura (p.ej. Noto Sans Devanagari) |
| Tamaño de archivo enorme | Sube el CRF (26–28), baja los fps o la resolución |
| El render es demasiado lento | Usa -preset veryfast o ultrafast; reduce la resolución mientras borrador |
7. Resumen del flujo de trabajo
- Planificar — relación de aspecto, fps, duración, lista de tomas, audio.
- Recolectar activos — imágenes, clips, música, fuentes; verifícalos con
ffprobe. - Borrador rápido — baja resolución / preset
ultrafastpara verificar la sincronización. - Construir final — configuración de codificación estándar (sección 3).
- Verificar — ffprobe + hoja de contacto + lista de verificación (sección 5).
- Entregar — envía el archivo final con un resumen de una línea (duración, resolución, qué contiene).