Stylique commited on
Commit
47e0e60
·
verified ·
1 Parent(s): 0243f01

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +199 -96
server.js CHANGED
@@ -2,7 +2,7 @@ import express from 'express';
2
  import cors from 'cors';
3
  import { bundle } from '@remotion/bundler';
4
  import { renderMedia, selectComposition } from '@remotion/renderer';
5
- import { readFile, mkdir, rm } from 'fs/promises';
6
  import { tmpdir } from 'os';
7
  import { join, dirname } from 'path';
8
  import { randomUUID } from 'crypto';
@@ -26,12 +26,11 @@ const BUNDLE_CACHE_TTL = 5 * 60 * 1000;
26
  app.get('/', (req, res) => {
27
  res.json({
28
  status: 'ok',
29
- service: 'FacelessFlowAI Video Renderer',
30
- gpu_available: process.env.COMPUTE_TYPE === 'gpu'
31
  });
32
  });
33
 
34
- // Get bundle with caching
35
  async function getCachedBundle() {
36
  const now = Date.now();
37
 
@@ -42,68 +41,64 @@ async function getCachedBundle() {
42
 
43
  console.log('[Performance] Creating new bundle');
44
 
45
- cachedBundle = await bundle({
46
- entryPoint: join(__dirname, 'remotion', 'index.tsx'),
47
- webpackOverride: (config) => {
48
- config.output = {
49
- ...config.output,
50
- filename: '[name].js',
51
- chunkFilename: '[name].chunk.js',
52
- };
53
-
54
- config.optimization = {
55
- ...config.optimization,
56
- splitChunks: false,
57
- runtimeChunk: false,
58
- };
59
-
60
- config.mode = 'production';
61
- config.devtool = false;
62
-
63
- return config;
64
- },
65
- });
66
-
67
- lastBundleTime = now;
68
- return cachedBundle;
69
- }
70
-
71
- // Check if GPU is available
72
- function isGpuAvailable() {
73
- return process.env.COMPUTE_TYPE === 'gpu' ||
74
- process.env.CUDA_VISIBLE_DEVICES !== undefined;
75
- }
76
-
77
- // Get optimized chromium options
78
- function getChromiumOptions() {
79
- const baseOptions = {
80
- executablePath: process.env.CHROME_BIN || '/usr/bin/google-chrome-stable',
81
- enableMultiProcessRendering: true,
82
- ignoreDefaultArgs: ['--disable-gpu'],
83
- args: ['--no-sandbox', '--disable-dev-shm-usage']
84
- };
85
-
86
- return baseOptions;
87
- }
88
-
89
- // Get FFmpeg override
90
- function getFFmpegOverride() {
91
- return ({ args }) => {
92
- return [
93
- ...args,
94
- '-c:v', 'libx264',
95
- '-preset', 'fast',
96
- '-crf', '23',
97
- '-movflags', '+faststart',
98
- ];
99
- };
100
  }
101
 
102
- // Calculate optimal concurrency
103
- async function getOptimalConcurrency() {
104
- const os = await import('os');
105
- const cpuCount = os.cpus().length;
106
- return Math.min(cpuCount, 8);
 
 
 
 
 
107
  }
108
 
109
  // Render endpoint
@@ -129,8 +124,7 @@ app.post('/render', async (req, res) => {
129
  // 1. Respond immediately
130
  res.json({
131
  success: true,
132
- message: 'Rendering started in background',
133
- gpu: isGpuAvailable() ? 'enabled' : 'disabled'
134
  });
135
 
136
  // 2. Start Background Process
@@ -139,16 +133,58 @@ app.post('/render', async (req, res) => {
139
 
140
  try {
141
  console.log(`[Render] Starting render for project ${projectId}`);
142
- console.log(`[Hardware] GPU: ${isGpuAvailable() ? 'Yes' : 'No'}`);
 
 
 
 
 
143
 
144
  const bundleLocation = await getCachedBundle();
145
  console.log('[Bundle] Bundle ready');
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  const composition = await selectComposition({
148
  serveUrl: bundleLocation,
149
  id: 'Main',
150
  inputProps: { scenes, settings },
151
- chromiumOptions: getChromiumOptions(),
 
 
 
 
 
 
 
152
  });
153
 
154
  console.log(`[Composition] Loaded: ${composition.durationInFrames} frames at ${composition.fps}fps`);
@@ -157,11 +193,6 @@ app.post('/render', async (req, res) => {
157
  await mkdir(tempDir, { recursive: true });
158
  const outputPath = join(tempDir, 'output.mp4');
159
 
160
- const concurrency = await getOptimalConcurrency();
161
- console.log(`[Concurrency] Using ${concurrency} threads`);
162
-
163
- let lastLog = 0;
164
-
165
  console.log('[Render] Starting video rendering...');
166
 
167
  await renderMedia({
@@ -173,19 +204,42 @@ app.post('/render', async (req, res) => {
173
  imageFormat: 'jpeg',
174
  jpegQuality: 80,
175
  inputProps: { scenes, settings },
176
- chromiumOptions: getChromiumOptions(),
177
- concurrency: concurrency,
 
 
 
178
  disallowParallelEncoding: false,
179
  onProgress: ({ progress }) => {
180
  const percent = Math.round(progress * 100);
181
- if (percent - lastLog >= 5 || percent === 100) {
182
- console.log(`[Progress] ${percent}%`);
183
- lastLog = percent;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  }
185
  },
186
- ffmpegOverride: getFFmpegOverride(),
187
  audioBitrate: '192k',
188
- videoBitrate: '4M',
189
  });
190
 
191
  const renderTime = Date.now() - startTime;
@@ -237,7 +291,7 @@ app.post('/render', async (req, res) => {
237
  await rm(tempDir, { recursive: true, force: true });
238
 
239
  } catch (error) {
240
- console.error(`[Render] Error for ${projectId}:`, error);
241
 
242
  // Update project status to 'error'
243
  try {
@@ -255,13 +309,27 @@ app.post('/render', async (req, res) => {
255
  })();
256
  });
257
 
258
- // Performance endpoint
259
  app.get('/status', async (req, res) => {
260
  const os = await import('os');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
262
  res.json({
263
  status: 'running',
264
- gpu: isGpuAvailable(),
265
  cpu_cores: os.cpus().length,
266
  total_memory: Math.round(os.totalmem() / (1024 * 1024 * 1024)) + 'GB',
267
  free_memory: Math.round(os.freemem() / (1024 * 1024 * 1024)) + 'GB',
@@ -270,19 +338,54 @@ app.get('/status', async (req, res) => {
270
  });
271
  });
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  // Start server
274
- (async () => {
 
 
275
  const os = await import('os');
 
 
276
 
277
- app.listen(PORT, () => {
278
- console.log(`🎬 FacelessFlowAI Renderer running on port ${PORT}`);
279
- console.log(`⚡ GPU Available: ${isGpuAvailable() ? 'Yes' : 'No'}`);
280
- console.log(`💾 Memory: ${Math.round(os.totalmem() / (1024 * 1024 * 1024))}GB`);
281
-
282
- // Environment check
283
- console.log('--- Environment ---');
284
- console.log('CHROME_BIN:', process.env.CHROME_BIN || 'default');
285
- console.log('COMPUTE_TYPE:', process.env.COMPUTE_TYPE || 'cpu');
286
- console.log('------------------');
287
- });
288
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import cors from 'cors';
3
  import { bundle } from '@remotion/bundler';
4
  import { renderMedia, selectComposition } from '@remotion/renderer';
5
+ import { readFile, mkdir, rm, existsSync } from 'fs/promises';
6
  import { tmpdir } from 'os';
7
  import { join, dirname } from 'path';
8
  import { randomUUID } from 'crypto';
 
26
  app.get('/', (req, res) => {
27
  res.json({
28
  status: 'ok',
29
+ service: 'FacelessFlowAI Video Renderer'
 
30
  });
31
  });
32
 
33
+ // Get bundle with caching - IMPROVED VERSION
34
  async function getCachedBundle() {
35
  const now = Date.now();
36
 
 
41
 
42
  console.log('[Performance] Creating new bundle');
43
 
44
+ try {
45
+ // Clean up old bundle cache
46
+ if (cachedBundle && cachedBundle.startsWith('/tmp/remotion-webpack-bundle-')) {
47
+ try {
48
+ await rm(cachedBundle, { recursive: true, force: true });
49
+ } catch (e) {
50
+ // Ignore cleanup errors
51
+ }
52
+ }
53
+
54
+ // Create bundle with explicit output configuration
55
+ cachedBundle = await bundle({
56
+ entryPoint: join(__dirname, 'remotion', 'index.tsx'),
57
+ webpackOverride: (config) => {
58
+ // Ensure bundle.js is created properly
59
+ config.output = {
60
+ ...config.output,
61
+ path: join(__dirname, 'dist'),
62
+ filename: 'bundle.js',
63
+ publicPath: '/',
64
+ };
65
+
66
+ // Disable code splitting to ensure single bundle
67
+ config.optimization = {
68
+ ...config.optimization,
69
+ splitChunks: false,
70
+ runtimeChunk: false,
71
+ };
72
+
73
+ // Production optimizations
74
+ config.mode = 'production';
75
+ config.devtool = false;
76
+
77
+ return config;
78
+ },
79
+ // Force fresh build
80
+ ignoreRegisterRootWarning: true,
81
+ });
82
+
83
+ console.log(`[Bundle] Bundle created at: ${cachedBundle}`);
84
+ lastBundleTime = now;
85
+ return cachedBundle;
86
+ } catch (error) {
87
+ console.error('[Bundle] Error creating bundle:', error);
88
+ throw error;
89
+ }
 
 
 
 
 
 
 
 
 
90
  }
91
 
92
+ // Function to check if Chrome is available
93
+ async function checkChrome() {
94
+ try {
95
+ const { execSync } = await import('child_process');
96
+ execSync(`${process.env.CHROME_BIN || '/usr/bin/google-chrome-stable'} --version`, { stdio: 'pipe' });
97
+ return true;
98
+ } catch (error) {
99
+ console.log('[Chrome] Chrome not found, will download headless shell');
100
+ return false;
101
+ }
102
  }
103
 
104
  // Render endpoint
 
124
  // 1. Respond immediately
125
  res.json({
126
  success: true,
127
+ message: 'Rendering started in background'
 
128
  });
129
 
130
  // 2. Start Background Process
 
133
 
134
  try {
135
  console.log(`[Render] Starting render for project ${projectId}`);
136
+
137
+ // Check Chrome before proceeding
138
+ const chromeAvailable = await checkChrome();
139
+ if (!chromeAvailable) {
140
+ console.log('[Chrome] Using Remotion headless shell');
141
+ }
142
 
143
  const bundleLocation = await getCachedBundle();
144
  console.log('[Bundle] Bundle ready');
145
 
146
+ // Verify bundle exists
147
+ try {
148
+ await readFile(join(bundleLocation, 'bundle.js'));
149
+ console.log('[Bundle] bundle.js verified');
150
+ } catch (error) {
151
+ console.error('[Bundle] bundle.js not found, checking alternative location');
152
+ // Try to find bundle.js in parent directory
153
+ const possiblePaths = [
154
+ join(bundleLocation, 'bundle.js'),
155
+ join(dirname(bundleLocation), 'bundle.js'),
156
+ bundleLocation // In case it's already the file path
157
+ ];
158
+
159
+ let found = false;
160
+ for (const path of possiblePaths) {
161
+ try {
162
+ await readFile(path);
163
+ console.log(`[Bundle] Found at: ${path}`);
164
+ found = true;
165
+ break;
166
+ } catch (e) {
167
+ // Continue checking
168
+ }
169
+ }
170
+
171
+ if (!found) {
172
+ throw new Error(`Bundle not found at ${bundleLocation}`);
173
+ }
174
+ }
175
+
176
  const composition = await selectComposition({
177
  serveUrl: bundleLocation,
178
  id: 'Main',
179
  inputProps: { scenes, settings },
180
+ chromiumOptions: {
181
+ executablePath: process.env.CHROME_BIN || '/usr/bin/google-chrome-stable',
182
+ args: ['--no-sandbox', '--disable-dev-shm-usage'],
183
+ // Prevent downloading if Chrome is available
184
+ onBrowserDownload: chromeAvailable ? undefined : (progress) => {
185
+ console.log(`[Chrome] Downloading headless shell: ${(progress.downloadedBytes / 1024 / 1024).toFixed(1)}Mb/${(progress.totalSize / 1024 / 1024).toFixed(1)}Mb`);
186
+ }
187
+ },
188
  });
189
 
190
  console.log(`[Composition] Loaded: ${composition.durationInFrames} frames at ${composition.fps}fps`);
 
193
  await mkdir(tempDir, { recursive: true });
194
  const outputPath = join(tempDir, 'output.mp4');
195
 
 
 
 
 
 
196
  console.log('[Render] Starting video rendering...');
197
 
198
  await renderMedia({
 
204
  imageFormat: 'jpeg',
205
  jpegQuality: 80,
206
  inputProps: { scenes, settings },
207
+ chromiumOptions: {
208
+ executablePath: process.env.CHROME_BIN || '/usr/bin/google-chrome-stable',
209
+ args: ['--no-sandbox', '--disable-dev-shm-usage']
210
+ },
211
+ concurrency: 8, // Increased for L4
212
  disallowParallelEncoding: false,
213
  onProgress: ({ progress }) => {
214
  const percent = Math.round(progress * 100);
215
+ console.log(`[Progress] ${percent}%`);
216
+ },
217
+ ffmpegOverride: ({ args }) => {
218
+ // Try to use NVENC if available
219
+ try {
220
+ const { execSync } = require('child_process');
221
+ execSync('ffmpeg -encoders | grep nvenc', { stdio: 'pipe' });
222
+ console.log('[FFmpeg] Using NVENC hardware encoding');
223
+ return [
224
+ ...args,
225
+ '-c:v', 'h264_nvenc',
226
+ '-preset', 'p1',
227
+ '-cq', '23',
228
+ '-movflags', '+faststart',
229
+ ];
230
+ } catch (e) {
231
+ console.log('[FFmpeg] Using software encoding (libx264)');
232
+ return [
233
+ ...args,
234
+ '-c:v', 'libx264',
235
+ '-preset', 'fast',
236
+ '-crf', '23',
237
+ '-movflags', '+faststart',
238
+ ];
239
  }
240
  },
 
241
  audioBitrate: '192k',
242
+ videoBitrate: '8M', // Higher for better quality
243
  });
244
 
245
  const renderTime = Date.now() - startTime;
 
291
  await rm(tempDir, { recursive: true, force: true });
292
 
293
  } catch (error) {
294
+ console.error(`[Render] Error for ${projectId}:`, error.message);
295
 
296
  // Update project status to 'error'
297
  try {
 
309
  })();
310
  });
311
 
312
+ // Status endpoint
313
  app.get('/status', async (req, res) => {
314
  const os = await import('os');
315
+ const { execSync } = await import('child_process');
316
+
317
+ let gpuInfo = 'Not detected';
318
+ try {
319
+ execSync('nvidia-smi --query-gpu=name --format=csv,noheader', { stdio: 'pipe' });
320
+ gpuInfo = 'NVIDIA GPU detected';
321
+ } catch (e) {
322
+ try {
323
+ execSync('lspci | grep -i vga', { stdio: 'pipe' });
324
+ gpuInfo = 'GPU detected (non-NVIDIA)';
325
+ } catch (e2) {
326
+ gpuInfo = 'No GPU detected';
327
+ }
328
+ }
329
 
330
  res.json({
331
  status: 'running',
332
+ gpu: gpuInfo,
333
  cpu_cores: os.cpus().length,
334
  total_memory: Math.round(os.totalmem() / (1024 * 1024 * 1024)) + 'GB',
335
  free_memory: Math.round(os.freemem() / (1024 * 1024 * 1024)) + 'GB',
 
338
  });
339
  });
340
 
341
+ // Warm-up endpoint to pre-download Chrome
342
+ app.get('/warmup', async (req, res) => {
343
+ try {
344
+ console.log('[Warmup] Starting warm-up...');
345
+ await getCachedBundle();
346
+ console.log('[Warmup] Bundle ready');
347
+
348
+ // Trigger Chrome check to pre-download if needed
349
+ await checkChrome();
350
+ console.log('[Warmup] Chrome ready');
351
+
352
+ res.json({ status: 'warmed_up' });
353
+ } catch (error) {
354
+ console.error('[Warmup] Error:', error.message);
355
+ res.status(500).json({ error: error.message });
356
+ }
357
+ });
358
+
359
  // Start server
360
+ app.listen(PORT, async () => {
361
+ console.log(`🎬 FacelessFlowAI Renderer running on port ${PORT}`);
362
+
363
  const os = await import('os');
364
+ console.log(`💾 Memory: ${Math.round(os.totalmem() / (1024 * 1024 * 1024))}GB`);
365
+ console.log(`⚡ CPU Cores: ${os.cpus().length}`);
366
 
367
+ // Check GPU
368
+ try {
369
+ const { execSync } = await import('child_process');
370
+ const gpu = execSync('lspci | grep -i vga || echo "No GPU detected"', { encoding: 'utf8' });
371
+ console.log(`🎮 GPU: ${gpu.trim()}`);
372
+ } catch (e) {
373
+ console.log(`🎮 GPU: Not detected`);
374
+ }
375
+
376
+ // Environment check
377
+ console.log('--- Environment ---');
378
+ console.log('CHROME_BIN:', process.env.CHROME_BIN || 'default');
379
+ console.log('COMPUTE_TYPE:', process.env.COMPUTE_TYPE || 'cpu');
380
+ console.log('------------------');
381
+
382
+ // Auto-warmup
383
+ setTimeout(async () => {
384
+ try {
385
+ await getCachedBundle();
386
+ console.log('[Auto-Warmup] Bundle ready');
387
+ } catch (error) {
388
+ console.error('[Auto-Warmup] Failed:', error.message);
389
+ }
390
+ }, 5000);
391
+ });