@remotion/renderer 4.0.0-preload.13 → 4.0.0-spawn.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/assets/download-and-map-assets-to-file.d.ts +6 -0
  2. package/dist/assets/download-and-map-assets-to-file.js +46 -19
  3. package/dist/assets/ffmpeg-volume-expression.d.ts +2 -1
  4. package/dist/assets/ffmpeg-volume-expression.js +15 -12
  5. package/dist/combine-videos.js +2 -0
  6. package/dist/cycle-browser-tabs.d.ts +2 -1
  7. package/dist/cycle-browser-tabs.js +2 -2
  8. package/dist/extract-frame-from-video.d.ts +18 -0
  9. package/dist/extract-frame-from-video.js +132 -0
  10. package/dist/frame-to-ffmpeg-timestamp.d.ts +1 -0
  11. package/dist/frame-to-ffmpeg-timestamp.js +8 -0
  12. package/dist/get-compositions.d.ts +4 -2
  13. package/dist/get-compositions.js +22 -5
  14. package/dist/index.d.ts +7 -8
  15. package/dist/index.js +0 -4
  16. package/dist/merge-audio-track.js +2 -2
  17. package/dist/offthread-video-server.d.ts +13 -0
  18. package/dist/offthread-video-server.js +65 -0
  19. package/dist/open-browser.d.ts +5 -5
  20. package/dist/prepare-server.d.ts +12 -2
  21. package/dist/prepare-server.js +22 -5
  22. package/dist/prespawn-ffmpeg.d.ts +1 -0
  23. package/dist/prespawn-ffmpeg.js +11 -10
  24. package/dist/puppeteer-screenshot.js +5 -1
  25. package/dist/render-frames.d.ts +7 -2
  26. package/dist/render-frames.js +90 -33
  27. package/dist/render-media.d.ts +8 -2
  28. package/dist/render-media.js +52 -3
  29. package/dist/render-still.d.ts +7 -2
  30. package/dist/render-still.js +33 -9
  31. package/dist/serve-static.d.ts +9 -3
  32. package/dist/serve-static.js +16 -0
  33. package/dist/set-props-and-env.d.ts +3 -1
  34. package/dist/set-props-and-env.js +25 -3
  35. package/dist/stitch-frames-to-video.js +1 -0
  36. package/dist/stringify-ffmpeg-filter.js +3 -0
  37. package/dist/tmp-dir.js +5 -1
  38. package/package.json +4 -5
@@ -10,9 +10,9 @@ export declare type ChromiumOptions = {
10
10
  };
11
11
  export declare const killAllBrowsers: () => Promise<void>;
12
12
  export declare const openBrowser: (browser: Browser, options?: {
13
- shouldDumpIo?: boolean | undefined;
14
- browserExecutable?: string | null | undefined;
15
- chromiumOptions?: ChromiumOptions | undefined;
16
- forceDeviceScaleFactor?: number | undefined;
17
- } | undefined) => Promise<puppeteer.Browser>;
13
+ shouldDumpIo?: boolean;
14
+ browserExecutable?: string | null;
15
+ chromiumOptions?: ChromiumOptions;
16
+ forceDeviceScaleFactor?: number;
17
+ }) => Promise<puppeteer.Browser>;
18
18
  export {};
