Stylique commited on
Commit
056e30b
·
verified ·
1 Parent(s): aaaab63

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +69 -21
server.js CHANGED
@@ -56,28 +56,80 @@ app.post('/render', async (req, res) => {
56
  webpackOverride: (config) => config,
57
  });
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  const composition = await selectComposition({
60
  serveUrl: bundleLocation,
61
  id: 'Main',
62
- inputProps: { scenes, settings },
63
  chromiumOptions: { executablePath: process.env.CHROME_BIN },
64
  });
65
 
66
- const tempDir = join(tmpdir(), `remotion-${randomUUID()}`);
67
- await mkdir(tempDir, { recursive: true });
68
  const outputPath = join(tempDir, 'output.mp4');
69
 
70
  const os = await import('os');
71
  const cpuCount = os.cpus().length;
72
- console.log(`[Render] Detected ${cpuCount} CPUs. Using all cores for rendering.`);
73
-
74
- // Tuning for CPU Only
75
- // Remotion is CPU-bound. Concurrency = CPU Cores is usually good,
76
- // but we leave 1 core for OS/Orchestration.
77
- const safeConcurrency = 1;
78
- console.log(`[Render] Requesting concurrency: ${safeConcurrency} (Host has ${cpuCount})`);
79
-
80
- let lastLog = 0;
81
 
