@remotion/renderer 4.0.0-offthread.34 → 4.0.0-offthread.6

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 (39) hide show
  1. package/dist/assets/download-and-map-assets-to-file.d.ts +1 -1
  2. package/dist/assets/download-and-map-assets-to-file.js +30 -13
  3. package/dist/combine-videos.d.ts +2 -1
  4. package/dist/combine-videos.js +3 -1
  5. package/dist/create-ffmpeg-complex-filter.d.ts +1 -4
  6. package/dist/cycle-browser-tabs.d.ts +2 -1
  7. package/dist/cycle-browser-tabs.js +9 -2
  8. package/dist/extract-frame-from-video.d.ts +0 -5
  9. package/dist/extract-frame-from-video.js +17 -7
  10. package/dist/get-compositions.d.ts +2 -1
  11. package/dist/get-compositions.js +3 -1
  12. package/dist/index.d.ts +2 -13
  13. package/dist/index.js +3 -5
  14. package/dist/last-frame-from-video-cache.d.ts +11 -0
  15. package/dist/last-frame-from-video-cache.js +52 -0
  16. package/dist/make-cancel-signal.d.ts +7 -0
  17. package/dist/make-cancel-signal.js +25 -0
  18. package/dist/merge-audio-track.js +2 -2
  19. package/dist/offthread-video-server.d.ts +6 -1
  20. package/dist/offthread-video-server.js +5 -4
  21. package/dist/open-browser.d.ts +5 -5
  22. package/dist/open-browser.js +2 -1
  23. package/dist/prepare-server.d.ts +2 -1
  24. package/dist/prepare-server.js +6 -6
  25. package/dist/prespawn-ffmpeg.d.ts +2 -0
  26. package/dist/prespawn-ffmpeg.js +3 -0
  27. package/dist/puppeteer-screenshot.js +5 -1
  28. package/dist/render-frames.d.ts +3 -0
  29. package/dist/render-frames.js +76 -37
  30. package/dist/render-media.d.ts +6 -2
  31. package/dist/render-media.js +117 -55
  32. package/dist/render-still.d.ts +7 -3
  33. package/dist/render-still.js +30 -12
  34. package/dist/serve-static.js +9 -1
  35. package/dist/set-props-and-env.d.ts +2 -1
  36. package/dist/set-props-and-env.js +22 -3
  37. package/dist/stitch-frames-to-video.d.ts +3 -1
  38. package/dist/stitch-frames-to-video.js +27 -14
  39. package/package.json +4 -4