@@ -1,4 +1,14 @@
1
- export declare const prepareServer: (webpackConfigOrServeUrl: string) => Promise<{
1
+ import { FfmpegExecutable } from 'remotion';
2
+ import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
3
+ export declare const prepareServer: ({ downloadDir, ffmpegExecutable, onDownload, onError, webpackConfigOrServeUrl, port, }: {
4
+ webpackConfigOrServeUrl: string;
5
+ downloadDir: string;
6
+ onDownload: RenderMediaOnDownload;
7
+ onError: (err: Error) => void;
8
+ ffmpegExecutable: FfmpegExecutable;
9
+ port: number | null;
10
+ }) => Promise<{
2
11
  serveUrl: string;
3
- closeServer: () => Promise<void>;
12
+ closeServer: () => Promise<unknown>;
13
+ offthreadPort: number;
4
14
  }>;
@@ -3,17 +3,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prepareServer = void 0;
4
4
  const is_serve_url_1 = require("./is-serve-url");
5
5
  const serve_static_1 = require("./serve-static");
6
- const prepareServer = async (webpackConfigOrServeUrl) => {
6
+ const prepareServer = async ({ downloadDir, ffmpegExecutable, onDownload, onError, webpackConfigOrServeUrl, port, }) => {
7
7
  if ((0, is_serve_url_1.isServeUrl)(webpackConfigOrServeUrl)) {
8
+ const { port: offthreadPort, close: closeProxy } = await (0, serve_static_1.serveStatic)(null, {
9
+ downloadDir,
10
+ onDownload,
11
+ onError,
12
+ ffmpegExecutable,
13
+ port,
14
+ });
8
15
  return Promise.resolve({
9
16
  serveUrl: webpackConfigOrServeUrl,
10
- closeServer: () => Promise.resolve(undefined),
17
+ closeServer: () => closeProxy(),
18
+ offthreadPort,
11
19
  });
12
20
  }
13
- const { port, close } = await (0, serve_static_1.serveStatic)(webpackConfigOrServeUrl);
21
+ const { port: serverPort, close } = await (0, serve_static_1.serveStatic)(webpackConfigOrServeUrl, {
22
+ downloadDir,
23
+ onDownload,
24
+ onError,
25
+ ffmpegExecutable,
26
+ port,
27
+ });
14
28
  return Promise.resolve({
15
- closeServer: () => close(),
16
- serveUrl: `http://localhost:${port}`,
29
+ closeServer: () => {
30
+ return close();
31
+ },
32
+ serveUrl: `http://localhost:${serverPort}`,
33
+ offthreadPort: serverPort,
17
34
  });
18
35
  };
19
36
  exports.prepareServer = prepareServer;
@@ -17,5 +17,6 @@ declare type PreSticherOptions = {
17
17
  export declare const prespawnFfmpeg: (options: PreSticherOptions) => Promise<{
18
18
  task: execa.ExecaChildProcess<string>;
19
19
  getLogs: () => string;
20
+ waitForSpawn: Promise<void>;
20
21
  }>;
21
22
  export {};
@@ -57,15 +57,13 @@ const prespawnFfmpeg = async (options) => {
57
57
  // -c:v is the same as -vcodec as -codec:video
58
58
  // and specified the video codec.
59
59
  ['-c:v', encoderName],
60
- ...[
61
- proResProfileName ? ['-profile:v', proResProfileName] : null,
62
- supportsCrf ? ['-crf', String(crf)] : null,
63
- ['-pix_fmt', pixelFormat],
64
- // Without explicitly disabling auto-alt-ref,
65
- // transparent WebM generation doesn't work
66
- pixelFormat === 'yuva420p' ? ['-auto-alt-ref', '0'] : null,
67
- ['-b:v', '1M'],
68
- ],
60
+ proResProfileName ? ['-profile:v', proResProfileName] : null,
61
+ supportsCrf ? ['-crf', String(crf)] : null,
62
+ ['-pix_fmt', pixelFormat],
63
+ // Without explicitly disabling auto-alt-ref,
64
+ // transparent WebM generation doesn't work
65
+ pixelFormat === 'yuva420p' ? ['-auto-alt-ref', '0'] : null,
66
+ ['-b:v', '1M'],
69
67
  '-y',
70
68
  options.outputLocation,
71
69
  ];
@@ -75,6 +73,9 @@ const prespawnFfmpeg = async (options) => {
75
73
  }
76
74
  const ffmpegString = ffmpegArgs.flat(2).filter(Boolean);
77
75
  const task = (0, execa_1.default)((_f = options.ffmpegExecutable) !== null && _f !== void 0 ? _f : 'ffmpeg', ffmpegString);
76
+ const waitForSpawn = new Promise((resolve) => {
77
+ task.on('spawn', () => resolve());
78
+ });
78
79
  let ffmpegOutput = '';
79
80
  (_g = task.stderr) === null || _g === void 0 ? void 0 : _g.on('data', (data) => {
80
81
  const str = data.toString();
@@ -86,6 +87,6 @@ const prespawnFfmpeg = async (options) => {
86
87
  }
87
88
  }
88
89
  });
89
- return { task, getLogs: () => ffmpegOutput };
90
+ return { task, getLogs: () => ffmpegOutput, waitForSpawn };
90
91
  };
91
92
  exports.prespawnFfmpeg = prespawnFfmpeg;
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import { Browser as PuppeteerBrowser } from 'puppeteer-core';
3
- import { BrowserExecutable, FrameRange, ImageFormat, SmallTCompMetadata } from 'remotion';
3
+ import { BrowserExecutable, FfmpegExecutable, FrameRange, ImageFormat, SmallTCompMetadata } from 'remotion';
4
4
  import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
5
5
  import { BrowserLog } from './browser-log';
6
6
  import { ServeUrlOrWebpackBundle } from './legacy-webpack-config';
@@ -33,6 +33,11 @@ declare type RenderFramesOptions = {
33
33
  timeoutInMilliseconds?: number;
34
34
  chromiumOptions?: ChromiumOptions;
35
35
  scale?: number;
36
+ ffmpegExecutable?: FfmpegExecutable;
37
+ port?: number | null;
36
38
  } & ConfigOrComposition & ServeUrlOrWebpackBundle;
37
- export declare const renderFrames: (options: RenderFramesOptions) => Promise<RenderFramesOutput>;
39
+ declare type ReturnTypeWithCancel = Promise<RenderFramesOutput> & {
40
+ cancel: () => void;
41
+ };
42
+ export declare const renderFrames: (options: RenderFramesOptions) => ReturnTypeWithCancel;
38
43
  export {};
@@ -33,7 +33,12 @@ const getComposition = (others) => {
33
33
  }
34
34
  return undefined;
35
35
  };
