Spaces:
Sleeping
Sleeping
File size: 4,451 Bytes
bc18ad5 23d4307 bc18ad5 23d4307 bc18ad5 fcf74ae bc18ad5 569f90a bc18ad5 fcf74ae bc18ad5 fcf74ae bc18ad5 fcf74ae bc18ad5 fcf74ae bc18ad5 fcf74ae bc18ad5 fcf74ae 23d4307 fcf74ae 23d4307 fcf74ae bc18ad5 23d4307 bc18ad5 23d4307 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
/**
* Render Module
*
* Handles Remotion video rendering with optimized settings.
* After render completes, uploads to Firebase Storage.
*/
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition, getCompositions } from '@remotion/renderer';
import path from 'path';
import fs from 'fs';
import { uploadToFirebase } from './firebase-admin';
const REMOTION_ENTRY = path.resolve(process.cwd(), 'remotion/index.tsx');
const VIDEOS_DIR = path.join(process.cwd(), 'videos');
const JOBS_DIR = path.join(process.cwd(), 'jobs');
const COMPOSITION_ID = 'VEditor';
// Configuration
const CONFIG = {
// Set concurrency to 1 to ensure stability in container environments
// os.cpus() can report host cores which are not accessible
concurrency: 1,
verbose: false,
timeoutMs: 300000, // 5 minutes
};
interface RenderOptions {
fps?: number;
width?: number;
height?: number;
format?: 'mp4' | 'webm';
}
// Helper to update job status file
function updateJobStatus(jobId: string, status: object): void {
const statusFile = path.join(JOBS_DIR, `${jobId}.status.json`);
fs.writeFileSync(statusFile, JSON.stringify(status));
}
export async function renderVideo(
jobId: string,
design: unknown,
options: RenderOptions
): Promise<string> {
console.log(`[Render] Job ${jobId}: Starting`);
console.log(`[Render] Concurrency: ${CONFIG.concurrency}`);
// Update status to bundling
updateJobStatus(jobId, { status: 'bundling', progress: 0 });
// Ensure output directory exists
if (!fs.existsSync(VIDEOS_DIR)) {
fs.mkdirSync(VIDEOS_DIR, { recursive: true });
}
// Bundle
console.log(`[Render] Bundling from: ${REMOTION_ENTRY}`);
const bundlePath = await bundle({
entryPoint: REMOTION_ENTRY,
});
console.log(`[Render] Bundle created: ${bundlePath}`);
// Select composition
const inputProps = { design };
const compositions = await getCompositions(bundlePath, { inputProps });
console.log(`[Render] Compositions: ${compositions.map(c => c.id).join(', ')}`);
const composition = await selectComposition({
serveUrl: bundlePath,
id: COMPOSITION_ID,
inputProps,
});
console.log(`[Render] Selected: ${composition.id} - ${composition.durationInFrames} frames`);
// Update status to rendering
updateJobStatus(jobId, { status: 'rendering', progress: 5 });
// Render
const format = options.format || 'mp4';
const outputPath = path.join(VIDEOS_DIR, `${jobId}.${format}`);
let lastReportedProgress = 0;
await renderMedia({
composition,
serveUrl: bundlePath,
codec: format === 'webm' ? 'vp8' : 'h264',
outputLocation: outputPath,
inputProps,
verbose: CONFIG.verbose,
concurrency: CONFIG.concurrency,
timeoutInMilliseconds: CONFIG.timeoutMs,
chromiumOptions: {
disableWebSecurity: true,
gl: 'angle',
},
onProgress: ({ progress, renderedFrames }) => {
const pct = Math.round(progress * 100);
// Only update if progress changed by at least 5%
if (pct >= lastReportedProgress + 5 || pct === 100) {
lastReportedProgress = pct;
console.log(`[Render] Progress: ${pct}% (${renderedFrames}/${composition.durationInFrames})`);
// Update status file with current progress (max 90% for render, 10% for upload)
updateJobStatus(jobId, {
status: 'rendering',
progress: Math.min(pct * 0.9, 90),
renderedFrames,
totalFrames: composition.durationInFrames
});
}
},
});
console.log(`[Render] Render completed: ${outputPath}`);
// Upload to Firebase Storage
updateJobStatus(jobId, { status: 'uploading', progress: 92 });
console.log(`[Render] Uploading to Firebase...`);
const firebaseUrl = await uploadToFirebase(outputPath, `exports/${jobId}.${format}`);
console.log(`[Render] Upload completed: ${firebaseUrl}`);
// Clean up local file to save space
try {
fs.unlinkSync(outputPath);
console.log(`[Render] Cleaned up local file: ${outputPath}`);
} catch (e) {
console.warn(`[Render] Failed to clean up local file: ${outputPath}`);
}
return firebaseUrl;
}
|