@@ -4,6 +4,7 @@ import { BrowserExecutable, FfmpegExecutable, FrameRange, ImageFormat, SmallTCom
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';
7
+ import { CancelSignal } from './make-cancel-signal';
7
8
  import { ChromiumOptions } from './open-browser';
8
9
  import { OnStartData, RenderFramesOutput } from './types';
9
10
  declare type ConfigOrComposition = {
@@ -34,6 +35,8 @@ declare type RenderFramesOptions = {
34
35
  chromiumOptions?: ChromiumOptions;
35
36
  scale?: number;
36
37
  ffmpegExecutable?: FfmpegExecutable;
38
+ port?: number | null;
39
+ cancelSignal?: CancelSignal;
37
40
  } & ConfigOrComposition & ServeUrlOrWebpackBundle;
38
41
  export declare const renderFrames: (options: RenderFramesOptions) => Promise<RenderFramesOutput>;
39
42
  export {};
@@ -33,9 +33,14 @@ 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, downloadDir, proxyPort, }) => {
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, cancelSignal, }) => {
37
42
  if (!puppeteerInstance) {
38
- throw new Error('weird');
43
+ throw new Error('no puppeteer instance passed to innerRenderFrames - internal error');
39
44
  }
40
45
  if (outputDir) {
41
46
  if (!fs_1.default.existsSync(outputDir)) {
@@ -49,7 +54,7 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
49
54
  const pages = new Array(actualParallelism).fill(true).map(async () => {
50
55
  const page = await puppeteerInstance.newPage();
51
56
  pagesArray.push(page);
52
- page.setViewport({
57
+ await page.setViewport({
53
58
  width: composition.width,
54
59
  height: composition.height,
55
60
  deviceScaleFactor: scale !== null && scale !== void 0 ? scale : 1,
@@ -77,6 +82,7 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
77
82
  initialFrame,
78
83
  timeoutInMilliseconds,
79
84
  proxyPort,
85
+ retriesRemaining: 2,
80
86
  });
81
87
  await (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
82
88
  pageFunction: (id) => {
@@ -92,23 +98,30 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
92
98
  page.off('console', logCallback);
93
99
  return page;
94
100
  });
95
- const puppeteerPages = await Promise.all(pages);
96
- const pool = new pool_1.Pool(puppeteerPages);
97
101
  const [firstFrameIndex, lastFrameIndex] = realFrameRange;
98
102
  // Substract one because 100 frames will be 00-99
99
103
  // --> 2 digits
100
104
  const filePadLength = String(lastFrameIndex).length;
101
105
  let framesRendered = 0;
106
+ const poolPromise = getPool(pages);
102
107
  onStart({
103
108
  frameCount,
104
109
  });
105
110
  const assets = new Array(frameCount).fill(undefined);
106
- await Promise.all(new Array(frameCount)
111
+ let stopped = false;
112
+ cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
113
+ stopped = true;
114
+ });
115
+ const progress = Promise.all(new Array(frameCount)
107
116
  .fill(Boolean)
108
- .map((x, i) => i)
117
+ .map((_x, i) => i)
109
118
  .map(async (index) => {
110
119
  const frame = realFrameRange[0] + index;
120
+ const pool = await poolPromise;
111
121
  const freePage = await pool.acquire();
122
+ if (stopped) {
123
+ throw new Error('Render was stopped');
124
+ }
112
125
  const paddedIndex = String(frame).padStart(filePadLength, '0');
113
126
  const errorCallbackOnFrame = (err) => {
114
127
  onError(err);
@@ -175,18 +188,28 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
175
188
  freePage.off('error', errorCallbackOnFrame);
176
189
  return compressedAssets;
177
190
  }));
178
- const returnValue = {
179
- assetsInfo: {
180
- assets,
181
- downloadDir,
182
- firstFrameIndex,
183
- imageSequenceName: `element-%0${filePadLength}d.${imageFormat}`,
184
- },
185
- frameCount,
186
- };
187
- return returnValue;
191
+ const happyPath = progress.then(() => {
192
+ const returnValue = {
193
+ assetsInfo: {
194
+ assets,
195
+ downloadDir,
196
+ firstFrameIndex,
197
+ imageSequenceName: `element-%0${filePadLength}d.${imageFormat}`,
198
+ },
199
+ frameCount,
200
+ };
201
+ return returnValue;
202
+ });
203
+ return Promise.race([
204
+ happyPath,
205
+ new Promise((_resolve, reject) => {
206
+ cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
207
+ reject(new Error('renderFrames() got cancelled'));
208
+ });
209
+ }),
210
+ ]);
188
211
  };
189
- const renderFrames = async (options) => {
212
+ const renderFrames = (options) => {
190
213
  var _a, _b, _c, _d;
191
214
  const composition = getComposition(options);
192
215
  if (!composition) {
@@ -202,33 +225,43 @@ const renderFrames = async (options) => {
202
225
  const selectedServeUrl = (0, legacy_webpack_config_1.getServeUrlWithFallback)(options);
203
226
  remotion_1.Internals.validateQuality(options.quality);
204
227
  (0, validate_scale_1.validateScale)(options.scale);
205
- const browserInstance = (_a = options.puppeteerInstance) !== null && _a !== void 0 ? _a : (await (0, open_browser_1.openBrowser)(remotion_1.Internals.DEFAULT_BROWSER, {
228
+ const browserInstance = (_a = options.puppeteerInstance) !== null && _a !== void 0 ? _a : (0, open_browser_1.openBrowser)(remotion_1.Internals.DEFAULT_BROWSER, {
206
229
  shouldDumpIo: options.dumpBrowserLogs,
207
230
  browserExecutable: options.browserExecutable,
208
231
  chromiumOptions: options.chromiumOptions,
209
232
  forceDeviceScaleFactor: (_b = options.scale) !== null && _b !== void 0 ? _b : 1,
210
- }));
233
+ });
211
234
  const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
212
235
  const onDownload = (_c = options.onDownload) !== null && _c !== void 0 ? _c : (() => () => undefined);
213
236
  const actualParallelism = (0, get_concurrency_1.getActualConcurrency)((_d = options.parallelism) !== null && _d !== void 0 ? _d : null);
214
- const { stopCycling } = (0, cycle_browser_tabs_1.cycleBrowserTabs)(browserInstance, actualParallelism);
215
237
  const openedPages = [];
216
238
  return new Promise((resolve, reject) => {
217
- var _a;
218
- let cleanup = null;
239
+ var _a, _b;
240
+ const cleanup = [];
219
241
  const onError = (err) => reject(err);
220
- (0, prepare_server_1.prepareServer)({
221
- webpackConfigOrServeUrl: selectedServeUrl,
222
- downloadDir,
223
- onDownload,
224
- onError,
225
- ffmpegExecutable: (_a = options.ffmpegExecutable) !== null && _a !== void 0 ? _a : null,
226
- })
227
- .then(({ serveUrl, closeServer, offthreadPort }) => {
228
- cleanup = closeServer;
229
- return innerRenderFrames({
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
+ var _a;
255
+ const { stopCycling } = (0, cycle_browser_tabs_1.cycleBrowserTabs)(puppeteerInstance, actualParallelism);
256
+ cleanup.push(stopCycling);
257
+ (_a = options.cancelSignal) === null || _a === void 0 ? void 0 : _a.call(options, () => {
258
+ stopCycling();
259
+ closeServer();
260
+ });
261
+ cleanup.push(closeServer);
262
+ const renderFramesProm = innerRenderFrames({
230
263
  ...options,
231
- puppeteerInstance: browserInstance,
264
+ puppeteerInstance,
232
265
  onError,
233
266
  pagesArray: openedPages,
234
267
  serveUrl,
@@ -238,6 +271,7 @@ const renderFrames = async (options) => {
238
271
  downloadDir,
239
272
  proxyPort: offthreadPort,
240
273
  });
274
+ return renderFramesProm;
241
275
  })
242
276
  .then((res) => resolve(res))
243
277
  .catch((err) => reject(err))
@@ -251,12 +285,17 @@ const renderFrames = async (options) => {
251
285
  });
252
286
  }
253
287
  else {
254
- browserInstance.close().catch((err) => {
288
+ Promise.resolve(browserInstance)
289
+ .then((puppeteerInstance) => {
290
+ return puppeteerInstance.close();
291
+ })
292
+ .catch((err) => {
255
293
  console.log('Unable to close browser', err);
256
294
  });
257
295
  }
258
- stopCycling();
259
- cleanup === null || cleanup === void 0 ? void 0 : cleanup();
296
+ cleanup.forEach((c) => {
297
+ c();
298
+ });
260
299
  });
261
300
  });
262
301
  };
@@ -1,8 +1,9 @@
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';
6
+ import { CancelSignal } from './make-cancel-signal';
6
7
  import { ChromiumOptions } from './open-browser';
7
8
  import { OnStartData } from './types';
8
9
  export declare type StitchingState = 'encoding' | 'muxing';
@@ -37,5 +38,8 @@ export declare type RenderMediaOptions = {
37
38
  timeoutInMilliseconds?: number;
38
39
  chromiumOptions?: ChromiumOptions;
39
40
  scale?: number;
41
+ port?: number | null;
42
+ cancelSignal?: CancelSignal;
43
+ browserExecutable?: BrowserExecutable;
40
44
  } & 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>;
45
+ 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, browserExecutable, port, cancelSignal, ...options }: RenderMediaOptions) => Promise<void>;
@@ -16,6 +16,7 @@ const get_extension_from_codec_1 = require("./get-extension-from-codec");
16
16
  const get_extension_of_filename_1 = require("./get-extension-of-filename");
17
17
  const get_frame_to_render_1 = require("./get-frame-to-render");
18
18
  const legacy_webpack_config_1 = require("./legacy-webpack-config");
19
+ const make_cancel_signal_1 = require("./make-cancel-signal");
19
20
  const prespawn_ffmpeg_1 = require("./prespawn-ffmpeg");
20
21
  const render_frames_1 = require("./render-frames");
21
22
  const stitch_frames_to_video_1 = require("./stitch-frames-to-video");
@@ -23,8 +24,7 @@ const tmp_dir_1 = require("./tmp-dir");
23
24
  const validate_even_dimensions_with_codec_1 = require("./validate-even-dimensions-with-codec");
24
25
  const validate_output_filename_1 = require("./validate-output-filename");
25
26
  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 }) => {
27
- var _a;
27
+ 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, browserExecutable, port, cancelSignal, ...options }) => {
28
28
  remotion_1.Internals.validateQuality(quality);
29
29
  if (typeof crf !== 'undefined' && crf !== null) {
30
30
  remotion_1.Internals.validateSelectedCrfAndCodecCombination(crf, codec);
@@ -39,6 +39,7 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
39
39
  let renderedFrames = 0;
40
40
  let renderedDoneIn = null;
41
41
  let encodedDoneIn = null;
42
+ let cancelled = false;
42
43
  const renderStart = Date.now();
43
44
  const tmpdir = (0, tmp_dir_1.tmpDir)('pre-encode');
44
45
  const parallelEncoding = (0, can_use_parallel_encoding_1.canUseParallelEncoding)(codec);
@@ -48,23 +49,31 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
48
49
  : null;
49
50
  const outputDir = parallelEncoding
50
51
  ? null
51
- : await fs_1.default.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'react-motion-render'));
52
+ : fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'react-motion-render'));
52
53
  (0, validate_even_dimensions_with_codec_1.validateEvenDimensionsWithCodec)({
53
54
  codec,
54
55
  height: composition.height,
55
56
  scale: scale !== null && scale !== void 0 ? scale : 1,
56
57
  width: composition.width,
57
58
  });
58
- try {
59
- const callUpdate = () => {
60
- onProgress === null || onProgress === void 0 ? void 0 : onProgress({
61
- encodedDoneIn,
62
- encodedFrames,
63
- renderedDoneIn,
64
- renderedFrames,
65
- stitchStage,
66
- });
67
- };
59
+ const callUpdate = () => {
60
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress({
61
+ encodedDoneIn,
62
+ encodedFrames,
63
+ renderedDoneIn,
64
+ renderedFrames,
65
+ stitchStage,
66
+ });
67
+ };
68
+ const realFrameRange = (0, get_frame_to_render_1.getRealFrameRange)(composition.durationInFrames, frameRange !== null && frameRange !== void 0 ? frameRange : null);
69
+ const cancelRenderFrames = (0, make_cancel_signal_1.makeCancelSignal)();
70
+ const cancelPrestitcher = (0, make_cancel_signal_1.makeCancelSignal)();
71
+ const cancelStitcher = (0, make_cancel_signal_1.makeCancelSignal)();
72
+ cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
73
+ cancelRenderFrames.cancel();
74
+ });
75
+ const { waitForRightTimeOfFrameToBeInserted, setFrameToStitch, waitForFinish } = (0, ensure_frames_in_order_1.ensureFramesInOrder)(realFrameRange);
76
+ const createPrestitcherIfNecessary = async () => {
68
77
  if (preEncodedFileLocation) {
69
78
  preStitcher = await (0, prespawn_ffmpeg_1.prespawnFfmpeg)({
70
79
  width: composition.width * (scale !== null && scale !== void 0 ? scale : 1),
@@ -82,12 +91,27 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
82
91
  verbose: remotion_1.Internals.Logging.isEqualOrBelowLogLevel(remotion_1.Internals.Logging.getLogLevel(), 'verbose'),
83
92
  ffmpegExecutable,
84
93
  imageFormat: actualImageFormat,
94
+ signal: cancelPrestitcher.cancelSignal,
85
95
  });
86
96
  stitcherFfmpeg = preStitcher.task;
87
97
  }
88
- const realFrameRange = (0, get_frame_to_render_1.getRealFrameRange)(composition.durationInFrames, frameRange !== null && frameRange !== void 0 ? frameRange : null);
89
- const { waitForRightTimeOfFrameToBeInserted, setFrameToStitch, waitForFinish, } = (0, ensure_frames_in_order_1.ensureFramesInOrder)(realFrameRange);
90
- const { assetsInfo } = await (0, render_frames_1.renderFrames)({
98
+ };
99
+ const waitForPrestitcherIfNecessary = async () => {
100
+ var _a;
101
+ if (stitcherFfmpeg) {
102
+ await waitForFinish();
103
+ (_a = stitcherFfmpeg === null || stitcherFfmpeg === void 0 ? void 0 : stitcherFfmpeg.stdin) === null || _a === void 0 ? void 0 : _a.end();
104
+ try {
105
+ await stitcherFfmpeg;
106
+ }
107
+ catch (err) {
108
+ throw new Error(preStitcher === null || preStitcher === void 0 ? void 0 : preStitcher.getLogs());
109
+ }
110
+ }
111
+ };
112
+ const happyPath = createPrestitcherIfNecessary()
113
+ .then(() => {
114
+ const renderFramesProc = (0, render_frames_1.renderFrames)({
91
115
  config: composition,
92
116
  onFrameUpdate: (frame) => {
93
117
  renderedFrames = frame;
@@ -110,6 +134,9 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
110
134
  ? async (buffer, frame) => {
111
135
  var _a;
112
136
  await waitForRightTimeOfFrameToBeInserted(frame);
137
+ if (cancelled) {
138
+ return;
139
+ }
113
140
  (_a = stitcherFfmpeg === null || stitcherFfmpeg === void 0 ? void 0 : stitcherFfmpeg.stdin) === null || _a === void 0 ? void 0 : _a.write(buffer);
114
141
  setFrameToStitch(frame + 1);
115
142
  }
@@ -121,57 +148,92 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
121
148
  timeoutInMilliseconds,
122
149
  chromiumOptions,
123
150
  scale,
124
- // TODO: Document new property for FFMPEG executable
125
151
  ffmpegExecutable,
152
+ browserExecutable,
153
+ port,
154
+ cancelSignal: cancelRenderFrames.cancelSignal,
126
155
  });
127
- if (stitcherFfmpeg) {
128
- await waitForFinish();
129
- (_a = stitcherFfmpeg === null || stitcherFfmpeg === void 0 ? void 0 : stitcherFfmpeg.stdin) === null || _a === void 0 ? void 0 : _a.end();
130
- try {
131
- await stitcherFfmpeg;
132
- }
133
- catch (err) {
134
- throw new Error(preStitcher === null || preStitcher === void 0 ? void 0 : preStitcher.getLogs());
135
- }
136
- }
156
+ return renderFramesProc;
157
+ })
158
+ .then((renderFramesReturn) => {
159
+ return Promise.all([renderFramesReturn, waitForPrestitcherIfNecessary()]);
160
+ })
161
+ .then(([{ assetsInfo }]) => {
137
162
  renderedDoneIn = Date.now() - renderStart;
138
163
  callUpdate();
139
164
  (0, ensure_output_directory_1.ensureOutputDirectory)(outputLocation);
140
165
  const stitchStart = Date.now();
141
- await (0, stitch_frames_to_video_1.stitchFramesToVideo)({
142
- width: composition.width * (scale !== null && scale !== void 0 ? scale : 1),
143
- height: composition.height * (scale !== null && scale !== void 0 ? scale : 1),
144
- fps: composition.fps,
145
- outputLocation,
146
- internalOptions: {
147
- preEncodedFileLocation,
148
- imageFormat: actualImageFormat,
149
- },
150
- force: overwrite !== null && overwrite !== void 0 ? overwrite : remotion_1.Internals.DEFAULT_OVERWRITE,
151
- pixelFormat,
152
- codec,
153
- proResProfile,
154
- crf,
155
- assetsInfo,
156
- ffmpegExecutable,
157
- onProgress: (frame) => {
158
- stitchStage = 'muxing';
159
- encodedFrames = frame;
160
- callUpdate();
161
- },
162
- onDownload,
163
- verbose: remotion_1.Internals.Logging.isEqualOrBelowLogLevel(remotion_1.Internals.Logging.getLogLevel(), 'verbose'),
164
- dir: outputDir !== null && outputDir !== void 0 ? outputDir : undefined,
165
- });
166
+ return Promise.all([
167
+ (0, stitch_frames_to_video_1.stitchFramesToVideo)({
168
+ width: composition.width * (scale !== null && scale !== void 0 ? scale : 1),
169
+ height: composition.height * (scale !== null && scale !== void 0 ? scale : 1),
170
+ fps: composition.fps,
171
+ outputLocation,
172
+ internalOptions: {
173
+ preEncodedFileLocation,
174
+ imageFormat: actualImageFormat,
175
+ },
176
+ force: overwrite !== null && overwrite !== void 0 ? overwrite : remotion_1.Internals.DEFAULT_OVERWRITE,
177
+ pixelFormat,
178
+ codec,
179
+ proResProfile,
180
+ crf,
181
+ assetsInfo,
182
+ ffmpegExecutable,
183
+ onProgress: (frame) => {
184
+ stitchStage = 'muxing';
185
+ encodedFrames = frame;
186
+ callUpdate();
187
+ },
188
+ onDownload,
189
+ verbose: remotion_1.Internals.Logging.isEqualOrBelowLogLevel(remotion_1.Internals.Logging.getLogLevel(), 'verbose'),
190
+ dir: outputDir !== null && outputDir !== void 0 ? outputDir : undefined,
191
+ cancelSignal: cancelStitcher.cancelSignal,
192
+ }),
193
+ stitchStart,
194
+ ]);
195
+ })
196
+ .then(([, stitchStart]) => {
166
197
  encodedFrames = (0, get_duration_from_frame_range_1.getDurationFromFrameRange)(frameRange !== null && frameRange !== void 0 ? frameRange : null, composition.durationInFrames);
167
198
  encodedDoneIn = Date.now() - stitchStart;
168
199
  callUpdate();
169
- }
170
- finally {
200
+ })
201
+ .catch((err) => {
202
+ /**
203
+ * 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.
204
+ * Therefore we first kill the FFMPEG process before deleting the file
205
+ */
206
+ cancelled = true;
207
+ cancelRenderFrames.cancel();
208
+ cancelStitcher.cancel();
209
+ cancelPrestitcher.cancel();
210
+ if (stitcherFfmpeg !== undefined && stitcherFfmpeg.exitCode === null) {
211
+ const promise = new Promise((resolve) => {
212
+ setTimeout(() => {
213
+ resolve();
214
+ }, 2000);
215
+ stitcherFfmpeg.on('close', resolve);
216
+ });
217
+ stitcherFfmpeg.kill();
218
+ return promise.then(() => {
219
+ throw err;
220
+ });
221
+ }
222
+ throw err;
223
+ })
224
+ .finally(() => {
171
225
  if (preEncodedFileLocation !== null &&
172
226
  fs_1.default.existsSync(preEncodedFileLocation)) {
173
227
  fs_1.default.unlinkSync(preEncodedFileLocation);
174
228
  }
175
- }
229
+ });
230
+ return Promise.race([
231
+ happyPath,
232
+ new Promise((_resolve, reject) => {
233
+ cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
234
+ reject(new Error('renderMedia() got cancelled'));
235
+ });
236
+ }),
237
+ ]);
176
238
  };