82
  await renderMedia({
83
  composition,
@@ -87,24 +139,20 @@ app.post('/render', async (req, res) => {
87
  outputLocation: outputPath,
88
  imageFormat: 'jpeg',
89
  jpegQuality: 80,
90
- inputProps: { scenes, settings },
91
  chromiumOptions: {
92
  executablePath: process.env.CHROME_BIN || '/usr/bin/google-chrome-stable',
93
  enableMultiProcessRendering: true,
94
  args: ['--no-sandbox', '--disable-dev-shm-usage', '--disable-software-rasterizer']
95
  },
 
96
  concurrency: 1,
97
  disallowParallelEncoding: false,
98
- timeoutInMilliseconds: 300000, // 5 minutes timeout for slow downloads
99
  onProgress: ({ progress }) => {
100
  const percent = Math.round(progress * 100);
101
- // Log every 1%
102
- if (percent - lastLog >= 1 || percent === 100) {
103
- console.log(`[Render] Progress: ${percent}%`);
104
- lastLog = percent;
105
- }
106
  },
107
- // Default software encoder (libx264) is robust and decent speed on high CPU
108
  });
109
 
110
  console.log(`[Render] Render complete. Uploading to Supabase...`);
@@ -115,7 +163,7 @@ app.post('/render', async (req, res) => {
115
  const fileName = `${projectId}/video-${Date.now()}.mp4`;
116
  const { data: uploadData, error: uploadError } = await supabase
117
  .storage
118
- .from('projects') // Assuming 'projects' bucket exists
119
  .upload(fileName, videoBuffer, {
120
  contentType: 'video/mp4',
121
  upsert: true
 
56
  webpackOverride: (config) => config,
57
  });
58
 
59
+ // --- STABILITY: Pre-download Assets ---
60
+ // To prevent "server sent no data" timeouts and reduce RAM usage,
61
+ // we download all assets to disk BEFORE starting the render.
62
+
63
+ const fs = await import('fs');
64
+ const { pipeline } = await import('stream/promises');
65
+ const { createWriteStream } = await import('fs');
66
+
67
+ // Helper: Robust Downloader with Retries
68
+ const downloadAsset = async (url, destPath, retries = 3) => {
69
+ for (let i = 0; i < retries; i++) {
70
+ try {
71
+ const response = await fetch(url);
72
+ if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
73
+ if (!response.body) throw new Error(`No body for ${url}`);
74
+
75
+ await pipeline(response.body, createWriteStream(destPath));
76
+ return; // Success
77
+ } catch (err) {
78
+ console.warn(`[Download] Attempt ${i + 1} failed for ${url}: ${err.message}`);
79
+ if (i === retries - 1) throw err; // Throw on last failure
80
+ await new Promise(r => setTimeout(r, 2000 * (i + 1))); // Backoff
81
+ }
82
+ }
83
+ };
84
+
85
+ const tempDir = join(tmpdir(), `remotion-${randomUUID()}`);
86
+ await mkdir(tempDir, { recursive: true });
87
+
88
+ console.log(`[Render] Pre-downloading assets to ${tempDir}...`);
89
+
90
+ // Iterate scenes and download assets
91
+ const localScenes = await Promise.all(scenes.map(async (scene, idx) => {
92
+ const newScene = { ...scene };
93
+
94
+ // 1. Audio
95
+ if (scene.audio_url) {
96
+ const audioExt = scene.audio_url.split('.').pop().split('?')[0] || 'mp3';
97
+ const localAudioPath = join(tempDir, `audio_${idx}.${audioExt}`);
98
+ await downloadAsset(scene.audio_url, localAudioPath);
99
+ newScene.audio_url = `file://${localAudioPath}`;
100
+ }
101
+
102
+ // 2. Video / Image
103
+ if (scene.image_url) {
104
+ // Check if it's a video (likely from Pexels/Supabase)
105
+ const isVideo = scene.media_type === 'video' || scene.image_url.includes('.mp4');
106
+
107
+ const ext = isVideo ? 'mp4' : 'jpg'; // Default extension if unknown
108
+ const localMediaPath = join(tempDir, `visual_${idx}.${ext}`);
109
+
110
+ await downloadAsset(scene.image_url, localMediaPath);
111
+ newScene.image_url = `file://${localMediaPath}`;
112
+ }
113
+
114
+ return newScene;
115
+ }));
116
+
117
+ console.log(`[Render] Assets downloaded. Starting engine...`);
118
+
119
+ // --------------------------------------
120
+
121
  const composition = await selectComposition({
122
  serveUrl: bundleLocation,
123
  id: 'Main',
124
+ inputProps: { scenes: localScenes, settings }, // Use LOCAL scenes
125
  chromiumOptions: { executablePath: process.env.CHROME_BIN },
126
  });
127
 
 
 
128
  const outputPath = join(tempDir, 'output.mp4');
129
 
130
  const os = await import('os');
131
  const cpuCount = os.cpus().length;
132
+ console.log(`[Render] Detected ${cpuCount} CPUs.`);
 
 
 
 
 
 
 
 
133
 
134
  await renderMedia({
135
  composition,
 
139
  outputLocation: outputPath,
140
  imageFormat: 'jpeg',
141
  jpegQuality: 80,
142
+ inputProps: { scenes: localScenes, settings }, // Use LOCAL scenes
143
  chromiumOptions: {
144
  executablePath: process.env.CHROME_BIN || '/usr/bin/google-chrome-stable',
145
  enableMultiProcessRendering: true,
146
  args: ['--no-sandbox', '--disable-dev-shm-usage', '--disable-software-rasterizer']
147
  },
148
+ // STRICT CONCURRENCY FOR STABILITY
149
  concurrency: 1,
150
  disallowParallelEncoding: false,
 
151
  onProgress: ({ progress }) => {
152
  const percent = Math.round(progress * 100);
153
+ // Log to console (Supabase doesn't need realtime logs, just status)
154
+ if (percent % 10 === 0) console.log(`[Render] Progress: ${percent}%`);
 
 
 
155
  },
 
156
  });
157
 
158
  console.log(`[Render] Render complete. Uploading to Supabase...`);
 
163
  const fileName = `${projectId}/video-${Date.now()}.mp4`;
164
  const { data: uploadData, error: uploadError } = await supabase
165
  .storage
166
+ .from('projects')
167
  .upload(fileName, videoBuffer, {
168
  contentType: 'video/mp4',
169
  upsert: true