36
- const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps, quality, imageFormat = image_format_1.DEFAULT_IMAGE_FORMAT, frameRange, puppeteerInstance, onError, envVariables, onBrowserLog, onFrameBuffer, onDownload, pagesArray, serveUrl, composition, timeoutInMilliseconds, scale, actualParallelism, }) => {
36
+ const getPool = async (pages) => {
37
+ const puppeteerPages = await Promise.all(pages);
38
+ const pool = new pool_1.Pool(puppeteerPages);
39
+ return pool;
40
+ };
41
+ const innerRenderFrames = ({ onFrameUpdate, outputDir, onStart, inputProps, quality, imageFormat = image_format_1.DEFAULT_IMAGE_FORMAT, frameRange, puppeteerInstance, onError, envVariables, onBrowserLog, onFrameBuffer, onDownload, pagesArray, serveUrl, composition, timeoutInMilliseconds, scale, actualParallelism, downloadDir, proxyPort, }) => {
37
42
  if (!puppeteerInstance) {
38
43
  throw new Error('weird');
39
44
  }
@@ -76,6 +81,8 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
76
81
  serveUrl,
77
82
  initialFrame,
78
83
  timeoutInMilliseconds,
84
+ proxyPort,
85
+ retriesRemaining: 2,
79
86
  });
80
87
  await (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
81
88
  pageFunction: (id) => {
@@ -91,23 +98,26 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
91
98
  page.off('console', logCallback);
92
99
  return page;
93
100
  });
94
- const puppeteerPages = await Promise.all(pages);
95
- const pool = new pool_1.Pool(puppeteerPages);
96
101
  const [firstFrameIndex, lastFrameIndex] = realFrameRange;
97
102
  // Substract one because 100 frames will be 00-99
98
103
  // --> 2 digits
99
104
  const filePadLength = String(lastFrameIndex).length;
100
105
  let framesRendered = 0;
106
+ const poolPromise = getPool(pages);
101
107
  onStart({
102
108
  frameCount,
103
109
  });
104
- const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
105
110
  const assets = new Array(frameCount).fill(undefined);