177
239
  exports.renderMedia = renderMedia;
@@ -1,10 +1,11 @@
1
1
  import { Browser as PuppeteerBrowser } from 'puppeteer-core';
2
- import { BrowserExecutable, FfmpegExecutable, StillImageFormat, TCompMetadata } from 'remotion';
2
+ import { BrowserExecutable, FfmpegExecutable, SmallTCompMetadata, StillImageFormat } from 'remotion';
3
3
  import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
4
4
  import { ServeUrlOrWebpackBundle } from './legacy-webpack-config';
5
+ import { CancelSignal } from './make-cancel-signal';
5
6
  import { ChromiumOptions } from './open-browser';
6
7
  declare type InnerStillOptions = {
7
- composition: TCompMetadata;
8
+ composition: SmallTCompMetadata;
8
9
  output: string;
9
10
  frame?: number;
10
11
  inputProps?: unknown;
@@ -19,9 +20,12 @@ declare type InnerStillOptions = {
19
20
  chromiumOptions?: ChromiumOptions;
20
21
  scale?: number;
21
22
  onDownload?: RenderMediaOnDownload;
23
+ cancelSignal?: CancelSignal;
22
24
  ffmpegExecutable?: FfmpegExecutable;
23
25
  };
24
- declare type RenderStillOptions = InnerStillOptions & ServeUrlOrWebpackBundle;
26
+ declare type RenderStillOptions = InnerStillOptions & ServeUrlOrWebpackBundle & {
27
+ port?: number | null;
28
+ };
25
29
  /**
26
30
  * @description Render a still frame from a composition and returns an image path
27
31
  */
@@ -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];
@@ -38,7 +42,7 @@ const seek_to_frame_1 = require("./seek-to-frame");
38
42
  const set_props_and_env_1 = require("./set-props-and-env");
