Stylique commited on
Commit
894f89c
·
verified ·
1 Parent(s): 6d29b7e

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +161 -211
server.js CHANGED
@@ -27,7 +27,7 @@ app.get('/', (req, res) => {
27
  res.json({
28
  status: 'ok',
29
  service: 'FacelessFlowAI Video Renderer',
30
- note: 'Auto-detect best encoding method'
31
  });
32
  });
33
 
@@ -74,164 +74,55 @@ async function getCachedBundle() {
74
  }
75
  }
76
 
77
- // Test NVENC with simple command
78
- async function testNvencCompatibility() {
79
  try {
80
  const { execSync } = await import('child_process');
81
- // Simple test command
82
- const testCommand = 'ffmpeg -f lavfi -i testsrc=duration=1:size=128x72:rate=1 -c:v h264_nvenc -f null - 2>&1';
83
- execSync(testCommand, { stdio: 'pipe', timeout: 5000 });
84
- console.log('[NVENC Test] Basic encoding works');
85
- return true;
 
 
 
 
 
86
  } catch (error) {
87
- console.log('[NVENC Test] Basic encoding failed:', error.message);
88
- return false;
89
  }
90
  }
91
 
92
- // Render with fallback mechanism
93
- async function renderWithFallback(composition, bundleLocation, scenes, settings, outputPath) {
94
- const startTime = Date.now();
95
-
96
- // Try NVENC first
97
- console.log('[Render] Attempt 1: Trying NVENC hardware encoding...');
98
  try {
99
- await renderMedia({
100
- composition,
101
- serveUrl: bundleLocation,
102
- codec: 'h264',
103
- pixelFormat: 'yuv420p',
104
- outputLocation: outputPath,
105
- imageFormat: 'jpeg',
106
- jpegQuality: 80,
107
- inputProps: { scenes, settings },
108
- chromiumOptions: {
109
- executablePath: '/usr/bin/google-chrome-stable',
110
- args: ['--no-sandbox', '--disable-dev-shm-usage']
111
- },
112
- concurrency: 6,
113
- disallowParallelEncoding: false,
114
- onProgress: ({ progress, renderedFrames }) => {
115
- const percent = Math.round(progress * 100);
116
- const elapsed = (Date.now() - startTime) / 1000;
117
- const fps = elapsed > 0 ? Math.round(renderedFrames / elapsed) : 0;
118
- console.log(`[NVENC Progress] ${percent}% | FPS: ${fps}`);
119
- },
120
- ffmpegOverride: ({ args }) => {
121
- console.log('[NVENC] Using simplified NVENC settings');
122
- // SIMPLIFIED NVENC SETTINGS - most compatible
123
- return [
124
- ...args,
125
- '-c:v', 'h264_nvenc',
126
- '-preset', 'default', // Most compatible preset
127
- '-cq', '23', // Standard quality
128
- '-movflags', '+faststart',
129
- '-c:a', 'aac',
130
- '-b:a', '192k',
131
- ];
132
- },
133
- audioBitrate: '192k',
134
- videoBitrate: '8M',
135
- });
136
 
137
- console.log(`[NVENC Success] Completed in ${Math.round((Date.now() - startTime) / 1000)}s`);
138
- return 'nvenc';
 
139
 
140
- } catch (nvencError) {
141
- console.log(`[NVENC Failed] ${nvencError.message}`);
 
142
 
143
- // Try even simpler NVENC
144
- console.log('[Render] Attempt 2: Trying minimal NVENC settings...');
145
- try {
146
- await renderMedia({
147
- composition,
148
- serveUrl: bundleLocation,
149
- codec: 'h264',
150
- pixelFormat: 'yuv420p',
151
- outputLocation: outputPath,
152
- imageFormat: 'jpeg',
153
- jpegQuality: 80,
154
- inputProps: { scenes, settings },
155
- chromiumOptions: {
156
- executablePath: '/usr/bin/google-chrome-stable',
157
- args: ['--no-sandbox', '--disable-dev-shm-usage']
158
- },
159
- concurrency: 4,
160
- disallowParallelEncoding: true, // More stable
161
- onProgress: ({ progress }) => {
162
- console.log(`[NVENC2 Progress] ${Math.round(progress * 100)}%`);
163
- },
164
- ffmpegOverride: ({ args }) => {
165
- console.log('[NVENC2] Using minimal NVENC settings');
166
- // MINIMAL NVENC SETTINGS
167
- return [
168
- ...args,
169
- '-c:v', 'h264_nvenc',
170
- '-preset', 'p1',
171
- '-movflags', '+faststart',
172
- '-c:a', 'aac',
173
- '-b:a', '192k',
174
- ];
175
- },
176
- audioBitrate: '192k',
177
- videoBitrate: '4M',
178
- });
179
-
180
- console.log(`[NVENC2 Success] Completed in ${Math.round((Date.now() - startTime) / 1000)}s`);
181
- return 'nvenc_minimal';
182
-
183
- } catch (nvenc2Error) {
184
- console.log(`[NVENC2 Failed] ${nvenc2Error.message}`);
185
-
186
- // Fallback to software encoding
187
- console.log('[Render] Attempt 3: Falling back to software encoding...');
188
- try {
189
- await renderMedia({
190
- composition,
191
- serveUrl: bundleLocation,
192
- codec: 'h264',
193
- pixelFormat: 'yuv420p',
194
- outputLocation: outputPath,
195
- imageFormat: 'jpeg',
196
- jpegQuality: 80,
197
- inputProps: { scenes, settings },
198
- chromiumOptions: {
199
- executablePath: '/usr/bin/google-chrome-stable',
200
- args: ['--no-sandbox', '--disable-dev-shm-usage']
201
- },
202
- concurrency: 4,
203
- disallowParallelEncoding: true,
204
- onProgress: ({ progress }) => {
205
- console.log(`[CPU Progress] ${Math.round(progress * 100)}%`);
206
- },
207
- ffmpegOverride: ({ args }) => {
208
- console.log('[CPU] Using software encoding');
209
- return [
210
- ...args,
211
- '-c:v', 'libx264',
212
- '-preset', 'fast',
213
- '-crf', '23',
214
- '-movflags', '+faststart',
215
- '-c:a', 'aac',
216
- '-b:a', '192k',
217
- ];
218
- },
219
- audioBitrate: '192k',
220
- videoBitrate: '4M',
221
- });
222
-
223
- console.log(`[CPU Success] Completed in ${Math.round((Date.now() - startTime) / 1000)}s`);
224
- return 'cpu';
225
-
226
- } catch (cpuError) {
227
- console.log(`[CPU Failed] ${cpuError.message}`);
228
- throw new Error(`All encoding methods failed. Last error: ${cpuError.message}`);
229
- }
230
- }
231
  }
232
  }
