Stylique's picture
Update server.js
03145d4 verified
// Try loading .env for local development, ignore if missing (production)
try { process.loadEnvFile?.(); } catch { } // Node 20+ native support
import express from 'express';
import cors from 'cors';
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';
import { readFile, rm } from 'fs/promises';
import { tmpdir } from 'os';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import os from 'os';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = express();
const PORT = process.env.PORT || 7860;
app.use(cors());
app.use(express.json({ limit: '50mb' }));
// Health check
app.get('/', (req, res) => {
res.json({ status: 'ok', service: 'FacelessFlowAI Video Renderer (Simple)' });
});
// Render endpoint
app.post('/render', async (req, res) => {
const { projectId, scenes, settings } = req.body;
if (!projectId || !scenes || !settings) {
return res.status(400).json({ error: 'Missing projectId, scenes, or settings' });
}
// Initialize Supabase Admin Client
const SUPABASE_URL = process.env.SUPABASE_URL;
const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE_KEY) {
console.error('[Config] Missing Supabase credentials');
return res.status(500).json({ error: 'Renderer configuration error' });
}
const { createClient } = await import('@supabase/supabase-js');
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY);
// 1. Respond immediately
res.json({ success: true, message: 'Rendering started in background' });
// 2. Start Background Process
(async () => {
try {
console.log(`[Render] Starting background render for project ${projectId}`);
// Available resources
const cpuCount = os.cpus().length;
const freeMem = (os.freemem() / 1024 / 1024).toFixed(0);
const totalMem = (os.totalmem() / 1024 / 1024).toFixed(0);
console.log(`[Resources] vCPUs: ${cpuCount} | RAM: ${freeMem}/${totalMem} MB Free`);
const bundleLocation = await bundle({
entryPoint: join(__dirname, 'remotion', 'index.tsx'),
webpackOverride: (config) => config,
});
// Clean Composition Params
const composition = await selectComposition({
serveUrl: bundleLocation,
id: 'Main',
inputProps: { scenes, settings }, // Direct Cloud URLs
chromiumOptions: { executablePath: process.env.CHROME_BIN },
});
const outputLocation = join(tmpdir(), `out-${projectId}.mp4`);
let lastLoggedPercent = -1;
// Simple Render
await renderMedia({
composition,
serveUrl: bundleLocation,
codec: 'h264',
pixelFormat: 'yuv420p',
outputLocation: outputLocation,
imageFormat: 'jpeg',
jpegQuality: 80,
inputProps: { scenes, settings },
chromiumOptions: {
executablePath: process.env.CHROME_BIN || '/usr/bin/google-chrome-stable',
enableMultiProcessRendering: true, // Requested: Enable Multi-process
args: [
'--no-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--mute-audio',
]
},
// Use all available cores (or set specific number)
concurrency: 1,
disallowParallelEncoding: false,
onProgress: ({ progress }) => {
const percent = Math.round(progress * 100);
if (percent !== lastLoggedPercent && percent % 5 === 0) {
console.log(`[Render] Progress: ${percent}%`);
lastLoggedPercent = percent;
}
},
});
console.log(`[Render] Render complete. Uploading to Supabase...`);
const videoBuffer = await readFile(outputLocation);
// Upload to Supabase Storage
const fileName = `${projectId}/video-${Date.now()}.mp4`;
const { data: uploadData, error: uploadError } = await supabase
.storage
.from('projects')
.upload(fileName, videoBuffer, {
contentType: 'video/mp4',
upsert: true
});
if (uploadError) throw new Error(`Upload failed: ${uploadError.message}`);
// Get Public URL
const { data: { publicUrl } } = supabase
.storage
.from('projects')
.getPublicUrl(fileName);
console.log(`[Render] Uploaded to ${publicUrl}`);
// Update Project in DB
const { error: dbError } = await supabase
.from('projects')
.update({
status: 'done',
video_url: publicUrl
})
.eq('id', projectId);
if (dbError) throw new Error(`DB Update failed: ${dbError.message}`);
console.log(`[Render] Project ${projectId} updated successfully`);
// Cleanup
await rm(outputLocation, { force: true });
} catch (error) {
console.error(`[Render] Background Error for ${projectId}:`, error);
await supabase
.from('projects')
.update({
status: 'error',
})
.eq('id', projectId);
}
})();
});
app.listen(PORT, () => {
console.log(`🎬 FacelessFlowAI Renderer running on port ${PORT}`);
// Debug: Check for Secrets on Startup
console.log('--- Environment Check ---');
console.log('SUPABASE_URL exists:', !!process.env.SUPABASE_URL);
console.log('SUPABASE_SERVICE_ROLE_KEY exists:', !!process.env.SUPABASE_SERVICE_ROLE_KEY);
});