39
43
  const validate_puppeteer_timeout_1 = require("./validate-puppeteer-timeout");
40
44
  const validate_scale_1 = require("./validate-scale");
41
- const innerRenderStill = async ({ composition, quality, imageFormat = 'png', serveUrl, puppeteerInstance, dumpBrowserLogs = false, onError, inputProps, envVariables, output, frame = 0, overwrite = true, browserExecutable, timeoutInMilliseconds, chromiumOptions, scale, proxyPort, }) => {
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, cancelSignal, }) => {
42
46
  remotion_1.Internals.validateDimension(composition.height, 'height', 'in the `config` object passed to `renderStill()`');
43
47
  remotion_1.Internals.validateDimension(composition.width, 'width', 'in the `config` object passed to `renderStill()`');
44
48
  remotion_1.Internals.validateFps(composition.fps, 'in the `config` object of `renderStill()`');
@@ -72,11 +76,20 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
72
76
  forceDeviceScaleFactor: scale !== null && scale !== void 0 ? scale : 1,
73
77
  }));
74
78
  const page = await browserInstance.newPage();
75
- page.setViewport({
79
+ await page.setViewport({
76
80
  width: composition.width,
77
81
  height: composition.height,
78
82
  deviceScaleFactor: scale !== null && scale !== void 0 ? scale : 1,
79
83
  });
84
+ const errorCallback = (err) => {
85
+ onError(err);
86
+ cleanup();
87
+ };
88
+ const cleanUpJSException = (0, handle_javascript_exception_1.handleJavascriptException)({
89
+ page,
90
+ onError: errorCallback,
91
+ frame: null,
92
+ });
80
93
  const cleanup = async () => {
81
94
  cleanUpJSException();
82
95
  if (puppeteerInstance) {
@@ -88,14 +101,8 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
88
101
  });
89
102
  }
90
103
  };
