← Volver al catálogo
VídeoAutomatizaciónIntermedioGratis

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.

Descargar SKILL.md

Descarga abierta · sin registro · para Python, ffmpeg, Pillow

// resultado_de_ejemplo

""" 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

PythonffmpegPillowImageMagick
Categoría
Vídeo
Tipo
Automatización
Nivel
Intermedio
Licencia
Apache-2.0
Seguridad
seguro · riesgo bajo
Versión
1.0.0

// 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 €.