Desglose técnico de
MoneyPrinterTurbo
Un generador automático de vídeos cortos impulsado por IA. Ingresa un tema y el sistema orquesta una pipeline completa: generación de guion con LLM, síntesis de voz, búsqueda de stock footage, subtitulado automático y composición final — todo sin intervención manual.
Arquitectura del sistema
El proyecto sigue un patrón MVC (Model-View-Controller) clásico, con FastAPI como backend REST, Streamlit como interfaz web, y una cola de tareas asíncrona para ejecutar los pipelines de generación de vídeo.
- Provider abstraction — Cada servicio (LLM, TTS) define una interfaz común y los providers concretos se seleccionan por configuración.
- Pipeline state machine — El orquestador (
task.py) avanza paso a paso; cada etapa puede ser un punto de parada (stop_at). - Task queue — Las solicitudes de generación se encolan con
ThreadPoolExecutory opcionalmente Redis para persistencia. - Graceful degradation — Si un provider TTS falla, se intenta con el siguiente; si no hay stock footage, se usan clips locales o color sólido.
Pipeline de generación de vídeo
El corazón del proyecto. Un flujo de 6 etapas encadenadas que transforman un tema cualquiera en un vídeo corto completo con voz, música, subtítulos y transiciones.
Si no se proporciona un guion manual, el sistema llama al LLM configurado para redactar un texto adaptado al formato de vídeo corto. Soporta más de 20 providers con una interfaz unificada: OpenAI, Gemini, DeepSeek, Groq, Claude (vía LiteLLM), Ollama (local), y muchos más.
def generate_script(params: VideoParams) -> str: prompt = build_script_prompt( subject=params.video_subject, paragraphs=params.paragraph_number, language=params.video_language ) for attempt in range(5): try: resp = llm_request(prompt) return clean_script(resp) except Exception as e: logger.warning(f"Attempt {attempt+1} failed: {e}") raise RuntimeError("Script generation failed")
El prompt incluye el tema, número de párrafos, idioma y un system prompt que instruye al modelo a escribir para formato de vídeo corto (oraciones directas, transiciones naturales). La respuesta se limpia de caracteres markdown antes de devolverla.
Una vez listo el guion, se pide al LLM que genere entre 5 y 8 keywords
en inglés para buscar stock footage. Con la opción match_materials_to_script,
los términos se ordenan cronológicamente para que el metraje coincida con la narrativa.
def generate_terms(script: str, style: str, language: str) -> list: prompt = f"Generate 5-8 English search terms for stock video about:\n{script}" for attempt in range(5): raw = llm_request(prompt) try: terms = json.loads(raw) if isinstance(terms, list) and len(terms) >= 3: return terms except json.JSONDecodeError: match = re.findall(r'"([^"]+)"', raw) if match: return match
El LLM devuelve un array JSON. Si el parseo falla, un regex extrae los strings entre comillas como fallback. Esto asegura robustez ante respuestas mal formateadas.
El texto del guion se convierte en audio mediante uno de 6 motores TTS. El sistema selecciona el provider según la configuración y maneja automáticamente la obtención de timestamps palabra por palabra para sincronizar subtítulos.
def tts(params: VideoParams) -> tuple[str, SubMaker]: provider = params.tts_provider if provider == "edge": return edge_tts_v1(params) elif provider == "azure": return azure_tts_v2(params) elif provider == "siliconflow": return siliconflow_tts(params) elif provider == "gemini": return gemini_tts(params) elif provider == "mimo": return mimo_tts(params) else: return no_voice(params) # silencio + duración estimada
edge-tts library · 470+ voces
gemini-2.5-flash-preview-tts · base64 inline
anullsrc · duración estimada
Todos los providers devuelven un objeto SubMaker que contiene los timestamps
palabra por palabra, esenciales para la generación precisa de subtítulos. Edge TTS los
obtiene via eventos WordBoundary; Azure via callbacks del SDK.
Dos providers disponibles: edge (usa los cues de Edge TTS, rápido, sin GPU)
y whisper (ejecuta faster-whisper localmente, más preciso
pero requiere descarga del modelo ~3GB).
def generate_subtitle(audio_path: str, sub_maker: SubMaker, provider: str) -> str: if provider == "edge": return sub_maker.generate_srt() elif provider == "whisper": model = WhisperModel("large-v3", device="cuda", compute_type="float16") segments, _ = model.transcribe(audio_path) return segments_to_srt(segments)
Ambos caminos convergen en una función de corrección post-procesado que alinea los textos del SRT con el guion original usando distancia Levenshtein, asegurando que los subtítulos coincidan exactamente con lo que el LLM generó.
Para cada término de búsqueda, el sistema consulta 3 APIs de stock video (Pexels, Pixabay, Coverr), descarga los clips de mayor resolución y los almacena en caché con un sistema de deduplicación por hash MD5.
def get_video_materials(params: VideoParams) -> list[MaterialInfo]: materials = [] source = params.video_source # "pexels" | "pixabay" | "coverr" | "local" for keyword in params.video_terms: api_clips = search_pexels(keyword) if source == "pexels" else ... for clip in api_clips: hash_key = hashlib.md5(clip.url.encode()).hexdigest() if hash_key in seen: continue seen.add(hash_key) if download_video(clip.url, dest): materials.append(MaterialInfo(...)) return materials
api.pexels.com/videos/search · API key · filtro orientación
pixabay.com/api/videos/ · API key · filtro width
api.coverr.co/videos · API key · signed URLs
storage/local_videos/
Los clips descargados se validan con MoviePy (duración > 0, FPS > 0). Con
match_materials_to_script, los clips se asignan a segmentos específicos
del guion en orden cronológico.
La etapa más compleja. MoviePy compone todos los elementos: clips de vídeo, transiciones, subtítulos sincronizados, voz superpuesta y música de fondo. El resultado se codifica con FFmpeg usando el codec configurado.
- Cada clip fuente se divide en segmentos de
max_clip_durationsegundos - Se prioriza diversidad: cada clip aparece una vez antes de repetir
- Los segmentos se renderizan individualmente con la transición elegida (FadeIn, SlideOut, Shuffle…)
- Se concatenan via FFmpeg concat demuxer
- Si la duración total < audio, los clips se loopan para llenar
- Los subtítulos se renderizan frame a frame como TextClip con wrapping vía PIL
- La música de fondo se mezcla con volumen escalado y fadeout
- Salida:
final-{n}.mp4+combined-{n}.mp4
Soporta dos relaciones de aspecto: 9:16 (1080×1920, vertical/portrait) y
16:9 (1920×1080, horizontal/landscape). Los clips con relación distinta
se centran con barras negras (letterbox). El codec de video se configura vía
video_codec (libx264, NVENC, AMF, QSV, VideoToolbox).
Web Interface (Streamlit)
Una aplicación Streamlit de página única que expone toda la funcionalidad del pipeline en una UI de tres columnas con configuración en vivo y logs en tiempo real.
El panel inferior colapsable ("Basic Settings") permite configurar el provider LLM y las API keys de los servicios de stock video sin salir de la interfaz.
La UI carga textos desde archivos JSON en webui/i18n/. Soporta
8 idiomas: español, inglés, chino, alemán, ruso, vietnamita, turco y portugués.
La selección de idioma persiste en la sesión de Streamlit.
Durante la generación, Loguru emite logs estructurados que se capturan y muestran en un bloque de código dentro de Streamlit. El usuario ve en tiempo real qué etapa se está ejecutando, qué provider TTS se usó, cuántos clips se descargaron, etc.
Configuración y despliegue
Toda la configuración reside en un archivo TOML con secciones para la aplicación, providers LLM, TTS, stock video y opciones de UI.
# Proveedor LLM principal llm_provider = "openai" openai_api_key = "sk-..." openai_base_url = "https://api.openai.com/v1" openai_model_name = "gpt-4o-mini" # Stock video video_source = "pexels" pexels_api_keys = ["..."] pixabay_api_keys = ["..."] coverr_api_keys = ["..."] # TTS tts_provider = "edge" subtitle_provider = "edge" # Whisper (local STT) [whisper] model_size = "large-v3" device = "CPU" compute_type = "int8"
uvicorn app.asgi:app + streamlit run webui/Main.py
python cli.py --subject "Tema" para generación headless
enable_redis=true)
Tecnologías y dependencias
El stack técnico completo del proyecto, con las versiones utilizadas y el rol de cada componente en la arquitectura.
| Dependencia | Versión | Función |
|---|---|---|
| Python | 3.11 | Runtime principal |
| FastAPI | 0.136.3 | Framework REST API |
| Uvicorn | 0.32.1 | Servidor ASGI |
| Streamlit | 1.58.0 | Interfaz web de usuario |
| MoviePy | 2.2.1 | Composición y edición de vídeo |
| FFmpeg | — | Codificación y concat de video |
| Pillow (PIL) | — | Medición de texto y wrapping para subtítulos |
| edge-tts | 7.2.7 | Síntesis de voz gratuita (Azure Cognitive Services edge) |
| faster-whisper | 1.1.0 | Speech-to-text local (subtítulos por Whisper) |
| pydub | 0.25.1 | Manipulación de audio (Gemini/MiMo TTS) |
| openai | — | SDK para OpenAI y providers compatibles |
| google-generativeai | — | SDK para Gemini (LLM + TTS) |
| LiteLLM | — | Gateway unificado para 100+ providers LLM |
| dashscope | — | SDK para Qwen (通义千问) |
| azure-cognitiveservices-speech | — | SDK Azure Speech (TTS V2) |
| httpx | — | Cliente HTTP async para API externas |
| loguru | 0.7.3 | Logging estructurado |
| toml | — | Parseo de configuración |
| pydantic | — | Validación de esquemas y datos |
| redis | — | Cola de tareas persistente (opcional) |
| requests | 2.33.1 | HTTP para APIs de stock video |
| uv | — | Gestor de paquetes y entornos |
| Docker | — | Containerización (CPU + GPU variants) |
- Provider rotator — Las API keys de Pexels/Pixabay se rotan automáticamente si se proporcionan múltiples, distribuyendo la carga.
- Dual-mode subtitle — Edge TTS para velocidad (sin GPU) vs Whisper para precisión (GPU recomendada). El sistema hace fallback automático.
- Codec fallback chain — Si el codec HW (NVENC/AMF/QSV) falla, degrada a libx264 automáticamente.
- Graceful TTS degradation — Si el TTS principal falla tras reintentos, el sistema puede continuar con silencio ("No Voice") en lugar de abortar.