Editor de intros de vídeo con Python: Silero VAD + MoviePy en Mac
Crea un editor de intros de vídeo en Python con Silero VAD, MoviePy y FFmpeg. Detecta automáticamente el habla en tu metraje y genera un montaje de intro con estilo 3D en Mac.
Actualizado Publicado 19 min de lectura

Cortar una intro a mano significa recorrer el metraje, buscar los buenos momentos y reconstruir el mismo montaje en cada subida. Esta guía te muestra cómo crear un editor de intros de vídeo en Python que lo hace por ti: analiza tu metraje, detecta cuándo habla alguien usando detección de actividad de voz (VAD) y renderiza un montaje de intro con estilo 3D.
El stack: Silero VAD encuentra el habla en local, FFmpeg se encarga del desenfoque y los efectos de perspectiva, MoviePy compone los cortes rápidos y uv ejecuta todo como un único script sin gestión manual de dependencias. El tutorial está pensado para Mac; el script en sí es Python puro.
Lo que aprenderás
- Detectar el habla en local con Silero VAD, sin clave de API
- Desenfocar y deformar clips en 3D con filtros de FFmpeg
- Componer cortes rápidos con MoviePy: velocidad 3x, deslizamientos y fundidos
- Ejecutarlo todo con un solo comando
uv rungracias a las dependencias inline de PEP 723
Requisitos previos
- FFmpeg instalado con Homebrew
- uv instalado (un comando curl)
- Un vídeo fuente con gente hablando
- Unos GB libres en disco para la descarga única de PyTorch
FFmpeg
FFmpeg se encarga de la extracción de audio, el desenfoque y la deformación 3D. Instálalo con Homebrew:
brew install ffmpegComprueba que funciona:
ffmpeg -versionDeberías ver una línea de versión como ffmpeg version 7.x. Si obtienes zsh: command not found: ffmpeg, Homebrew no lo enlazó; ejecuta brew link ffmpeg o reabre el terminal.
uv
uv gestiona la parte de Python, incluido el propio intérprete. Instálalo con:
curl -LsSf https://astral.sh/uv/install.sh | shCompruébalo:
uv --versionSobre Python y el espacio en disco
El script declara requires-python = ">=3.10", y uv descarga y gestiona un Python compatible en la primera ejecución, así que no necesitas instalar ni configurar Python a mano. Si prefieres mantener un intérprete normal con venv en la máquina, sigue esta guía para instalar Python en Mac y configurar un entorno virtual.
Reserva unos gigabytes de espacio libre: la primera ejecución descarga PyTorch y los pesos del modelo Silero VAD. Todo lo demás queda en caché.
¿Nuevo en uv?
Si eres nuevo en uv, Primeros pasos con uv: configuración de proyectos Python en 2026 cubre la configuración de proyectos y los conceptos básicos del entorno. Para este artículo solo necesitas un comando: uv run.
Cómo funciona el editor de intros de vídeo en Python
El script ejecuta un pipeline directo desde el metraje en bruto hasta el montaje final:
- Extrae el audio de tu vídeo con FFmpeg como WAV mono de 16 kHz
- Ejecuta Silero VAD sobre el audio para encontrar dónde habla alguien
- Elige segmentos de voz de más de 2 segundos y toma una muestra de seis
- Construye un fondo desenfocado más un primer plano con deformación de perspectiva 3D por clip con FFmpeg
- Compone las capas en MoviePy a velocidad 3x con deslizamiento y fundido
- Concatena los clips y multiplexa una pista de audio muda en
intro_only.mp4
Con los valores por defecto, son seis clips de 1,5 segundos cada uno: un montaje de unos 9 segundos con una pista de audio muda, listo para colocar bajo una base musical.
El pipeline de la intro: FFmpeg extrae el audio, Silero VAD encuentra el habla, el selector toma una muestra de seis segmentos, FFmpeg construye el fondo desenfocado y el primer plano deformado en 3D por clip, MoviePy compone y concatena, y una pasada final de FFmpeg añade la pista de audio muda.
El script completo
Aquí está el script completo. Guárdalo como intro_generator.py; la cabecera PEP 723 de arriba le dice a uv exactamente qué dependencias fijadas instalar, así que uv run es el único comando que necesitas.
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "openai-whisper",
# "torch",
# "torchaudio<2.6",
# "soundfile",
# "numpy<2.0.0",
# "moviepy==1.0.3",
# "packaging",
# "Pillow<10.0.0",
# ]
# ///
import os
import sys
import warnings
# 1. Suppress warnings BEFORE importing moviepy
# This hides the "invalid escape sequence" text
warnings.filterwarnings("ignore")
import random
import subprocess
from pathlib import Path
import torch
import whisper
from moviepy.editor import (
CompositeVideoClip,
VideoFileClip,
concatenate_videoclips,
vfx,
)
# --- Configuration ---
MIN_SILENCE = 0.5
MIN_SPEECH = 0.25
PADDING = 0.1
# Intro Style Settings
INTRO_CLIP_COUNT = 6 # Number of fast cuts
INTRO_SPEED = 3.0 # Speed multiplier (3x fast)
CLIP_DURATION = 1.5 # Duration of each clip in seconds
OUTPUT_FILENAME = "intro_only.mp4"
def check_ffmpeg():
try:
subprocess.run(["ffmpeg", "-version"], capture_output=True, check=True)
except:
print("❌ Error: FFmpeg not found. Run: brew install ffmpeg")
sys.exit(1)
def extract_audio(video_path, audio_path):
subprocess.run(
[
"ffmpeg",
"-y",
"-i",
video_path,
"-vn",
"-acodec",
"pcm_s16le",
"-ar",
"16000",
"-ac",
"1",
"-loglevel",
"error",
audio_path,
],
check=True,
)
def get_good_segments(audio_path):
"""Finds segments where people are actually speaking (Key Moments)."""
print("🧠 Scanning video for key moments...")
# Load Silero VAD
# trust_repo=True fixes the "cache" warning/error
model, utils = torch.hub.load(
repo_or_dir="snakers4/silero-vad", model="silero_vad", trust_repo=True
)
(get_speech_timestamps, _, read_audio, _, _) = utils
wav = read_audio(audio_path)
vad_stamps = get_speech_timestamps(
wav,
model,
threshold=0.5,
min_speech_duration_ms=int(MIN_SPEECH * 1000),
min_silence_duration_ms=int(MIN_SILENCE * 1000),
)
segments = []
for v in vad_stamps:
segments.append((v["start"] / 16000, v["end"] / 16000))
return segments
def apply_blur_background(input_path, output_path):
subprocess.run(
[
"ffmpeg",
"-y",
"-i",
input_path,
"-vf",
"boxblur=40:5,eq=brightness=-0.4",
"-c:v",
"libx264",
"-preset",
"ultrafast",
"-an",
"-loglevel",
"error",
output_path,
],
check=True,
)
def apply_3d_warp(input_path, output_path, direction="left"):
if direction == "left":
vf = "perspective=x0=0:y0=0:x1=W:y1=H/5:x2=0:y2=H:x3=W:y3=4*H/5:sense=destination"
else:
vf = "perspective=x0=0:y0=H/5:x1=W:y1=0:x2=0:y2=4*H/5:x3=W:y3=H:sense=destination"
vf += ",pad=w=iw+100:h=ih+100:x=50:y=50:color=black@0"
subprocess.run(
[
"ffmpeg",
"-y",
"-i",
input_path,
"-vf",
vf,
"-c:v",
"libx264",
"-preset",
"ultrafast",
"-an",
"-loglevel",
"error",
output_path,
],
check=True,
)
def generate_intro(video_path, segments):
print("✨ Rendering 3D Intro (No Text)...")
long_segments = [s for s in segments if (s[1] - s[0]) > 2.0]
if len(long_segments) < INTRO_CLIP_COUNT:
print(f"⚠️ Not enough footage found. Need {INTRO_CLIP_COUNT} distinct moments.")
if not long_segments:
return
picks = random.choices(long_segments, k=INTRO_CLIP_COUNT)
else:
picks = sorted(random.sample(long_segments, INTRO_CLIP_COUNT))
intro_clips = []
temp_files = []
for i, (start, end) in enumerate(picks):
raw_dur = CLIP_DURATION * INTRO_SPEED
mid = start + (end - start) / 2 - (raw_dur / 2)
raw_clip = f"temp_raw_{i}.mp4"
bg_clip = f"temp_bg_{i}.mp4"
fg_clip = f"temp_fg_{i}.mp4"
# 1. Extract
subprocess.run(
[
"ffmpeg",
"-y",
"-ss",
str(mid),
"-t",
str(raw_dur),
"-i",
video_path,
"-c:v",
"libx264",
"-an",
"-loglevel",
"error",
raw_clip,
],
check=True,
)
# 2. Process
apply_blur_background(raw_clip, bg_clip)
direction = "left" if i % 2 == 0 else "right"
apply_3d_warp(raw_clip, fg_clip, direction)
# 3. Composite
try:
bg = VideoFileClip(bg_clip).fx(vfx.speedx, INTRO_SPEED)
fg = VideoFileClip(fg_clip).fx(vfx.speedx, INTRO_SPEED)
if direction == "left":
fg = fg.set_position(
lambda t: (int(-50 + 50 * (t / CLIP_DURATION)), "center")
)
else:
fg = fg.set_position(
lambda t: (int(50 - 50 * (t / CLIP_DURATION)), "center")
)
comp = CompositeVideoClip([bg, fg]).set_duration(CLIP_DURATION)
if i > 0:
comp = comp.crossfadein(0.2)
intro_clips.append(comp)
temp_files.extend([raw_clip, bg_clip, fg_clip])
except Exception as e:
print(f" ⚠️ Error processing clip {i}: {e}")
if not intro_clips:
print("❌ Failed to generate intro clips.")
return
# Concatenate
full_montage = concatenate_videoclips(intro_clips, method="compose")
print(" 💾 Saving video file...")
full_montage.write_videofile(
"temp_visual_intro.mp4", fps=24, codec="libx264", logger=None
)
# Add silent audio
print(" 🔊 Adding silent audio track...")
subprocess.run(
[
"ffmpeg",
"-y",
"-i",
"temp_visual_intro.mp4",
"-f",
"lavfi",
"-i",
"anullsrc=channel_layout=mono:sample_rate=44100",
"-c:v",
"copy",
"-c:a",
"aac",
"-shortest",
"-loglevel",
"error",
OUTPUT_FILENAME,
],
check=True,
)
# Cleanup
for f in temp_files:
if os.path.exists(f):
os.remove(f)
if os.path.exists("temp_visual_intro.mp4"):
os.remove("temp_visual_intro.mp4")
print(f"✅ Success! Intro saved as: {OUTPUT_FILENAME}")
def main():
if len(sys.argv) < 2:
print("Usage: uv run intro_generator.py <video.mp4>")
sys.exit(1)
input_video = sys.argv[1]
check_ffmpeg()
temp_wav = "temp_analysis.wav"
try:
extract_audio(input_video, temp_wav)
good_parts = get_good_segments(temp_wav)
if not good_parts:
print("⚠️ No speech detected. Picking random segments...")
duration = float(
subprocess.check_output(
[
"ffprobe",
"-v",
"error",
"-show_entries",
"format=duration",
"-of",
"default=noprint_wrappers=1:nokey=1",
input_video,
]
)
)
good_parts = [(t, t + 5) for t in range(0, int(duration), 10)]
generate_intro(input_video, good_parts)
finally:
if os.path.exists(temp_wav):
os.remove(temp_wav)
if __name__ == "__main__":
main()Entendiendo el script
El script usa metadatos inline de PEP 723 para declarar sus dependencias:
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "openai-whisper",
# "torch",
# "torchaudio<2.6",
# "soundfile",
# "numpy<2.0.0",
# "moviepy==1.0.3",
# "packaging",
# "Pillow<10.0.0",
# ]
# ///Cuando lo ejecutas, uv crea un entorno aislado, instala exactamente estas versiones y no toca nada de tu Python global. El patrón se cubre con más detalle en uv run: ejecuta scripts de Python sin gestión de dependencias.
Variables de configuración
| Variable | Por defecto | Descripción |
|---|---|---|
MIN_SILENCE |
0.5 | Duración mínima de silencio en segundos |
MIN_SPEECH |
0.25 | Duración mínima de voz en segundos |
INTRO_CLIP_COUNT |
6 | Número de clips de la intro |
INTRO_SPEED |
3.0 | Multiplicador de velocidad de los clips |
CLIP_DURATION |
1.5 | Duración de cada clip en segundos |
OUTPUT_FILENAME |
“intro_only.mp4” | Nombre del archivo de salida |
Dos de ellas controlan la duración de la intro: el tiempo total siempre es INTRO_CLIP_COUNT × CLIP_DURATION, así que los valores por defecto producen un montaje de 9 segundos.
Detección de actividad de voz (VAD)
La detección de voz es lo que convierte esto en un editor en lugar de un muestreador aleatorio de clips. get_good_segments() carga Silero VAD a través de torch.hub y le pide todas las ventanas donde alguien está hablando:
def get_good_segments(audio_path):
"""Finds segments where people are actually speaking (Key Moments)."""
model, utils = torch.hub.load(
repo_or_dir="snakers4/silero-vad", model="silero_vad", trust_repo=True
)
(get_speech_timestamps, _, read_audio, _, _) = utils
wav = read_audio(audio_path)
vad_stamps = get_speech_timestamps(
wav,
model,
threshold=0.5,
min_speech_duration_ms=int(MIN_SPEECH * 1000),
min_silence_duration_ms=int(MIN_SILENCE * 1000),
)
return [(v["start"] / 16000, v["end"] / 16000) for v in vad_stamps]Silero VAD es la opción habitual para la detección de actividad de voz en Python: el modelo ocupa unos dos megabytes, funciona en CPU y corre completamente offline tras la descarga inicial. No necesitas clave de API y no hay coste por minuto. Devuelve posiciones de inicio y fin en muestras a 16 kHz, que la función convierte a segundos. El argumento threshold=0.5 es el dial de sensibilidad: bájalo si el habla baja se escapa, súbelo si el ruido de fondo produce falsos positivos.
Efectos de perspectiva 3D con FFmpeg
El aspecto 3D viene del filtro perspective de FFmpeg, aplicado a cada clip con direcciones alternas:
def apply_3d_warp(input_path, output_path, direction="left"):
if direction == "left":
vf = "perspective=x0=0:y0=0:x1=W:y1=H/5:x2=0:y2=H:x3=W:y3=4*H/5:sense=destination"
else:
vf = "perspective=x0=0:y0=H/5:x1=W:y1=0:x2=0:y2=4*H/5:x3=W:y3=H:sense=destination"sense=destination le dice a FFmpeg que mapee los cuatro puntos de esquina de salida sobre el origen. Eso comprime el fotograma en un trapecio en vez de estirarlo: un borde se inclina hacia dentro mientras el opuesto queda fijo. El filtro pad añadido tras la cadena de perspectiva agrega 50 píxeles de margen transparente en cada lado, para que el clip deformado pueda deslizarse sobre el fondo desenfocado después sin bordes duros.
Composición de vídeo con MoviePy
MoviePy apila cada clip como un fondo desenfocado con un primer plano deformado deslizándose por encima:
bg = VideoFileClip(bg_clip).fx(vfx.speedx, INTRO_SPEED)
fg = VideoFileClip(fg_clip).fx(vfx.speedx, INTRO_SPEED)
if direction == "left":
fg = fg.set_position(
lambda t: (int(-50 + 50 * (t / CLIP_DURATION)), "center")
)
comp = CompositeVideoClip([bg, fg]).set_duration(CLIP_DURATION)Ambas capas se aceleran 3x con vfx.speedx, el primer plano se mueve 50 píxeles hacia el centro durante los 1,5 segundos del clip y CompositeVideoClip aplana la pila. Cada clip terminado recibe un crossfadein de 0,2 segundos, y luego concatenate_videoclips apila los seis en el montaje.
Por qué MoviePy está fijado a 1.0.3
MoviePy 2.0 se lanzó a finales de 2024 con una reescritura incompatible: el módulo moviepy.editor ya no existe, set_position pasó a ser with_position y el encadenamiento de .fx() cambió. Este script importa directamente de moviepy.editor, así que cualquier instalación 2.x falla en el import.
Ese pin es lo que mantiene el script reproducible. uv lee la cabecera, instala MoviePy 1.0.3 en un entorno limitado a este archivo y nada más en tu máquina cambia. Migrar a la API 2.x es un proyecto razonable (el estilo with_position es más limpio), pero es una reescritura del código de composición, no un cambio de versión. Si el error de import aparece de todos modos, la solución está en la sección de solución de problemas más abajo.
Ejecutar el script
Con uv y FFmpeg instalados:
uv run intro_generator.py your_video.mp4La primera ejecución es más lenta: uv resuelve e instala las dependencias fijadas (PyTorch es la grande) y torch.hub descarga el modelo Silero VAD una vez. Después todo queda en caché y las ejecuciones posteriores solo pagan el render.
Como todo es uv más FFmpeg, el script también viaja bien: si quieres un entorno reproducible en un servidor en vez de tu Mac, mira cómo ejecutar cualquier app Python en Docker.
Salida esperada
🧠 Scanning video for key moments...
✨ Rendering 3D Intro (No Text)...
💾 Saving video file...
🔊 Adding silent audio track...
✅ Success! Intro saved as: intro_only.mp4Verificar el resultado
Tres comprobaciones antes de darlo por terminado.
La duración debería acercarse a los 9 segundos con los valores por defecto (6 clips × 1,5 s):
ffprobe -v error -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 intro_only.mp4El stream de audio debe existir y ser aac, que es la pista muda que el script multiplexa:
ffprobe -v error -select_streams a -show_entries stream=codec_name \
-of default=noprint_wrappers=1:nokey=1 intro_only.mp4Salida esperada: aac. Si no imprime nada, el paso del audio mudo falló y los editores se quejarán de una pista ausente.
Por último, la comprobación visual: open intro_only.mp4. Deberías ver seis cortes rápidos alternando deformaciones 3D a izquierda y derecha sobre un fondo desenfocado y oscurecido, con fundidos rápidos entre clips. Cuando falla: una duración lejos de 9 segundos significa que algunos clips dieron error durante el procesado (busca las líneas con ⚠️ en el terminal), y una imagen plana sin deformar significa que la cadena del filtro de perspectiva se rompió en una edición.
Personalizar el resultado
Edita el bloque de configuración al inicio del script para cambiar el estilo.
Cambiar el número de clips
INTRO_CLIP_COUNT = 8 # More clipsAjustar la velocidad
INTRO_SPEED = 2.0 # Slower
INTRO_SPEED = 4.0 # FasterCambiar la duración de los clips
CLIP_DURATION = 2.0 # Longer clips
CLIP_DURATION = 1.0 # Shorter clipsModificar la intensidad del desenfoque
Edita la cadena de filtro en apply_blur_background():
# Stronger blur
"-vf", "boxblur=60:10,eq=brightness=-0.5"
# Lighter blur
"-vf", "boxblur=20:3,eq=brightness=-0.2"Preajustes de estilo
Tres puntos de partida si no quieres ajustar cuatro mandos desde cero. Cada uno cambia el bloque de configuración, y los preajustes que mencionan desenfoque también tocan la cadena de filtro en apply_blur_background().
Por defecto (rápida)
INTRO_CLIP_COUNT = 6
INTRO_SPEED = 3.0
CLIP_DURATION = 1.5# in apply_blur_background()
"boxblur=40:5,eq=brightness=-0.4"Total: 6 × 1.5 = 9 segundos. El comportamiento original.
Relajada
INTRO_CLIP_COUNT = 4
INTRO_SPEED = 2.0
CLIP_DURATION = 2.0# in apply_blur_background()
"boxblur=20:3,eq=brightness=-0.2"Total: 8 segundos. Cortes más lentos, fondo más limpio, más fácil de seguir.
Cinematográfica
INTRO_CLIP_COUNT = 5
INTRO_SPEED = 1.5
CLIP_DURATION = 2.5# in apply_blur_background()
"boxblur=40:5,eq=brightness=-0.5"Total: 12.5 segundos. Movimiento más lento, fondo más oscuro, más espacio para un título superpuesto.
Son puntos de partida, no datos de calibración. El montaje se renderiza con pista de audio muda a propósito, así que superpón música o una voz en off en tu editor.
Dependencias explicadas
| Paquete | Propósito |
|---|---|
| openai-whisper | Kit de reconocimiento de voz; se importa arriba pero nunca se llama. Silero VAD hace la detección, así que puedes quitar esta línea y el import para una instalación más ligera |
| torch | PyTorch para ejecutar el modelo VAD |
| torchaudio | Procesamiento de audio con PyTorch; proporciona el helper read_audio que usa Silero VAD |
| soundfile | Backend de decodificación de audio que torchaudio puede usar al cargar el WAV |
| numpy | Operaciones numéricas |
| moviepy | Edición y composición de vídeo |
| Pillow | Procesamiento de imágenes (requerido por MoviePy) |
| packaging | Utilidades de gestión de versiones |
Restricciones de versión
Los pins de la cabecera PEP 723 son estructurales, no decoración:
moviepy==1.0.3: MoviePy 2.x eliminó el módulomoviepy.editor, así que el import falla en cualquier instalación 2.x. Mira «Por qué MoviePy está fijado a 1.0.3» más arriba.torchaudio<2.6: evita problemas de emparejamiento torch/torchaudio en Apple Silicon. La sección de solución de problemas de abajo cubre instalaciones lentas o fallidas.Pillow<10.0.0: MoviePy 1.0.3 aún referenciaImage.ANTIALIAS, que Pillow 10 eliminó.numpy<2.0.0: mantiene el stack de audio en la ABI de numpy 1.x que esperan esas builds de torch.
Solución de problemas
Error: FFmpeg no encontrado
Instala FFmpeg con Homebrew:
brew install ffmpegComprueba la instalación:
ffmpeg -versionAdvertencia: no hay metraje suficiente
Esto ocurre cuando el vídeo no tiene suficientes segmentos de habla de más de 2 segundos. El script reutiliza segmentos en ese caso. Prueba a:
- Usar un vídeo fuente más largo
- Reducir
INTRO_CLIP_COUNT - Bajar la duración mínima de segmento en el código
Errores de memoria con vídeos grandes
Para vídeos grandes, prueba a:
- Procesar clips más cortos
- Reducir la resolución del vídeo fuente
- Cerrar otras aplicaciones
La primera ejecución es lenta
La primera ejecución descarga el modelo Silero VAD y las dependencias de Python. Las ejecuciones posteriores son más rápidas porque uv y PyTorch cachean estos archivos.
ImportError: cannot import name 'editor' from 'moviepy'
Estás ejecutando MoviePy 2.x. Esto ocurre cuando el script se ejecuta con un intérprete distinto de uv run (un moviepy 2.x instalado con pip ensombrece al fijado) o cuando la cabecera PEP 723 perdió el pin moviepy==1.0.3 en una edición.
Soluciones:
- Ejecuta el script exactamente como se documenta:
uv run intro_generator.py your_video.mp4 - Confirma que la cabecera aún contiene
moviepy==1.0.3 - Si migraste a 2.x a propósito, actualiza la API:
moviepy.editorya no existe (importa los clips desdemoviepydirectamente),set_positionpasó awith_positiony la sintaxis.fx(vfx.speedx, ...)cambió
La instalación de torch o torchaudio falla o va lenta en Apple Silicon
Ve siguiendo estos pasos en orden:
- Confirma que el pin
torchaudio<2.6sigue en la cabecera. Un torchaudio sin pin puede traer una build que no coincide con tu versión de torch. - Limpia la caché y reintenta:
uv cache clean, y luego repite el mismo comandouv run. Una descarga a medias es la causa más común. - Comprueba que uv eligió un Python arm64 nativo:
uv python list. Un intérprete x86_64 corriendo bajo Rosetta produce instalaciones lentas y fallos aleatorios de wheels.
No se detecta voz en metraje solo con música o B-roll
Comportamiento esperado, no un bug: Silero VAD escucha voces, así que el metraje solo con música no produce marcas de tiempo y el script recurre a segmentos aleatorios espaciados cada 10 segundos.
Si quieres mejores elecciones en metraje silencioso o mixto:
- Baja el
thresholddel VAD de 0.5 a unos 0.4 enget_good_segments(). Más sensible, pero más falsos positivos. - Baja el filtro de segmento mínimo de 2.0 segundos en
generate_intro()para que califiquen ráfagas de voz más cortas. - Dale metraje de gente hablando. Para eso está hecho el detector.
Preguntas frecuentes
¿Silero VAD necesita clave de API o acceso a internet?
No. torch.hub descarga el modelo una vez en la primera ejecución; después, la detección de actividad de voz con Silero VAD corre completamente offline en tu CPU. No necesitas cuenta ni clave de API, y no hay facturación por minuto. El modelo ocupa unos dos megabytes, que es también por qué el arranque es rápido.
¿Funciona este editor de intros de vídeo en Python en Linux o Windows?
El script es Python puro, FFmpeg y paquetes pip, todo multiplataforma. Solo cambia el comando de instalación de FFmpeg: apt install ffmpeg en Debian/Ubuntu, winget install ffmpeg en Windows. Advertencia honesta: este tutorial está escrito en un Mac y no lo he vuelto a ejecutar en otros sistemas, así que trata Linux y Windows como «debería funcionar» hasta que lo ejecutes una vez tú mismo.
¿Por qué la intro renderizada es muda?
Por diseño. El script elimina el audio fuente y multiplexa una pista AAC muda para que el montaje encaje limpiamente bajo música o narración en tu editor sin pelear con el sonido original. Si quieres audio en el archivo, añádelo en postproducción o genera primero una voz en off.
¿Cuánto dura la intro?
INTRO_CLIP_COUNT × CLIP_DURATION. Los valores por defecto dan 6 × 1.5 = 9 segundos. Los preajustes de estilo de arriba producen 8 segundos (Relajada) y 12,5 segundos (Cinematográfica). Para una intro de YouTube, quedarse por debajo de 10 segundos es una buena regla; más que eso y los espectadores empiezan a saltársela.
Conclusión
Ese es todo el pipeline: un editor de intros de vídeo en Python donde Silero VAD encuentra los momentos hablados, FFmpeg hace el trabajo de desenfoque y perspectiva 3D, MoviePy compone los cortes y uv fija cada dependencia al propio archivo del script. Apúntalo a cualquier vídeo en bruto con gente hablando y obtienes un montaje de nueve segundos listo para colocar bajo una base musical.
Formas de llevarlo más lejos:
- La intro se renderiza muda a propósito, así que superpón música en tu editor, genera una voz en off con un script Python de texto a voz, o usa una opción alojada con clonación de voz como Fish Audio para una voz de narrador consistente sin ejecutar modelos en local.
- La CLI acepta un argumento, lo que la convierte en buena candidata para envolver este script en una UI de escritorio con NiceGUI: un selector de archivos, un botón de renderizado, una barra de progreso.
- El montaje solo puede lucir tan bien como su entrada. Screen.Studio automatiza zooms y ediciones para grabaciones de pantalla en macOS; la reseña completa de Screen.Studio cubre la versión larga.
- ¿Sin metraje que cortar? Las APIs de generación de vídeo con IA como Veo y Kling pueden producir b-roll desde un prompt, y Kie.ai pone esos modelos detrás de una única API.
- Cuando el vídeo esté listo, aloja la intro terminada y los vídeos con Bunny Stream en lugar de pelear con los embeds de las plataformas, y acompaña la intro con arte de miniatura de la guía sobre cómo generar imágenes con IA en local en Mac.