233
 
234
- // Render endpoint with intelligent fallback
235
  app.post('/render', async (req, res) => {
236
  const { projectId, scenes, settings } = req.body;
237
 
@@ -254,8 +145,7 @@ app.post('/render', async (req, res) => {
254
  // 1. Respond immediately
255
  res.json({
256
  success: true,
257
- message: 'Rendering started in background',
258
- note: 'Using intelligent encoding detection'
259
  });
260
 
261
  // 2. Start Background Process
@@ -268,6 +158,10 @@ app.post('/render', async (req, res) => {
268
  const bundleLocation = await getCachedBundle();
269
  console.log('[Bundle] Bundle ready');
270
 
 
 
 
 
271
  const composition = await selectComposition({
272
  serveUrl: bundleLocation,
273
  id: 'Main',
@@ -284,22 +178,65 @@ app.post('/render', async (req, res) => {
284
  await mkdir(tempDir, { recursive: true });
285
  const outputPath = join(tempDir, 'output.mp4');
286
 
287
- console.log('[Render] Starting intelligent rendering...');
288
 
289
- // Test NVENC compatibility first
290
- const nvencCompatible = await testNvencCompatibility();
291
- console.log(`[Compatibility] NVENC compatible: ${nvencCompatible}`);
292
-
293
- const encodingMethod = await renderWithFallback(
294
- composition,
295
- bundleLocation,
296
- scenes,
297
- settings,
298
- outputPath
299
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
 
301
- const totalTime = Date.now() - startTime;
302
- console.log(`[Render] Completed in ${Math.round(totalTime / 1000)}s using ${encodingMethod}`);
303
 
304
  console.log(`[Upload] Reading video file...`);
305
  const videoBuffer = await readFile(outputPath);
@@ -334,9 +271,9 @@ app.post('/render', async (req, res) => {
334
  .update({
335
  status: 'done',
336
  video_url: publicUrl,
337
- render_time: totalTime,
338
  rendered_at: new Date().toISOString(),
339
- encoding_method: encodingMethod
340
  })
341
  .eq('id', projectId);
342
 
@@ -369,15 +306,22 @@ app.post('/render', async (req, res) => {
369
  // Status endpoint
370
  app.get('/status', async (req, res) => {
371
  const os = await import('os');
 
372
 
373
- // Test NVENC compatibility
374
- let nvencStatus = 'Unknown';
375
  try {
376
- const { execSync } = await import('child_process');
377
- execSync('ffmpeg -encoders 2>/dev/null | grep -i nvenc', { stdio: 'pipe' });
378
- nvencStatus = 'Detected (needs test)';
379
- } catch {
380
- nvencStatus = 'Not detected';
 
 
 
 
 
 
 
381
  }
382
 
383
  res.json({
@@ -385,59 +329,42 @@ app.get('/status', async (req, res) => {
385
  cpu_cores: os.cpus().length,
386
  total_memory: Math.round(os.totalmem() / (1024 * 1024 * 1024)) + 'GB',
387
  free_memory: Math.round(os.freemem() / (1024 * 1024 * 1024)) + 'GB',
388
- nvenc: nvencStatus,
389
  bundle_cached: cachedBundle !== null,
390
- uptime: Math.round(process.uptime()) + 's',
391
- strategy: 'Intelligent fallback (NVENC → CPU)'
392
  });
393
  });
394
 
395
- // Enhanced NVENC test
396
- app.get('/test-nvenc-full', async (req, res) => {
397
  try {
398
  const { execSync } = await import('child_process');
399
- const fs = await import('fs');
400
 
401
- const tests = [];
 
402
 
403
- // Test 1: Check if NVENC encoder exists
404
- try {
405
- const encoders = execSync('ffmpeg -encoders 2>/dev/null | grep -i nvenc', { encoding: 'utf8' });
406
- tests.push({ test: 'NVENC encoders exist', result: 'PASS', details: encoders.trim() });
407
- } catch {
408
- tests.push({ test: 'NVENC encoders exist', result: 'FAIL', details: 'Not found' });
409
- }
410
-
411
- // Test 2: Try simple encoding
412
- try {
413
- execSync('ffmpeg -f lavfi -i testsrc=duration=0.1:size=64x36:rate=1 -c:v h264_nvenc -f null - 2>&1', { stdio: 'pipe' });
414
- tests.push({ test: 'Simple NVENC encode', result: 'PASS', details: 'Works' });
415
- } catch (error) {
416
- tests.push({ test: 'Simple NVENC encode', result: 'FAIL', details: error.message.substring(0, 100) });
417
- }
418
 
419
- // Test 3: Try with CQ parameter
420
- try {
421
- execSync('ffmpeg -f lavfi -i testsrc=duration=0.1:size=64x36:rate=1 -c:v h264_nvenc -cq 23 -f null - 2>&1', { stdio: 'pipe' });
422
- tests.push({ test: 'NVENC with CQ', result: 'PASS', details: 'Works' });
423
- } catch (error) {
424
- tests.push({ test: 'NVENC with CQ', result: 'FAIL', details: error.message.substring(0, 100) });
425
- }
426
 
427
- // Test 4: Try with preset
 
428
  try {
429
- execSync('ffmpeg -f lavfi -i testsrc=duration=0.1:size=64x36:rate=1 -c:v h264_nvenc -preset p1 -f null - 2>&1', { stdio: 'pipe' });
430
- tests.push({ test: 'NVENC with preset', result: 'PASS', details: 'Works' });
431
  } catch (error) {
432
- tests.push({ test: 'NVENC with preset', result: 'FAIL', details: error.message.substring(0, 100) });
433
  }
434
 
435
  res.json({
436
- hardware: 'L4 GPU Tests',
437
- tests: tests,
438
- summary: `${tests.filter(t => t.result === 'PASS').length}/${tests.length} tests passed`
 
 
439
  });
440
-
441
  } catch (error) {
442
  res.status(500).json({ error: error.message });
443
  }
@@ -451,10 +378,33 @@ app.listen(PORT, async () => {
451
  console.log(`💾 Memory: ${Math.round(os.totalmem() / (1024 * 1024 * 1024))}GB`);
452
  console.log(`⚡ CPU Cores: ${os.cpus().length}`);
453
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  console.log('--- Configuration ---');
455
- console.log('STRATEGY: Intelligent fallback (NVENC CPU)');
456
- console.log('CONCURRENCY: Adaptive');
457
- console.log('QUALITY: Adaptive');
458
  console.log('---------------------');
459
 
460
  // Auto-warmup
 
27
  res.json({
28
  status: 'ok',
29
  service: 'FacelessFlowAI Video Renderer',
30
+ note: 'Using system FFmpeg for hardware acceleration'
31
  });
32
  });
33
 
 
74
  }
75
  }
76
 
77
+ // Check which FFmpeg encoders are available
78
+ async function getAvailableEncoders() {
79
  try {
80
  const { execSync } = await import('child_process');
81
+ const output = execSync('ffmpeg -encoders 2>&1', { encoding: 'utf8' });
82
+
83
+ const encoders = {
84
+ nvenc: output.includes('h264_nvenc'),
85
+ libx264: output.includes('libx264'),
86
+ systemFfmpeg: !output.includes('remotion'), // Check if it's system FFmpeg
87
+ };
88
+
89
+ console.log('[FFmpeg] Available encoders:', encoders);
90
+ return encoders;
91
  } catch (error) {
92
+ console.log('[FFmpeg] Could not check encoders:', error.message);
93
+ return { nvenc: false, libx264: true, systemFfmpeg: false };
94
  }
95
  }
96
 
97
+ // Force use of system FFmpeg
98
+ async function useSystemFFmpeg() {
 
 
 
 
99
  try {
100
+ const { execSync } = await import('child_process');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
+ // Get system FFmpeg path
103
+ const ffmpegPath = execSync('which ffmpeg', { encoding: 'utf8' }).trim();
104
+ console.log(`[FFmpeg] System FFmpeg found at: ${ffmpegPath}`);
105
 
106
+ // Check if it has NVENC
107
+ const encoders = execSync(`${ffmpegPath} -encoders 2>&1 | grep -i nvenc`, { encoding: 'utf8' });
108
+ const hasNvenc = encoders.includes('h264_nvenc');
109
 
110
+ console.log(`[FFmpeg] System FFmpeg has NVENC: ${hasNvenc}`);
111
+
112
+ return {
113
+ path: ffmpegPath,
114
+ hasNvenc: hasNvenc
115
+ };
116
+ } catch (error) {
117
+ console.log('[FFmpeg] Using default FFmpeg');
118
+ return {
119
+ path: 'ffmpeg',
120
+ hasNvenc: false
121
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  }
123
  }
124
 
125
+ // Render endpoint
126
  app.post('/render', async (req, res) => {
127
  const { projectId, scenes, settings } = req.body;
128
 
 
145
  // 1. Respond immediately
146
  res.json({
147
  success: true,
148
+ message: 'Rendering started in background'
 
149
  });
150
 
151
  // 2. Start Background Process
 
158
  const bundleLocation = await getCachedBundle();
159
  console.log('[Bundle] Bundle ready');
160
 
161
+ // Get system FFmpeg info
162
+ const ffmpegInfo = await useSystemFFmpeg();
163
+ console.log(`[FFmpeg] Using: ${ffmpegInfo.path}, NVENC: ${ffmpegInfo.hasNvenc}`);
164
+
165
  const composition = await selectComposition({
166
  serveUrl: bundleLocation,
167
  id: 'Main',
 
178
  await mkdir(tempDir, { recursive: true });
179
  const outputPath = join(tempDir, 'output.mp4');
180
 
181
+ console.log('[Render] Starting video rendering...');
182
 
183
+ await renderMedia({
184
+ composition,
185
+ serveUrl: bundleLocation,
186
+ codec: 'h264',
187
+ pixelFormat: 'yuv420p',
188
+ outputLocation: outputPath,
189
+ imageFormat: 'jpeg',
190
+ jpegQuality: 80,
191
+ inputProps: { scenes, settings },
192
+ chromiumOptions: {
193
+ executablePath: '/usr/bin/google-chrome-stable',
194
+ args: ['--no-sandbox', '--disable-dev-shm-usage']
195
+ },
196
+ concurrency: 6,
197
+ disallowParallelEncoding: false,
198
+ onProgress: ({ progress }) => {
199
+ console.log(`[Progress] ${Math.round(progress * 100)}%`);
200
+ },
201
+ ffmpegOverride: (ff) => {
202
+ console.log('[FFmpeg] Overriding FFmpeg path to use system FFmpeg');
203
+
204
+ // Force use of system FFmpeg
205
+ const newArgs = [
206
+ ffmpegInfo.path, // Use system FFmpeg instead of Remotion's
207
+ ...ff.args.slice(1) // Skip the first argument (which is the path)
208
+ ];
209
+
210
+ // Add NVENC settings if available
211
+ if (ffmpegInfo.hasNvenc) {
212
+ console.log('[FFmpeg] Adding NVENC settings');
213
+ // Find where to insert video encoder settings
214
+ const videoIndex = newArgs.findIndex(arg => arg === '-c:v' || arg === '-vcodec');
215
+
216
+ if (videoIndex !== -1 && newArgs[videoIndex + 1] === 'libx264') {
217
+ // Replace libx264 with h264_nvenc
218
+ newArgs[videoIndex + 1] = 'h264_nvenc';
219
+
220
+ // Add NVENC-specific settings AFTER the output file
221
+ const outputIndex = newArgs.findIndex(arg => arg === outputPath);
222
+ if (outputIndex !== -1) {
223
+ // Insert NVENC settings before output
224
+ newArgs.splice(outputIndex, 0,
225
+ '-preset', 'p1',
226
+ '-cq', '23'
227
+ );
228
+ }
229
+ }
230
+ }
231
+
232
+ return newArgs;
233
+ },
234
+ audioBitrate: '192k',
235
+ videoBitrate: '8M',
236
+ });
237
 
238
+ const renderTime = Date.now() - startTime;
239
+ console.log(`[Render] Completed in ${Math.round(renderTime / 1000)}s`);
240
 
241
  console.log(`[Upload] Reading video file...`);
242
  const videoBuffer = await readFile(outputPath);
 
271
  .update({
272
  status: 'done',
273
  video_url: publicUrl,
274
+ render_time: renderTime,
275
  rendered_at: new Date().toISOString(),
276
+ encoding_method: ffmpegInfo.hasNvenc ? 'NVENC' : 'libx264'
277
  })
278
  .eq('id', projectId);
279
 
 
306
  // Status endpoint
307
  app.get('/status', async (req, res) => {
308
  const os = await import('os');
309
+ const { execSync } = await import('child_process');
310
 
311
+ let ffmpegInfo = {};
 
312
  try {
313
+ const ffmpegPath = execSync('which ffmpeg', { encoding: 'utf8' }).trim();
314
+ const version = execSync(`${ffmpegPath} -version 2>&1 | head -1`, { encoding: 'utf8' }).trim();
315
+ const encoders = execSync(`${ffmpegPath} -encoders 2>&1`, { encoding: 'utf8' });
316
+
317
+ ffmpegInfo = {
318
+ path: ffmpegPath,
319
+ version: version,
320
+ has_nvenc: encoders.includes('h264_nvenc'),
321
+ is_system: !version.includes('remotion')
322
+ };
323
+ } catch (error) {
324
+ ffmpegInfo = { error: error.message };
325
  }
326
 
327
  res.json({
 
329
  cpu_cores: os.cpus().length,
330
  total_memory: Math.round(os.totalmem() / (1024 * 1024 * 1024)) + 'GB',
331
  free_memory: Math.round(os.freemem() / (1024 * 1024 * 1024)) + 'GB',
332
+ ffmpeg: ffmpegInfo,
333
  bundle_cached: cachedBundle !== null,
334
+ uptime: Math.round(process.uptime()) + 's'
 
335
  });
336
  });
337
 
338
+ // Direct FFmpeg test
339
+ app.get('/test-ffmpeg', async (req, res) => {
340
  try {
341
  const { execSync } = await import('child_process');
 
342
 
343
+ // Test 1: Which FFmpeg
344
+ const whichFfmpeg = execSync('which ffmpeg', { encoding: 'utf8' }).trim();
345
 
346
+ // Test 2: Version
347
+ const version = execSync(`${whichFfmpeg} -version 2>&1 | head -5`, { encoding: 'utf8' }).trim();
 
 
 
 
 
 
 
 
 
 
 
 
 
348
 
349
+ // Test 3: Encoders
350
+ const encoders = execSync(`${whichFfmpeg} -encoders 2>&1 | grep -E "(h264_nvenc|libx264)"`, { encoding: 'utf8' }).trim();
 
 
 
 
 
351
 
352
+ // Test 4: Direct NVENC test
353
+ let nvencTest = 'Not tested';
354
  try {
355
+ execSync(`${whichFfmpeg} -f lavfi -i testsrc=duration=0.5:size=128x72:rate=1 -c:v h264_nvenc -y /tmp/test.mp4 2>&1`, { stdio: 'pipe' });
356
+ nvencTest = 'Success';
357
  } catch (error) {
358
+ nvencTest = `Failed: ${error.message.substring(0, 100)}`;
359
  }
360
 
361
  res.json({
362
+ ffmpeg_path: whichFfmpeg,
363
+ version: version,
364
+ available_encoders: encoders,
365
+ nvenc_test: nvencTest,
366
+ using_system_ffmpeg: !version.includes('remotion')
367
  });
 
368
  } catch (error) {
369
  res.status(500).json({ error: error.message });
370
  }
 
378
  console.log(`💾 Memory: ${Math.round(os.totalmem() / (1024 * 1024 * 1024))}GB`);
379
  console.log(`⚡ CPU Cores: ${os.cpus().length}`);
380
 
381
+ // Check FFmpeg
382
+ try {
383
+ const { execSync } = await import('child_process');
384
+ const ffmpegPath = execSync('which ffmpeg', { encoding: 'utf8' }).trim();
385
+ const version = execSync(`${ffmpegPath} -version 2>&1 | head -1`, { encoding: 'utf8' }).trim();
386
+ console.log(`📹 FFmpeg: ${version}`);
387
+
388
+ if (version.includes('remotion')) {
389
+ console.log('⚠️ Using Remotion FFmpeg (no NVENC)');
390
+ } else {
391
+ console.log('✅ Using system FFmpeg');
392
+
393
+ // Check NVENC
394
+ try {
395
+ const nvenc = execSync(`${ffmpegPath} -encoders 2>&1 | grep -i nvenc`, { encoding: 'utf8' });
396
+ console.log(`⚡ NVENC: Available`);
397
+ } catch {
398
+ console.log('⚡ NVENC: Not in system FFmpeg');
399
+ }
400
+ }
401
+ } catch (error) {
402
+ console.log('📹 FFmpeg: Not found');
403
+ }
404
+
405
  console.log('--- Configuration ---');
406
+ console.log('FFMPEG: Forcing system FFmpeg for hardware acceleration');
407
+ console.log('ENCODING: Auto-detect best available');
 
408
  console.log('---------------------');
409
 
410
  // Auto-warmup