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

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +153 -194
server.js CHANGED
@@ -1,8 +1,8 @@
1
  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';
@@ -27,7 +27,7 @@ app.get('/', (req, res) => {
27
  res.json({
28
  status: 'ok',
29
  service: 'FacelessFlowAI Video Renderer',
30
- note: 'Using system FFmpeg for hardware acceleration'
31
  });
32
  });
33
 
@@ -74,54 +74,138 @@ async function getCachedBundle() {
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;
@@ -145,7 +229,8 @@ app.post('/render', async (req, res) => {
145
  // 1. Respond immediately
146
  res.json({
147
  success: true,
148
- message: 'Rendering started in background'
 
149
  });
150
 
151
  // 2. Start Background Process
@@ -158,19 +243,17 @@ app.post('/render', async (req, res) => {
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',
168
- inputProps: { scenes, settings },
169
- chromiumOptions: {
170
- executablePath: '/usr/bin/google-chrome-stable',
171
- args: ['--no-sandbox', '--disable-dev-shm-usage']
172
- },
173
- });
174
 
175
  console.log(`[Composition] Loaded: ${composition.durationInFrames} frames at ${composition.fps}fps`);
176
 
@@ -178,64 +261,16 @@ app.post('/render', async (req, res) => {
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...`);
@@ -272,8 +307,7 @@ app.post('/render', async (req, res) => {
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,70 +340,18 @@ app.post('/render', async (req, res) => {
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({
328
  status: 'running',
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
- }
371
- });
372
-
373
  // Start server
374
  app.listen(PORT, async () => {
375
  console.log(`🎬 FacelessFlowAI Renderer running on port ${PORT}`);
@@ -378,33 +360,10 @@ app.listen(PORT, async () => {
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
 
1
  import express from 'express';
2
  import cors from 'cors';
3
  import { bundle } from '@remotion/bundler';
4
+ import { renderFrames, stitchFramesToVideo } from '@remotion/renderer';
5
+ import { readFile, mkdir, rm, writeFile } from 'fs/promises';
6
  import { tmpdir } from 'os';
7
  import { join, dirname } from 'path';
8
  import { randomUUID } from 'crypto';
 
27
  res.json({
28
  status: 'ok',
29
  service: 'FacelessFlowAI Video Renderer',
30
+ note: 'Custom rendering pipeline with NVENC'
31
  });
32
  });
33
 
 
74
  }
75
  }
76
 
77
+ // Custom video stitching with system FFmpeg
78
+ async function stitchFramesWithNVENC(frameDir, fps, outputPath, width = 1920, height = 1080) {
79
+ const { exec } = await import('child_process');
80
+ const { promisify } = await import('util');
81
+ const execAsync = promisify(exec);
82
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  try {
84
+ console.log('[NVENC] Starting hardware encoding with system FFmpeg');
85
 
86
+ // First, check if NVENC is available
87
+ const { stdout: encoderCheck } = await execAsync('ffmpeg -encoders 2>&1 | grep -i nvenc');
88
+ const hasNVENC = encoderCheck.includes('h264_nvenc');
89
 
90
+ if (hasNVENC) {
91
+ console.log('[NVENC] Using hardware encoding');
92
+
93
+ // Build FFmpeg command for NVENC
94
+ const ffmpegCmd = [
95
+ 'ffmpeg',
96
+ '-y', // Overwrite output
97
+ '-framerate', String(fps),
98
+ '-i', `${frameDir}/%d.png`, // Input frames
99
+ '-c:v', 'h264_nvenc', // NVENC encoder
100
+ '-preset', 'p1', // Fastest quality preset
101
+ '-cq', '20', // Constant quality
102
+ '-movflags', '+faststart',
103
+ '-pix_fmt', 'yuv420p',
104
+ '-vf', `scale=${width}:${height}:flags=lanczos`,
105
+ '-c:a', 'aac',
106
+ '-b:a', '192k',
107
+ outputPath
108
+ ].join(' ');
109
+
110
+ console.log(`[NVENC] Command: ${ffmpegCmd}`);
111
+
112
+ const { stdout, stderr } = await execAsync(ffmpegCmd, { maxBuffer: 1024 * 1024 * 50 });
113
+ console.log('[NVENC] Encoding completed');
114
+
115
+ } else {
116
+ console.log('[NVENC] Falling back to software encoding');
117
+
118
+ // Fallback to software encoding
119
+ const ffmpegCmd = [
120
+ 'ffmpeg',
121
+ '-y',
122
+ '-framerate', String(fps),
123
+ '-i', `${frameDir}/%d.png`,
124
+ '-c:v', 'libx264',
125
+ '-preset', 'fast',
126
+ '-crf', '23',
127
+ '-movflags', '+faststart',
128
+ '-pix_fmt', 'yuv420p',
129
+ '-vf', `scale=${width}:${height}:flags=lanczos`,
130
+ '-c:a', 'aac',
131
+ '-b:a', '192k',
132
+ outputPath
133
+ ].join(' ');
134
+
135
+ const { stdout, stderr } = await execAsync(ffmpegCmd, { maxBuffer: 1024 * 1024 * 50 });
136
+ console.log('[Software] Encoding completed');
137
+ }
138
 
139
+ return true;
140
 
 
 
 
 
141
  } catch (error) {
142
+ console.error('[Encoding] Error:', error.message);
143
+ throw error;
 
 
 
144
  }
145
  }
146
 
147
+ // Custom render pipeline
148
+ async function customRender(composition, bundleLocation, scenes, settings, outputPath) {
149
+ const startTime = Date.now();
150
+
151
+ console.log('[Custom Render] Starting frame rendering...');
152
+
153
+ // Step 1: Render frames
154
+ const frameDir = join(tmpdir(), `frames-${randomUUID()}`);
155
+ await mkdir(frameDir, { recursive: true });
156
+
157
+ const renderStart = Date.now();
158
+
159
+ await renderFrames({
160
+ config: composition,
161
+ serveUrl: bundleLocation,
162
+ onStart: ({ frameCount }) => {
163
+ console.log(`[Frames] Rendering ${frameCount} frames`);
164
+ },
165
+ onFrameUpdate: ({ renderedFrames, encodedFrames }) => {
166
+ const percent = Math.round((renderedFrames / composition.durationInFrames) * 100);
167
+ const elapsed = (Date.now() - renderStart) / 1000;
168
+ const fps = elapsed > 0 ? Math.round(renderedFrames / elapsed) : 0;
169
+ console.log(`[Frames] ${percent}% | FPS: ${fps} | Rendered: ${renderedFrames}/${composition.durationInFrames}`);
170
+ },
171
+ outputDir: frameDir,
172
+ inputProps: { scenes, settings },
173
+ imageFormat: 'png',
174
+ compositionId: composition.id,
175
+ concurrency: 6,
176
+ chromiumOptions: {
177
+ executablePath: '/usr/bin/google-chrome-stable',
178
+ args: ['--no-sandbox', '--disable-dev-shm-usage']
179
+ },
180
+ });
181
+
182
+ const frameTime = Date.now() - renderStart;
183
+ console.log(`[Frames] Completed in ${Math.round(frameTime / 1000)}s`);
184
+
185
+ // Step 2: Stitch frames with NVENC
186
+ console.log('[Custom Render] Stitching frames to video...');
187
+ const stitchStart = Date.now();
188
+
189
+ await stitchFramesWithNVENC(
190
+ frameDir,
191
+ composition.fps,
192
+ outputPath,
193
+ composition.width,
194
+ composition.height
195
+ );
196
+
197
+ const stitchTime = Date.now() - stitchStart;
198
+ console.log(`[Stitching] Completed in ${Math.round(stitchTime / 1000)}s`);
199
+
200
+ // Step 3: Cleanup frames
201
+ await rm(frameDir, { recursive: true, force: true });
202
+
203
+ const totalTime = Date.now() - startTime;
204
+ console.log(`[Custom Render] Total time: ${Math.round(totalTime / 1000)}s`);
205
+
206
+ return totalTime;
207
+ }
208
+
209
  // Render endpoint
210
  app.post('/render', async (req, res) => {
211
  const { projectId, scenes, settings } = req.body;
 
229
  // 1. Respond immediately
230
  res.json({
231
  success: true,
232
+ message: 'Rendering started in background',
233
+ note: 'Using custom render pipeline with hardware acceleration'
234
  });
235
 
236
  // 2. Start Background Process
 
243
  const bundleLocation = await getCachedBundle();
244
  console.log('[Bundle] Bundle ready');
245
 
246
+ const composition = await import('@remotion/renderer').then(mod =>
247
+ mod.selectComposition({
248
+ serveUrl: bundleLocation,
249
+ id: 'Main',
250
+ inputProps: { scenes, settings },
251
+ chromiumOptions: {
252
+ executablePath: '/usr/bin/google-chrome-stable',
253
+ args: ['--no-sandbox', '--disable-dev-shm-usage']
254
+ },
255
+ })
256
+ );
 
 
257
 
258
  console.log(`[Composition] Loaded: ${composition.durationInFrames} frames at ${composition.fps}fps`);
259
 
 
261
  await mkdir(tempDir, { recursive: true });
262
  const outputPath = join(tempDir, 'output.mp4');
263
 
264
+ console.log('[Render] Starting custom render pipeline...');
265
 
266
+ const renderTime = await customRender(
267
  composition,
268
+ bundleLocation,
269
+ scenes,
270
+ settings,
271
+ outputPath
272
+ );
273
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  console.log(`[Render] Completed in ${Math.round(renderTime / 1000)}s`);
275
 
276
  console.log(`[Upload] Reading video file...`);
 
307
  status: 'done',
308
  video_url: publicUrl,
309
  render_time: renderTime,
310
+ rendered_at: new Date().toISOString()
 
311
  })
312
  .eq('id', projectId);
313
 
 
340
  // Status endpoint
341
  app.get('/status', async (req, res) => {
342
  const os = await import('os');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
 
344
  res.json({
345
  status: 'running',
346
  cpu_cores: os.cpus().length,
347
  total_memory: Math.round(os.totalmem() / (1024 * 1024 * 1024)) + 'GB',
348
  free_memory: Math.round(os.freemem() / (1024 * 1024 * 1024)) + 'GB',
 
349
  bundle_cached: cachedBundle !== null,
350
+ uptime: Math.round(process.uptime()) + 's',
351
+ render_method: 'Custom pipeline with hardware acceleration'
352
  });
353
  });
354
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  // Start server
356
  app.listen(PORT, async () => {
357
  console.log(`🎬 FacelessFlowAI Renderer running on port ${PORT}`);
 
360
  console.log(`💾 Memory: ${Math.round(os.totalmem() / (1024 * 1024 * 1024))}GB`);
361
  console.log(`⚡ CPU Cores: ${os.cpus().length}`);
362
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  console.log('--- Configuration ---');
364
+ console.log('RENDER: Custom pipeline (renderFrames + FFmpeg)');
365
+ console.log('ENCODING: Hardware NVENC when available');
366
+ console.log('FRAMES: PNG format for quality');
367
  console.log('---------------------');
368
 
369
  // Auto-warmup