91
- const errorCallback = (err) => {
92
- onError(err);
104
+ cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
93
105
  cleanup();
94
- };
95
- const cleanUpJSException = (0, handle_javascript_exception_1.handleJavascriptException)({
96
- page,
97
- onError: errorCallback,
98
- frame: null,
99
106
  });
100
107
  await (0, set_props_and_env_1.setPropsAndEnv)({
101
108
  inputProps,
@@ -105,6 +112,7 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
105
112
  initialFrame: frame,
106
113
  timeoutInMilliseconds,
107
114
  proxyPort,
115
+ retriesRemaining: 2,
108
116
  });
109
117
  await (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
110
118
  pageFunction: (id) => {
@@ -137,8 +145,8 @@ const renderStill = (options) => {
137
145
  const selectedServeUrl = (0, legacy_webpack_config_1.getServeUrlWithFallback)(options);
138
146
  const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
139
147
  const onDownload = (_a = options.onDownload) !== null && _a !== void 0 ? _a : (() => () => undefined);
140
- return new Promise((resolve, reject) => {
141
- var _a;
148
+ const happyPath = new Promise((resolve, reject) => {
149
+ var _a, _b;
142
150
  const onError = (err) => reject(err);
143
151
  let close = null;
144
152
  (0, prepare_server_1.prepareServer)({
@@ -147,6 +155,7 @@ const renderStill = (options) => {
147
155
  onDownload,
148
156
  onError,
149
157
  ffmpegExecutable: (_a = options.ffmpegExecutable) !== null && _a !== void 0 ? _a : null,
158
+ port: (_b = options.port) !== null && _b !== void 0 ? _b : null,
150
159
  })
151
160
  .then(({ serveUrl, closeServer, offthreadPort }) => {
152
161
  close = closeServer;
@@ -161,5 +170,14 @@ const renderStill = (options) => {
161
170
  .catch((err) => reject(err))
162
171
  .finally(() => close === null || close === void 0 ? void 0 : close());
163
172
  });
173
+ return Promise.race([
174
+ happyPath,
175
+ new Promise((_resolve, reject) => {
176
+ var _a;
177
+ (_a = options.cancelSignal) === null || _a === void 0 ? void 0 : _a.call(options, () => {
178
+ reject(new Error('renderStill() got cancelled'));
179
+ });
180
+ }),
181
+ ]);
164
182
  };
165
183
  exports.renderStill = renderStill;
@@ -12,7 +12,12 @@ const offthread_video_server_1 = require("./offthread-video-server");
12
12
  const serveStatic = async (path, options) => {
13
13
  var _a, _b;
14
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)(options.ffmpegExecutable, options.downloadDir, options.onDownload, options.onError);
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
+ });
16
21
  try {
17
22
  const server = http_1.default
18
23
  .createServer((request, response) => {
@@ -39,6 +44,9 @@ const serveStatic = async (path, options) => {
39
44
  return new Promise((resolve, reject) => {
40
45
  server.close((err) => {
41
46
  if (err) {
47
+ if (err.code === 'ERR_SERVER_NOT_RUNNING') {
48
+ return resolve();
49
+ }
42
50
  reject(err);
43
51
  }
44
52
  else {
@@ -1,5 +1,5 @@
1
1
  import { Page } from 'puppeteer-core';
2
- export declare const setPropsAndEnv: ({ inputProps, envVariables, page, serveUrl, initialFrame, timeoutInMilliseconds, proxyPort, }: {
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;
@@ -7,4 +7,5 @@ export declare const setPropsAndEnv: ({ inputProps, envVariables, page, serveUrl
7
7
  initialFrame: number;
8
8
  timeoutInMilliseconds: number | undefined;
9
9
  proxyPort: number;
10
+ retriesRemaining: number;
10
11
  }) => Promise<void>;