106
- await Promise.all(new Array(frameCount)
111
+ let stopped = false;
112
+ const progress = Promise.all(new Array(frameCount)
107
113
  .fill(Boolean)
108
- .map((x, i) => i)
114
+ .map((_x, i) => i)
109
115
  .map(async (index) => {
116
+ if (stopped) {
117
+ throw new Error('Render was stopped');
118
+ }
110
119
  const frame = realFrameRange[0] + index;
120
+ const pool = await poolPromise;
111
121
  const freePage = await pool.acquire();
112
122
  const paddedIndex = String(frame).padStart(filePadLength, '0');
113
123
  const errorCallbackOnFrame = (err) => {
@@ -163,7 +173,7 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
163
173
  (0, download_and_map_assets_to_file_1.downloadAndMapAssetsToFileUrl)({
164
174
  asset,
165
175
  downloadDir,
166
- onDownload: onDownload !== null && onDownload !== void 0 ? onDownload : (() => () => undefined),
176
+ onDownload,
167
177
  }).catch((err) => {
168
178
  onError(new Error(`Error while downloading asset: ${err.stack}`));
169
179
  });
@@ -175,19 +185,30 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
175
185
  freePage.off('error', errorCallbackOnFrame);
176
186
  return compressedAssets;
177
187
  }));
178
- const returnValue = {
179
- assetsInfo: {
180
- assets,
181
- downloadDir,
182
- firstFrameIndex,
183
- imageSequenceName: `element-%0${filePadLength}d.${imageFormat}`,
188
+ const prom = Object.assign(new Promise((res, rej) => {
189
+ progress
190
+ .then(() => {
191
+ const returnValue = {
192
+ assetsInfo: {
193
+ assets,
194
+ downloadDir,
195
+ firstFrameIndex,
196
+ imageSequenceName: `element-%0${filePadLength}d.${imageFormat}`,
197
+ },
198
+ frameCount,
199
+ };
200
+ res(returnValue);
201
+ })
202
+ .catch((err) => rej(err));
203
+ }), {
204
+ cancel: () => {
205
+ stopped = true;
184
206
  },
185
- frameCount,
186
- };
187
- return returnValue;
207
+ });
208
+ return prom;
188
209
  };
189
- const renderFrames = async (options) => {
190
- var _a, _b, _c;
210
+ const renderFrames = (options) => {
211
+ var _a, _b, _c, _d;
191
212
  const composition = getComposition(options);
192
213
  if (!composition) {
193
214
  throw new Error('No `composition` option has been specified for renderFrames()');
@@ -202,25 +223,51 @@ const renderFrames = async (options) => {
202
223
  const selectedServeUrl = (0, legacy_webpack_config_1.getServeUrlWithFallback)(options);
203
224
  remotion_1.Internals.validateQuality(options.quality);
204
225
  (0, validate_scale_1.validateScale)(options.scale);
205
- const { closeServer, serveUrl } = await (0, prepare_server_1.prepareServer)(selectedServeUrl);
206
- const browserInstance = (_a = options.puppeteerInstance) !== null && _a !== void 0 ? _a : (await (0, open_browser_1.openBrowser)(remotion_1.Internals.DEFAULT_BROWSER, {
226
+ const browserInstance = (_a = options.puppeteerInstance) !== null && _a !== void 0 ? _a : (0, open_browser_1.openBrowser)(remotion_1.Internals.DEFAULT_BROWSER, {
207
227
  shouldDumpIo: options.dumpBrowserLogs,
208
228
  browserExecutable: options.browserExecutable,
209
229
  chromiumOptions: options.chromiumOptions,
210
230
  forceDeviceScaleFactor: (_b = options.scale) !== null && _b !== void 0 ? _b : 1,
211
- }));
212
- const actualParallelism = (0, get_concurrency_1.getActualConcurrency)((_c = options.parallelism) !== null && _c !== void 0 ? _c : null);
231
+ });
232
+ const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
233
+ const onDownload = (_c = options.onDownload) !== null && _c !== void 0 ? _c : (() => () => undefined);
234
+ const actualParallelism = (0, get_concurrency_1.getActualConcurrency)((_d = options.parallelism) !== null && _d !== void 0 ? _d : null);
213
235
  const { stopCycling } = (0, cycle_browser_tabs_1.cycleBrowserTabs)(browserInstance, actualParallelism);
214
236
  const openedPages = [];
215
- return new Promise((resolve, reject) => {
216
- innerRenderFrames({
217
- ...options,
218
- puppeteerInstance: browserInstance,
219
- onError: (err) => reject(err),
220
- pagesArray: openedPages,
221
- serveUrl,
222
- composition,
223
- actualParallelism,
237
+ let cancel = () => undefined;
238
+ const prom = new Promise((resolve, reject) => {
239
+ var _a, _b;
240
+ let cleanup = null;
241
+ const onError = (err) => reject(err);
242
+ Promise.all([
243
+ (0, prepare_server_1.prepareServer)({
244
+ webpackConfigOrServeUrl: selectedServeUrl,
245
+ downloadDir,
246
+ onDownload,
247
+ onError,
248
+ ffmpegExecutable: (_a = options.ffmpegExecutable) !== null && _a !== void 0 ? _a : null,
249
+ port: (_b = options.port) !== null && _b !== void 0 ? _b : null,
250
+ }),
251
+ browserInstance,
252
+ ])
253
+ .then(([{ serveUrl, closeServer, offthreadPort }, puppeteerInstance]) => {
254
+ cleanup = closeServer;
255
+ const renderFramesProm = innerRenderFrames({
256
+ ...options,
257
+ puppeteerInstance,
258
+ onError,
259
+ pagesArray: openedPages,
260
+ serveUrl,
261
+ composition,
262
+ actualParallelism,
263
+ onDownload,
264
+ downloadDir,
265
+ proxyPort: offthreadPort,
266
+ });
267
+ cancel = () => {
268
+ renderFramesProm.cancel();
269
+ };
270
+ return renderFramesProm;
224
271
  })
225
272
  .then((res) => resolve(res))
226
273
  .catch((err) => reject(err))
@@ -234,13 +281,23 @@ const renderFrames = async (options) => {
234
281
  });
235
282
  }
236
283
  else {
237
- browserInstance.close().catch((err) => {
284
+ Promise.resolve(browserInstance)
285
+ .then((puppeteerInstance) => {
286
+ return puppeteerInstance.close();
287
+ })
288
+ .catch((err) => {
238
289
  console.log('Unable to close browser', err);
239
290
  });
240
291
  }
241
292
  stopCycling();
242
- closeServer();
293
+ cleanup === null || cleanup === void 0 ? void 0 : cleanup();
243
294
  });
244
295
  });
296
+ const returnType = Object.assign(prom, {
297
+ cancel: () => {
298
+ cancel();
299
+ },
300
+ });
301
+ return returnType;
245
302
  };
246
303
  exports.renderFrames = renderFrames;
@@ -1,5 +1,5 @@
1
1
  import type { Browser as PuppeteerBrowser } from 'puppeteer-core';
2
- import { Codec, FfmpegExecutable, FrameRange, PixelFormat, ProResProfile, SmallTCompMetadata } from 'remotion';
2
+ import { BrowserExecutable, Codec, FfmpegExecutable, FrameRange, PixelFormat, ProResProfile, SmallTCompMetadata } from 'remotion';
3
3
  import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
4
4
  import { BrowserLog } from './browser-log';
5
5
  import { ServeUrlOrWebpackBundle } from './legacy-webpack-config';
@@ -37,5 +37,11 @@ export declare type RenderMediaOptions = {
37
37
  timeoutInMilliseconds?: number;
38
38
  chromiumOptions?: ChromiumOptions;
39
39
  scale?: number;
40
+ port?: number | null;
41
+ browserExecutable?: BrowserExecutable;
40
42
  } & ServeUrlOrWebpackBundle;
41
- export declare const renderMedia: ({ parallelism, proResProfile, crf, composition, imageFormat, ffmpegExecutable, inputProps, pixelFormat, codec, envVariables, quality, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, dumpBrowserLogs, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, ...options }: RenderMediaOptions) => Promise<void>;
43
+ declare type RenderMediaReturnType = Promise<void> & {
44
+ cancel: () => void;
45
+ };
46
+ export declare const renderMedia: (options: RenderMediaOptions) => RenderMediaReturnType;
47
+ export {};
@@ -23,7 +23,22 @@ const tmp_dir_1 = require("./tmp-dir");
23
23
  const validate_even_dimensions_with_codec_1 = require("./validate-even-dimensions-with-codec");
24
24
  const validate_output_filename_1 = require("./validate-output-filename");
25
25
  const validate_scale_1 = require("./validate-scale");
26
- const renderMedia = async ({ parallelism, proResProfile, crf, composition, imageFormat, ffmpegExecutable, inputProps, pixelFormat, codec, envVariables, quality, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, dumpBrowserLogs, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, ...options }) => {
26
+ const renderMedia = (options) => {
27
+ let resolve = () => undefined;
28
+ let reject = () => undefined;
29
+ const rejectIfCancel = new Promise((res, rej) => {
30
+ resolve = res;
31
+ reject = rej;
32
+ });
33
+ const prom = innerRenderMedia({ ...options, rejectIfCancel }).then(() => {
34
+ resolve();
35
+ });
36
+ return Object.assign(prom, {
37
+ cancel: () => reject(new Error('Render got cancelled')),
38
+ });
39
+ };
40
+ exports.renderMedia = renderMedia;
41
+ const innerRenderMedia = async ({ parallelism, proResProfile, crf, composition, imageFormat, ffmpegExecutable, inputProps, pixelFormat, codec, envVariables, quality, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, dumpBrowserLogs, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, browserExecutable, port, rejectIfCancel, ...options }) => {
27
42
  var _a;
28
43
  remotion_1.Internals.validateQuality(quality);
29
44
  if (typeof crf !== 'undefined' && crf !== null) {
@@ -39,6 +54,14 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
39
54
  let renderedFrames = 0;
40
55
  let renderedDoneIn = null;
41
56
  let encodedDoneIn = null;
57
+ let cancelRenderFrames = () => undefined;
58
+ rejectIfCancel
59
+ .then(() => undefined)
60
+ .catch((err) => {
61
+ preStitcher === null || preStitcher === void 0 ? void 0 : preStitcher.task.kill();
62
+ cancelRenderFrames();
63
+ throw err;
64
+ });
42
65
  const renderStart = Date.now();
43
66
  const tmpdir = (0, tmp_dir_1.tmpDir)('pre-encode');
44
67
  const parallelEncoding = (0, can_use_parallel_encoding_1.canUseParallelEncoding)(codec);
@@ -87,7 +110,7 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
87
110
  }
88
111
  const realFrameRange = (0, get_frame_to_render_1.getRealFrameRange)(composition.durationInFrames, frameRange !== null && frameRange !== void 0 ? frameRange : null);
89
112
  const { waitForRightTimeOfFrameToBeInserted, setFrameToStitch, waitForFinish, } = (0, ensure_frames_in_order_1.ensureFramesInOrder)(realFrameRange);
90
- const { assetsInfo } = await (0, render_frames_1.renderFrames)({
113
+ const renderFramesProc = (0, render_frames_1.renderFrames)({
91
114
  config: composition,
92
115
  onFrameUpdate: (frame) => {
93
116
  renderedFrames = frame;
@@ -121,7 +144,14 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
121
144
  timeoutInMilliseconds,
122
145
  chromiumOptions,
123
146
  scale,
147
+ ffmpegExecutable,
148
+ browserExecutable,
149
+ port,
124
150
  });
151
+ cancelRenderFrames = () => {
152
+ renderFramesProc.cancel();
153
+ };
154
+ const { assetsInfo } = await renderFramesProc;
125
155
  if (stitcherFfmpeg) {
126
156
  await waitForFinish();
127
157
  (_a = stitcherFfmpeg === null || stitcherFfmpeg === void 0 ? void 0 : stitcherFfmpeg.stdin) === null || _a === void 0 ? void 0 : _a.end();
@@ -165,6 +195,26 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
165
195
  encodedDoneIn = Date.now() - stitchStart;
166
196
  callUpdate();
167
197
  }
198
+ catch (err) {
199
+ /**
200
+ * When an error is thrown in renderFrames(...) (e.g., when delayRender() is used incorrectly), fs.unlinkSync(...) throws an error that the file is locked because ffmpeg is still running, and renderMedia returns it.
201
+ * Therefore we first kill the FFMPEG process before deleting the file
202
+ */
203
+ cancelRenderFrames();
204
+ if (stitcherFfmpeg !== undefined && stitcherFfmpeg.exitCode === null) {
205
+ const promise = new Promise((resolve) => {
206
+ setTimeout(() => {
207
+ resolve();
208
+ }, 2000);
209
+ stitcherFfmpeg.on('close', resolve);
210
+ });
211
+ // Can only kill the process once it has spawned, otherwise getting EPIPE error in Node.JS
212
+ await (preStitcher === null || preStitcher === void 0 ? void 0 : preStitcher.waitForSpawn);
213
+ stitcherFfmpeg.kill();
214
+ await promise;
215
+ }
216
+ throw err;
217
+ }
168
218
  finally {
169
219
  if (preEncodedFileLocation !== null &&
170
220
  fs_1.default.existsSync(preEncodedFileLocation)) {
@@ -172,4 +222,3 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
172
222
  }
173
223
  }
174
224
  };
175
- exports.renderMedia = renderMedia;
@@ -1,5 +1,6 @@
1
1
  import { Browser as PuppeteerBrowser } from 'puppeteer-core';
2
- import { BrowserExecutable, StillImageFormat, TCompMetadata } from 'remotion';
2
+ import { BrowserExecutable, FfmpegExecutable, StillImageFormat, TCompMetadata } from 'remotion';
3
+ import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
3
4
  import { ServeUrlOrWebpackBundle } from './legacy-webpack-config';
4
5
  import { ChromiumOptions } from './open-browser';
5
6
  declare type InnerStillOptions = {
@@ -17,8 +18,12 @@ declare type InnerStillOptions = {
17
18
  timeoutInMilliseconds?: number;
18
19
  chromiumOptions?: ChromiumOptions;
19
20
  scale?: number;
21
+ onDownload?: RenderMediaOnDownload;
22
+ ffmpegExecutable?: FfmpegExecutable;
23
+ };
24
+ declare type RenderStillOptions = InnerStillOptions & ServeUrlOrWebpackBundle & {
25
+ port?: number | null;
20
26
  };
21
- declare type RenderStillOptions = InnerStillOptions & ServeUrlOrWebpackBundle;
22
27
  /**
23
28
  * @description Render a still frame from a composition and returns an image path
24
29
  */
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -29,6 +33,7 @@ const remotion_1 = require("remotion");
29
33
  const ensure_output_directory_1 = require("./ensure-output-directory");
30
34
  const handle_javascript_exception_1 = require("./error-handling/handle-javascript-exception");
31
35
  const legacy_webpack_config_1 = require("./legacy-webpack-config");
36
+ const make_assets_download_dir_1 = require("./make-assets-download-dir");
32
37
  const open_browser_1 = require("./open-browser");
33
38
  const prepare_server_1 = require("./prepare-server");
34
39
  const provide_screenshot_1 = require("./provide-screenshot");
@@ -37,7 +42,7 @@ const seek_to_frame_1 = require("./seek-to-frame");
37
42
  const set_props_and_env_1 = require("./set-props-and-env");
38
43
  const validate_puppeteer_timeout_1 = require("./validate-puppeteer-timeout");
39
44
  const validate_scale_1 = require("./validate-scale");
40
- const innerRenderStill = async ({ composition, quality, imageFormat = 'png', serveUrl, puppeteerInstance, dumpBrowserLogs = false, onError, inputProps, envVariables, output, frame = 0, overwrite = true, browserExecutable, timeoutInMilliseconds, chromiumOptions, scale, }) => {
45
+ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', serveUrl, puppeteerInstance, dumpBrowserLogs = false, onError, inputProps, envVariables, output, frame = 0, overwrite = true, browserExecutable, timeoutInMilliseconds, chromiumOptions, scale, proxyPort, }) => {
41
46
  remotion_1.Internals.validateDimension(composition.height, 'height', 'in the `config` object passed to `renderStill()`');
42
47
  remotion_1.Internals.validateDimension(composition.width, 'width', 'in the `config` object passed to `renderStill()`');
43
48
  remotion_1.Internals.validateFps(composition.fps, 'in the `config` object of `renderStill()`');
@@ -103,6 +108,8 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
103
108
  serveUrl,
104
109
  initialFrame: frame,
105
110
  timeoutInMilliseconds,
111
+ proxyPort,
112
+ retriesRemaining: 2,
106
113
  });
107
114
  await (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
108
115
  pageFunction: (id) => {
@@ -130,18 +137,35 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
130
137
  /**
131
138
  * @description Render a still frame from a composition and returns an image path
132
139
  */
133
- const renderStill = async (options) => {
140
+ const renderStill = (options) => {
141
+ var _a;
134
142
  const selectedServeUrl = (0, legacy_webpack_config_1.getServeUrlWithFallback)(options);
135
- const { closeServer, serveUrl } = await (0, prepare_server_1.prepareServer)(selectedServeUrl);
143
+ const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
144
+ const onDownload = (_a = options.onDownload) !== null && _a !== void 0 ? _a : (() => () => undefined);
136
145
  return new Promise((resolve, reject) => {
137
- innerRenderStill({
138
- ...options,
139
- serveUrl,
140
- onError: (err) => reject(err),
146
+ var _a, _b;
147
+ const onError = (err) => reject(err);
148
+ let close = null;
149
+ (0, prepare_server_1.prepareServer)({
150
+ webpackConfigOrServeUrl: selectedServeUrl,
151
+ downloadDir,
152
+ onDownload,
153
+ onError,
154
+ ffmpegExecutable: (_a = options.ffmpegExecutable) !== null && _a !== void 0 ? _a : null,
155
+ port: (_b = options.port) !== null && _b !== void 0 ? _b : null,
156
+ })
157
+ .then(({ serveUrl, closeServer, offthreadPort }) => {
158
+ close = closeServer;
159
+ return innerRenderStill({
160
+ ...options,
161
+ serveUrl,
162
+ onError: (err) => reject(err),
163
+ proxyPort: offthreadPort,
164
+ });
141
165
  })
142
166
  .then((res) => resolve(res))
143
167
  .catch((err) => reject(err))
144
- .finally(() => closeServer());
168
+ .finally(() => close === null || close === void 0 ? void 0 : close());
145
169
  });
146
170
  };
147
171
  exports.renderStill = renderStill;
@@ -1,6 +1,12 @@
1
- export declare const serveStatic: (path: string, options?: {
2
- port?: number | undefined;
3
- } | undefined) => Promise<{
1
+ import { FfmpegExecutable } from 'remotion';
2
+ import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
3
+ export declare const serveStatic: (path: string | null, options: {
4
+ port: number | null;
5
+ ffmpegExecutable: FfmpegExecutable;
6
+ downloadDir: string;
7
+ onDownload: RenderMediaOnDownload;
8
+ onError: (err: Error) => void;
9
+ }) => Promise<{
4
10
  port: number;
5
11
  close: () => Promise<void>;
6
12
  }>;
@@ -8,12 +8,28 @@ const http_1 = __importDefault(require("http"));
8
8
  const remotion_1 = require("remotion");
9
9
  const serve_handler_1 = __importDefault(require("serve-handler"));
10
10
  const get_port_1 = require("./get-port");
11
+ const offthread_video_server_1 = require("./offthread-video-server");
11
12
  const serveStatic = async (path, options) => {
12
13
  var _a, _b;
13
14
  const port = await (0, get_port_1.getDesiredPort)((_b = (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : remotion_1.Internals.getServerPort()) !== null && _b !== void 0 ? _b : undefined, 3000, 3100);
15
+ const offthreadRequest = (0, offthread_video_server_1.startOffthreadVideoServer)({
16
+ ffmpegExecutable: options.ffmpegExecutable,
17
+ downloadDir: options.downloadDir,
18
+ onDownload: options.onDownload,
19
+ onError: options.onError,
20
+ });
14
21
  try {
15
22
  const server = http_1.default
16
23
  .createServer((request, response) => {
24
+ var _a;
25
+ if ((_a = request.url) === null || _a === void 0 ? void 0 : _a.startsWith('/proxy')) {
26
+ return offthreadRequest(request, response);
27
+ }
28
+ if (path === null) {
29
+ response.writeHead(404);
30
+ response.end('Server only supports /proxy');
31
+ return;
32
+ }
17
33
  (0, serve_handler_1.default)(request, response, {
18
34
  public: path,
19
35
  directoryListing: false,
@@ -1,9 +1,11 @@
1
1
  import { Page } from 'puppeteer-core';
2
- export declare const setPropsAndEnv: ({ inputProps, envVariables, page, serveUrl, initialFrame, timeoutInMilliseconds, }: {
2
+ export declare const setPropsAndEnv: ({ inputProps, envVariables, page, serveUrl, initialFrame, timeoutInMilliseconds, proxyPort, retriesRemaining, }: {
3
3
  inputProps: unknown;
4
4
  envVariables: Record<string, string> | undefined;
5
5
  page: Page;
6
6
  serveUrl: string;
7
7
  initialFrame: number;
8
8
  timeoutInMilliseconds: number | undefined;
9
+ proxyPort: number;
10
+ retriesRemaining: number;
9
11
  }) => Promise<void>;