Edicion de Video con Python usando MoviePy
Edita y compone video de forma programatica con MoviePy: recorte, concatenacion, superposicion de texto, efectos, mezcla de audio y procesamiento masivo automatizado. Ideal para pipelines de video sin GUI basados en codigo Python.
Descarga abierta · sin registro · para Python, ffmpeg, Pillow
""" generar_stories_cursos.py
Pipeline de generacion automatizada de Instagram Stories para Cultiva Cursos. Genera 5 videos 1080x1920 a partir de cursos.json usando MoviePy 2.x.
Uso: python generar_stories_cursos.py
Requisitos: pip install moviepy Pillow apt install -y ffmpeg # o brew install ffmpeg en macOS
Estructura de salida: stories/ 01_ia-aplicada-marketing.mp4 02_automatizacion-n8n.mp4 03_copywriting-llm.mp4 04_seo-tecnico-ia.mp4 05_ads-performance-ia.mp4 """
import os import json import textwrap from pathlib import Path
---------------------------------------------------------------------------
Datos de cursos (equivalente a leer cursos.json)
---------------------------------------------------------------------------
CURSOS = [ { "id": "01", "slug": "ia-aplicada-marketing", "nombre": "IA Aplicada al Marketing", "precio": "197 €", "tagline": "De cero a experto en IA para agencias en 6 semanas", "duracion_min": 340, "color_bg": (15, 23, 42), # azul marino oscuro "color_accent": (99, 102, 241), # indigo "emoji": "🚀", }, { "id": "02", "slug": "automatizacion-n8n", "nombre": "Automatizacion con n8n", "precio": "147 €", "tagline": "Construye flujos de trabajo que trabajan mientras duermes", "duracion_min": 280, "color_bg": (10, 36, 25), # verde muy oscuro "color_accent": (34, 197, 94), # verde "emoji": "⚡", }, { "id": "03", "slug": "copywriting-llm", "nombre": "Copywriting con LLMs", "precio": "97 €", "tagline": "Escribe textos que convierten con ayuda de la IA", "duracion_min": 210, "color_bg": (30, 10, 40), # purpura oscuro "color_accent": (168, 85, 247), # purpura "emoji": "✍️", }, { "id": "04", "slug": "seo-tecnico-ia", "nombre": "SEO Tecnico + IA", "precio": "167 €", "tagline": "Posiciona cualquier web en Google usando agentes de IA", "duracion_min": 295, "color_bg": (25, 25, 10), # amarillo muy oscuro "color_accent": (234, 179, 8), # amarillo "emoji": "📈", }, { "id": "05", "slug": "ads-performance-ia", "nombre": "Ads Performance con IA", "precio": "217 €", "tagline": "Multiplica el ROAS de tus campanas con datos e IA", "duracion_min": 310, "color_bg": (30, 10, 10), # rojo muy oscuro "color_accent": (239, 68, 68), # rojo "emoji": "🎯", }, ]
Duracion del video en segundos
VIDEO_DURATION = 12 VIDEO_SIZE = (1080, 1920) FPS = 30 OUTPUT_DIR = Path("stories")
def horas_minutos(minutos: int) -> str: h, m = divmod(minutos, 60) return f"{h}h {m}min" if h else f"{m}min"
def generar_story(curso: dict, output_path: Path) -> None: """Genera un Instagram Story para un curso dado.""" from moviepy import ( ColorClip, TextClip, CompositeVideoClip, ImageClip, vfx, ) import numpy as np
print(f" Generando: {curso['nombre']}...")
# ------------------------------------------------------------------
# 1. Fondo degradado (simulado con ColorClip + efecto custom)
# ------------------------------------------------------------------
bg = ColorClip(size=VIDEO_SIZE, color=curso["color_bg"], duration=VIDEO_DURATION)
# Franja de color accent en la parte inferior (30% height)
accent_height = int(VIDEO_SIZE[1] * 0.30)
accent_bar = (
ColorClip(size=(VIDEO_SIZE[0], accent_height), color=curso["color_accent"], duration=VIDEO_DURATION)
.with_opacity(0.15)
.with_position((0, VIDEO_SIZE[1] - accent_height))
)
# ------------------------------------------------------------------
# 2. Texto: etiqueta de categoria (arriba centrado, pequeno)
# ------------------------------------------------------------------
label = (
TextClip(
text="CULTIVA CURSOS",
font_size=32,
color="white",
font="Arial",
stroke_color=tuple(curso["color_accent"]),
stroke_width=1,
)
.with_duration(VIDEO_DURATION)
.with_opacity(0.8)
.with_position(("center", 120))
)
# ------------------------------------------------------------------
# 3. Emoji grande como elemento visual central
# ------------------------------------------------------------------
# Nota: en produccion usarias una imagen real del curso;
# aqui usamos TextClip con emoji como placeholder visual
emoji_clip = (
TextClip(
text=curso["emoji"],
font_size=180,
color="white",
font="Arial",
)
.with_duration(VIDEO_DURATION)
.with_position(("center", 480))
.crossfadein(0.4)
)
# ------------------------------------------------------------------
# 4. Titulo del curso (bold, blanco, arriba del emoji)
# ------------------------------------------------------------------
titulo = (
TextClip(
text=curso["nombre"].upper(),
font_size=72,
color="white",
font="Arial-Bold",
stroke_color="black",
stroke_width=2,
size=(960, None),
method="caption", # word wrap
)
.with_duration(VIDEO_DURATION)
.with_position(("center", 280))
.crossfadein(0.3)
)
# ------------------------------------------------------------------
# 5. Precio (verde/accent, grande, centrado)
# ------------------------------------------------------------------
precio_color = "#{:02x}{:02x}{:02x}".format(*curso["color_accent"])
precio = (
TextClip(
text=curso["precio"],
font_size=96,
color=precio_color,
font="Arial-Bold",
stroke_color="black",
stroke_width=1,
)
.with_duration(VIDEO_DURATION)
.with_position(("center", 760))
.crossfadein(0.5)
)
# ------------------------------------------------------------------
# 6. Duracion del curso
# ------------------------------------------------------------------
duracion_texto = f"{horas_minutos(curso['duracion_min'])} de contenido"
duracion_clip = (
TextClip(
text=duracion_texto,
font_size=36,
color="white",
font="Arial",
)
.with_duration(VIDEO_DURATION)
.with_opacity(0.7)
.with_position(("center", 880))
.crossfadein(0.6)
)
# ------------------------------------------------------------------
# 7. Tagline (parte inferior, fade-in)
# ------------------------------------------------------------------
tagline = (
TextClip(
text=f'"{curso["tagline"]}"',
font_size=44,
color="white",
font="Arial",
size=(900, None),
method="caption",
)
.with_duration(VIDEO_DURATION)
.with_position(("center", 1400))
.crossfadein(0.8)
.crossfadeout(0.5)
)
# ------------------------------------------------------------------
# 8. CTA (call to action)
# ------------------------------------------------------------------
cta_bg = (
ColorClip(size=(700, 90), color=curso["color_accent"], duration=VIDEO_DURATION)
.with_position(("center", 1620))
.crossfadein(1.0)
)
cta_text = (
TextClip(
text="VER CURSO →",
font_size=42,
color="white",
font="Arial-Bold",
)
.with_duration(VIDEO_DURATION)
.with_position(("center", 1635))
.crossfadein(1.0)
)
# ------------------------------------------------------------------
# 9. Watermark (semitransparente, esquina superior derecha)
# ------------------------------------------------------------------
watermark = (
TextClip(
text="@cultivacursos",
font_size=30,
color="white",
font="Arial",
)
.with_duration(VIDEO_DURATION)
.with_opacity(0.4)
.with_position((880, 60))
)
# ------------------------------------------------------------------
# 10. Composicion final
# ------------------------------------------------------------------
layers = [bg, accent_bar, label, emoji_clip, titulo, precio,
duracion_clip, tagline, cta_bg, cta_text, watermark]
composite = CompositeVideoClip(layers, size=VIDEO_SIZE)
# Fade in/out del video completo
composite = composite.with_effects([vfx.FadeIn(0.3), vfx.FadeOut(0.5)])
# ------------------------------------------------------------------
# 11. Render
# ------------------------------------------------------------------
OUTPUT_DIR.mkdir(exist_ok=True)
composite.write_videofile(
str(output_path),
fps=FPS,
codec="libx264",
audio=False, # sin audio en este ejemplo (se añadiria musica ambient)
preset="fast",
threads=4,
logger=None, # silenciar logs de ffmpeg en produccion
)
composite.close()
print(f" ✓ Guardado: {output_path}")
def main(): print("=" * 60) print(" CULTIVA CURSOS — Generador de Instagram Stories") print("=" * 60) print(f" Procesando {len(CURSOS)} cursos → carpeta '{OUTPUT_DIR}/'") print()
resultados = []
for curso in CURSOS:
filename = f"{curso['id']}_{curso['slug']}.mp4"
output_path = OUTPUT_DIR / filename
try:
generar_story(curso, output_path)
resultados.append({"curso": curso["nombre"], "archivo": str(output_path), "ok": True})
except Exception as e:
print(f" ERROR en {curso['nombre']}: {e}")
resultados.append({"curso": curso["nombre"], "error": str(e), "ok": False})
print()
print("=" * 60)
print(" RESUMEN")
print("=" * 60)
ok_count = sum(1 for r in resultados if r["ok"])
for r in resultados:
estado = "✓" if r["ok"] else "✗"
print(f" {estado} {r['curso']}")
print()
print(f" {ok_count}/{len(CURSOS)} videos generados correctamente.")
# Guardar log JSON
with open(OUTPUT_DIR / "_log.json", "w", encoding="utf-8") as f:
json.dump(resultados, f, ensure_ascii=False, indent=2)
print(f" Log guardado en {OUTPUT_DIR}/_log.json")
if name == "main": main()
---------------------------------------------------------------------------
EXTENSION: Añadir musica ambient
---------------------------------------------------------------------------
Para mezclar musica de fondo, descomentar y adaptar:
from moviepy import AudioFileClip, CompositeAudioClip
def add_ambient_music(video_path: str, music_path: str, volume: float = 0.20):
from moviepy import VideoFileClip
video = VideoFileClip(video_path)
music = (AudioFileClip(music_path)
.subclipped(0, video.duration)
.with_volume_scaled(volume))
final = video.with_audio(music)
out = video_path.replace(".mp4", "_music.mp4")
final.write_videofile(out, fps=FPS, codec="libx264",
audio_codec="aac", preset="fast", threads=4)
video.close()
return out
Uso tras generar todos los videos:
for f in OUTPUT_DIR.glob("*.mp4"):
if "_music" not in f.name:
add_ambient_music(str(f), "assets/ambient.mp3")
// qué_hace
Automatiza la edicion de video con Python: recorta, concatena, superpone texto y efectos, mezcla audio y procesa lotes de clips sin interfaz grafica.
// cómo_lo_hace
Usa la libreria MoviePy 2.x (sobre ffmpeg) con clases VideoFileClip, CompositeVideoClip y TextClip para componer y renderizar videos mediante codigo Python.
// ejemplo_de_uso
Úsala para procesar lotes de vídeos sin abrir un editor: recortar, añadir subtítulos o mezclar audio por script. Ej.: generas 50 clips cortos a partir de un vídeo largo con textos de portada distintos en cada uno.
// plataformas
// opiniones_de_la_comunidad
Opiniones
Cargando opiniones…
// pase_cultiva_ia
Llévate todo el arsenal con el Pase
Todas las skills, prompts y automatizaciones del catálogo en un único archivo, listas para usar: un pago, acceso de por vida y las novedades que añadamos. Sin suscripción.
Pago único · IVA incluido · pago seguro con Stripe.
Acceso inmediato · si no es lo que esperabas, te devolvemos los 10 €.