Sistema de Cola de Rendering
El sistema automáticamente encola el renderizado de videos cuando todos los clips están procesados.
Flujo Completo
1. POST /api/videos
↓
2. Clips se encolan (CLIP_PROCESSING queue)
↓
3. Workers procesan clips en paralelo (TTS, etc)
↓
4. Último clip procesado → Video READY
↓
5. Se envía webhook (si está configurado)
↓
6. Se encola en VIDEO_RENDERING queue
↓
7. Render worker procesa el video
↓
8. Video generado → Status COMPLETED
Colas en RabbitMQ
El sistema utiliza 3 colas:
1. CLIP_PROCESSING (alta prioridad)
- Procesa clips individuales (TTS, metadata)
- Workers: 5 por defecto
- Concurrencia: 5 clips simultáneos por worker
2. VIDEO_PROCESSING (legacy)
- Procesamiento secuencial de todos los clips de un video
- Workers: 2 por defecto
- Uso: Solo para estrategia "sequential"
3. VIDEO_RENDERING (nueva)
- Renderizado del video final con Remotion/ffmpeg
- Workers: Configurables
- Se activa automáticamente cuando video está READY
Cuando un Video está READY
Automáticamente se ejecutan estas acciones:
1. Enviar Webhook (si está configurado)
if (video.webhookUrl) {
await webhookService.sendVideoReadyWebhook(videoId);
}
2. Encolar Rendering
await videoQueue.enqueueVideoRendering(videoId);
Ambas acciones ocurren automáticamente, no necesitas hacer nada.
Render Worker (Próximo Paso)
El render worker aún no está implementado. Cuando lo implementes, debe:
- Consumir la cola
VIDEO_RENDERING - Obtener el JSON final del video
- Renderizar el video con Remotion o ffmpeg
- Actualizar el estado a
RENDERING→COMPLETED - Guardar la URL del video generado
Ejemplo de Render Worker
// app/workers/render-worker.server.ts
export async function startRenderWorker() {
const channel = await getRabbitMQChannel();
await channel.assertQueue(QUEUES.VIDEO_RENDERING, { durable: true });
await channel.prefetch(2); // 2 renders simultáneos
channel.consume(QUEUES.VIDEO_RENDERING, async (msg) => {
const job: VideoRenderingJob = JSON.parse(msg.content.toString());
try {
// 1. Marcar como RENDERING
await prisma.videoConfiguration.update({
where: { id: job.videoConfigId },
data: { status: 'RENDERING' }
});
// 2. Obtener JSON final
const finalJSON = await clipProcessor.buildFinalJSON(job.videoConfigId);
// 3. Renderizar con Remotion
const videoUrl = await renderWithRemotion(finalJSON);
// 4. Marcar como COMPLETED
await prisma.videoConfiguration.update({
where: { id: job.videoConfigId },
data: {
status: 'COMPLETED',
videoUrl: videoUrl
}
});
channel.ack(msg);
} catch (error) {
console.error('Render failed:', error);
await prisma.videoConfiguration.update({
where: { id: job.videoConfigId },
data: { status: 'FAILED' }
});
channel.nack(msg, false, true); // Reintenta
}
});
}
Verificar Cola de Rendering
Ver mensajes pendientes
# Con PM2 logs
pm2 logs render-worker
# O con script de estadísticas
npx tsx scripts/queue-stats.ts
Crear script de estadísticas
// scripts/queue-stats.ts
import { videoQueue } from '../app/services/video-queue.server';
const stats = await videoQueue.getQueueStats();
console.log('Video Processing:', stats.videoQueue.messages, 'messages');
console.log('Clip Processing:', stats.clipQueue.messages, 'messages');
console.log('Video Rendering:', stats.renderQueue.messages, 'messages');
Estados del Video
PENDING → Clips creados, esperando procesamiento
PROCESSING → Procesando clips
READY → Todos los clips listos, JSON disponible (encola rendering)
RENDERING → Renderizando video (worker procesa)
COMPLETED → Video renderizado exitosamente
FAILED → Error en procesamiento o rendering
CANCELLED → Cancelado por el usuario
Comportamiento Actual
Sin render worker:
- Videos llegan a estado
READY - Se envía webhook con JSON final
- Se encola en
VIDEO_RENDERING - Pero no se procesa (no hay worker escuchando)
Con render worker:
- Videos llegan a
READY - Se encola en
VIDEO_RENDERING - Render worker toma el job
- Renderiza el video
- Marca como
COMPLETED
Configuración
Variables de Entorno
# Número de renders simultáneos por worker
RENDER_WORKER_CONCURRENCY=2
# Timeout para rendering (en minutos)
RENDER_TIMEOUT=30
PM2 Ecosystem
Añade el render worker a ecosystem.config.cjs:
{
name: 'render-worker',
script: './node_modules/.bin/tsx',
args: 'scripts/start-render-worker.ts',
instances: 2, // 2 renders simultáneos
env: {
RENDER_WORKER_CONCURRENCY: '1', // 1 video por instancia
RENDER_TIMEOUT: '30'
},
max_memory_restart: '2G' // Rendering usa más memoria
}
Ejemplo Completo
1. Usuario envía video
curl -X POST http://localhost:5173/api/videos \
-H "Authorization: Bearer api-key" \
-d '{
"webhookUrl": "https://mi-servidor.com/webhook",
"output": { "fps": 30, "width": 1920, "height": 1080 },
"timeline": [
{ "asset": { "type": "tts", "text": "Hola" }, "start": 0, "length": "{{audio.duration}}" }
]
}'
# Respuesta: { "videoId": "cmg6..." }
2. Clips se procesan automáticamente
[ClipWorker] Processing clip cmg6...
[ClipProcessor] Generating TTS audio...
[ClipWorker] Clip READY
3. Video READY → Webhook + Rendering
[ClipWorker] Video cmg6... completed with status: READY
[ClipWorker] Sending webhook for video cmg6...
[Webhook] Sending webhook to https://mi-servidor.com/webhook
[ClipWorker] Enqueuing rendering for video cmg6...
[VideoQueue] Enqueued video rendering: cmg6...
4. Render worker procesa (futuro)
[RenderWorker] Processing video cmg6...
[RenderWorker] Rendering with Remotion...
[RenderWorker] Video rendered successfully
[RenderWorker] Video cmg6... completed
Prioridades de Cola
Si tienes muchos videos pendientes, puedes configurar prioridades:
// Alta prioridad para rendering
channel.sendToQueue(
QUEUES.VIDEO_RENDERING,
Buffer.from(JSON.stringify(job)),
{
persistent: true,
priority: 10 // Mayor = más prioridad
}
);
Monitoreo
CloudAMQP Dashboard
Ve a https://customer.cloudamqp.com para ver:
- Mensajes en
video-renderingqueue - Consumers activos
- Throughput
Logs
pm2 logs render-worker
pm2 logs clip-worker
Próximos Pasos
Para completar el sistema de rendering:
- ✅ Cola
VIDEO_RENDERINGcreada - ✅ Auto-encolado cuando video READY
- ❌ Falta: Crear render worker
- ❌ Falta: Integrar Remotion o ffmpeg
- ❌ Falta: Subir videos a S3 o storage
¡El sistema está listo para que implementes el render worker! 🎬