@remotion/renderer 3.0.14 → 3.0.17

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 (210) hide show
  1. package/dist/abort.d.ts +7 -0
  2. package/dist/abort.js +20 -0
  3. package/dist/assets/get-audio-channels.d.ts +2 -1
  4. package/dist/assets/get-audio-channels.js +2 -2
  5. package/dist/cancel.d.ts +7 -0
  6. package/dist/cancel.js +25 -0
  7. package/dist/combine-videos.d.ts +2 -1
  8. package/dist/combine-videos.js +3 -1
  9. package/dist/cycle-browser-tabs.d.ts +2 -1
  10. package/dist/cycle-browser-tabs.js +9 -2
  11. package/dist/extract-frame-from-video.d.ts +4 -6
  12. package/dist/extract-frame-from-video.js +54 -8
  13. package/dist/get-compositions.d.ts +1 -0
  14. package/dist/get-compositions.js +4 -2
  15. package/dist/index.d.ts +3 -1
  16. package/dist/index.js +3 -1
  17. package/dist/is-beyond-last-frame.d.ts +2 -0
  18. package/dist/is-beyond-last-frame.js +12 -0
  19. package/dist/last-frame-from-video-cache.d.ts +13 -0
  20. package/dist/last-frame-from-video-cache.js +52 -0
  21. package/dist/make-cancel-signal.d.ts +7 -0
  22. package/dist/make-cancel-signal.js +25 -0
  23. package/dist/merge-audio-track.js +2 -2
  24. package/dist/offthread/index.d.ts +0 -0
  25. package/dist/offthread/index.js +1 -0
  26. package/dist/offthread-video-server.d.ts +2 -1
  27. package/dist/offthread-video-server.js +2 -1
  28. package/dist/open-browser.js +2 -1
  29. package/dist/prepare-server.d.ts +2 -1
  30. package/dist/prepare-server.js +3 -1
  31. package/dist/preprocess-audio-track.d.ts +1 -0
  32. package/dist/preprocess-audio-track.js +2 -2
  33. package/dist/prespawn-ffmpeg.d.ts +2 -0
  34. package/dist/prespawn-ffmpeg.js +3 -0
  35. package/dist/provide-screenshot.js +1 -1
  36. package/dist/render-frames.d.ts +3 -0
  37. package/dist/render-frames.js +78 -37
  38. package/dist/render-media.d.ts +4 -1
  39. package/dist/render-media.js +105 -57
  40. package/dist/render-still.d.ts +5 -2
  41. package/dist/render-still.js +26 -12
  42. package/dist/serve-static.d.ts +1 -0
  43. package/dist/serve-static.js +4 -0
  44. package/dist/set-props-and-env.d.ts +2 -1
  45. package/dist/set-props-and-env.js +20 -1
  46. package/dist/stitch-frames-to-video.d.ts +4 -1
  47. package/dist/stitch-frames-to-video.js +30 -15
  48. package/package.json +3 -3
  49. package/dist/assets/calculate-asset-positions.d.ts.map +0 -1
  50. package/dist/assets/calculate-asset-positions.js.map +0 -1
  51. package/dist/assets/calculate-atempo.d.ts.map +0 -1
  52. package/dist/assets/calculate-atempo.js.map +0 -1
  53. package/dist/assets/convert-assets-to-file-urls.d.ts.map +0 -1
  54. package/dist/assets/convert-assets-to-file-urls.js.map +0 -1
  55. package/dist/assets/download-and-map-assets-to-file.d.ts.map +0 -1
  56. package/dist/assets/download-and-map-assets-to-file.js.map +0 -1
  57. package/dist/assets/download-file.d.ts.map +0 -1
  58. package/dist/assets/download-file.js.map +0 -1
  59. package/dist/assets/ffmpeg-volume-expression.d.ts.map +0 -1
  60. package/dist/assets/ffmpeg-volume-expression.js.map +0 -1
  61. package/dist/assets/flatten-volume-array.d.ts.map +0 -1
  62. package/dist/assets/flatten-volume-array.js.map +0 -1
  63. package/dist/assets/get-audio-channels.d.ts.map +0 -1
  64. package/dist/assets/get-audio-channels.js.map +0 -1
  65. package/dist/assets/read-file.d.ts.map +0 -1
  66. package/dist/assets/read-file.js.map +0 -1
  67. package/dist/assets/round-volume-to-avoid-stack-overflow.d.ts.map +0 -1
  68. package/dist/assets/round-volume-to-avoid-stack-overflow.js.map +0 -1
  69. package/dist/assets/sanitize-filename.d.ts.map +0 -1
  70. package/dist/assets/sanitize-filename.js.map +0 -1
  71. package/dist/assets/sanitize-filepath.d.ts.map +0 -1
  72. package/dist/assets/sanitize-filepath.js.map +0 -1
  73. package/dist/assets/truncate-utf8-bytes.d.ts.map +0 -1
  74. package/dist/assets/truncate-utf8-bytes.js.map +0 -1
  75. package/dist/assets/types.d.ts.map +0 -1
  76. package/dist/assets/types.js.map +0 -1
  77. package/dist/browser-log.d.ts.map +0 -1
  78. package/dist/browser-log.js.map +0 -1
  79. package/dist/calculate-ffmpeg-filters.d.ts.map +0 -1
  80. package/dist/calculate-ffmpeg-filters.js.map +0 -1
  81. package/dist/can-use-parallel-encoding.d.ts.map +0 -1
  82. package/dist/can-use-parallel-encoding.js.map +0 -1
  83. package/dist/chunk.d.ts.map +0 -1
  84. package/dist/chunk.js.map +0 -1
  85. package/dist/combine-videos.d.ts.map +0 -1
  86. package/dist/combine-videos.js.map +0 -1
  87. package/dist/convert-to-pcm.d.ts.map +0 -1
  88. package/dist/convert-to-pcm.js.map +0 -1
  89. package/dist/create-ffmpeg-complex-filter.d.ts.map +0 -1
  90. package/dist/create-ffmpeg-complex-filter.js.map +0 -1
  91. package/dist/create-ffmpeg-merge-filter.d.ts.map +0 -1
  92. package/dist/create-ffmpeg-merge-filter.js.map +0 -1
  93. package/dist/create-silent-audio.d.ts.map +0 -1
  94. package/dist/create-silent-audio.js.map +0 -1
  95. package/dist/cycle-browser-tabs.d.ts.map +0 -1
  96. package/dist/cycle-browser-tabs.js.map +0 -1
  97. package/dist/delay-render-embedded-stack.d.ts.map +0 -1
  98. package/dist/delay-render-embedded-stack.js.map +0 -1
  99. package/dist/delete-directory.d.ts.map +0 -1
  100. package/dist/delete-directory.js.map +0 -1
  101. package/dist/ensure-frames-in-order.d.ts.map +0 -1
  102. package/dist/ensure-frames-in-order.js.map +0 -1
  103. package/dist/ensure-output-directory.d.ts.map +0 -1
  104. package/dist/ensure-output-directory.js.map +0 -1
  105. package/dist/error-handling/handle-javascript-exception.d.ts.map +0 -1
  106. package/dist/error-handling/handle-javascript-exception.js.map +0 -1
  107. package/dist/error-handling/symbolicate-error.d.ts.map +0 -1
  108. package/dist/error-handling/symbolicate-error.js.map +0 -1
  109. package/dist/error-handling/symbolicateable-error.d.ts.map +0 -1
  110. package/dist/error-handling/symbolicateable-error.js.map +0 -1
  111. package/dist/ffmpeg-filter-file.d.ts.map +0 -1
  112. package/dist/ffmpeg-filter-file.js.map +0 -1
  113. package/dist/ffmpeg-flags.d.ts.map +0 -1
  114. package/dist/ffmpeg-flags.js.map +0 -1
  115. package/dist/get-audio-codec-name.d.ts.map +0 -1
  116. package/dist/get-audio-codec-name.js.map +0 -1
  117. package/dist/get-browser-instance.d.ts.map +0 -1
  118. package/dist/get-browser-instance.js.map +0 -1
  119. package/dist/get-codec-name.d.ts.map +0 -1
  120. package/dist/get-codec-name.js.map +0 -1
  121. package/dist/get-compositions.d.ts.map +0 -1
  122. package/dist/get-compositions.js.map +0 -1
  123. package/dist/get-concurrency.d.ts.map +0 -1
  124. package/dist/get-concurrency.js.map +0 -1
  125. package/dist/get-duration-from-frame-range.d.ts.map +0 -1
  126. package/dist/get-duration-from-frame-range.js.map +0 -1
  127. package/dist/get-extension-from-codec.d.ts.map +0 -1
  128. package/dist/get-extension-from-codec.js.map +0 -1
  129. package/dist/get-frame-to-render.d.ts.map +0 -1
  130. package/dist/get-frame-to-render.js.map +0 -1
  131. package/dist/get-local-browser-executable.d.ts.map +0 -1
  132. package/dist/get-local-browser-executable.js.map +0 -1
  133. package/dist/get-port.d.ts.map +0 -1
  134. package/dist/get-port.js.map +0 -1
  135. package/dist/get-prores-profile-name.d.ts.map +0 -1
  136. package/dist/get-prores-profile-name.js.map +0 -1
  137. package/dist/image-format.d.ts.map +0 -1
  138. package/dist/image-format.js.map +0 -1
  139. package/dist/index.d.ts.map +0 -1
  140. package/dist/index.js.map +0 -1
  141. package/dist/is-serve-url.d.ts.map +0 -1
  142. package/dist/is-serve-url.js.map +0 -1
  143. package/dist/legacy-webpack-config.d.ts.map +0 -1
  144. package/dist/legacy-webpack-config.js.map +0 -1
  145. package/dist/make-assets-download-dir.d.ts.map +0 -1
  146. package/dist/make-assets-download-dir.js.map +0 -1
  147. package/dist/merge-audio-track.d.ts.map +0 -1
  148. package/dist/merge-audio-track.js.map +0 -1
  149. package/dist/normalize-serve-url.d.ts.map +0 -1
  150. package/dist/normalize-serve-url.js.map +0 -1
  151. package/dist/open-browser.d.ts.map +0 -1
  152. package/dist/open-browser.js.map +0 -1
  153. package/dist/p-limit.d.ts.map +0 -1
  154. package/dist/p-limit.js.map +0 -1
  155. package/dist/parse-browser-error-stack.d.ts.map +0 -1
  156. package/dist/parse-browser-error-stack.js.map +0 -1
  157. package/dist/parse-ffmpeg-progress.d.ts.map +0 -1
  158. package/dist/parse-ffmpeg-progress.js.map +0 -1
  159. package/dist/pool.d.ts.map +0 -1
  160. package/dist/pool.js.map +0 -1
  161. package/dist/prepare-server.d.ts.map +0 -1
  162. package/dist/prepare-server.js.map +0 -1
  163. package/dist/preprocess-audio-track.d.ts.map +0 -1
  164. package/dist/preprocess-audio-track.js.map +0 -1
  165. package/dist/prespawn-ffmpeg.d.ts.map +0 -1
  166. package/dist/prespawn-ffmpeg.js.map +0 -1
  167. package/dist/provide-screenshot.d.ts.map +0 -1
  168. package/dist/provide-screenshot.js.map +0 -1
  169. package/dist/puppeteer-evaluate.d.ts.map +0 -1
  170. package/dist/puppeteer-evaluate.js.map +0 -1
  171. package/dist/puppeteer-screenshot.d.ts.map +0 -1
  172. package/dist/puppeteer-screenshot.js.map +0 -1
  173. package/dist/render-frames.d.ts.map +0 -1
  174. package/dist/render-frames.js.map +0 -1
  175. package/dist/render-media.d.ts.map +0 -1
  176. package/dist/render-media.js.map +0 -1
  177. package/dist/render-still.d.ts.map +0 -1
  178. package/dist/render-still.js.map +0 -1
  179. package/dist/resolve-asset-src.d.ts.map +0 -1
  180. package/dist/resolve-asset-src.js.map +0 -1
  181. package/dist/sample-rate.d.ts.map +0 -1
  182. package/dist/sample-rate.js.map +0 -1
  183. package/dist/screenshot-dom-element.d.ts.map +0 -1
  184. package/dist/screenshot-dom-element.js.map +0 -1
  185. package/dist/screenshot-task.d.ts.map +0 -1
  186. package/dist/screenshot-task.js.map +0 -1
  187. package/dist/seek-to-frame.d.ts.map +0 -1
  188. package/dist/seek-to-frame.js.map +0 -1
  189. package/dist/serve-static.d.ts.map +0 -1
  190. package/dist/serve-static.js.map +0 -1
  191. package/dist/set-props-and-env.d.ts.map +0 -1
  192. package/dist/set-props-and-env.js.map +0 -1
  193. package/dist/stitch-frames-to-video.d.ts.map +0 -1
  194. package/dist/stitch-frames-to-video.js.map +0 -1
  195. package/dist/stringify-ffmpeg-filter.d.ts.map +0 -1
  196. package/dist/stringify-ffmpeg-filter.js.map +0 -1
  197. package/dist/symbolicate-stacktrace.d.ts.map +0 -1
  198. package/dist/symbolicate-stacktrace.js.map +0 -1
  199. package/dist/tmp-dir.d.ts.map +0 -1
  200. package/dist/tmp-dir.js.map +0 -1
  201. package/dist/types.d.ts.map +0 -1
  202. package/dist/types.js.map +0 -1
  203. package/dist/validate-even-dimensions-with-codec.d.ts.map +0 -1
  204. package/dist/validate-even-dimensions-with-codec.js.map +0 -1
  205. package/dist/validate-ffmpeg.d.ts.map +0 -1
  206. package/dist/validate-ffmpeg.js.map +0 -1
  207. package/dist/validate-puppeteer-timeout.d.ts.map +0 -1
  208. package/dist/validate-puppeteer-timeout.js.map +0 -1
  209. package/dist/validate-scale.d.ts.map +0 -1
  210. package/dist/validate-scale.js.map +0 -1
@@ -73,6 +73,9 @@ const prespawnFfmpeg = async (options) => {
73
73
  }
74
74
  const ffmpegString = ffmpegArgs.flat(2).filter(Boolean);
75
75
  const task = (0, execa_1.default)((_f = options.ffmpegExecutable) !== null && _f !== void 0 ? _f : 'ffmpeg', ffmpegString);
76
+ options.signal(() => {
77
+ task.kill();
78
+ });
76
79
  let ffmpegOutput = '';
77
80
  (_g = task.stderr) === null || _g === void 0 ? void 0 : _g.on('data', (data) => {
78
81
  const str = data.toString();
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.provideScreenshot = void 0;
4
4
  const screenshot_dom_element_1 = require("./screenshot-dom-element");
5
- const provideScreenshot = async ({ page, imageFormat, options, quality, }) => {
5
+ const provideScreenshot = ({ page, imageFormat, options, quality, }) => {
6
6
  return (0, screenshot_dom_element_1.screenshotDOMElement)({
7
7
  page,
8
8
  opts: {
@@ -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,7 +35,9 @@ declare type RenderFramesOptions = {
34
35
  chromiumOptions?: ChromiumOptions;
35
36
  scale?: number;
36
37
  ffmpegExecutable?: FfmpegExecutable;
38
+ ffprobeExecutable?: FfmpegExecutable;
37
39
  port?: number | null;
40
+ cancelSignal?: CancelSignal;
38
41
  } & ConfigOrComposition & ServeUrlOrWebpackBundle;
39
42
  export declare const renderFrames: (options: RenderFramesOptions) => Promise<RenderFramesOutput>;
40
43
  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
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);
@@ -122,6 +135,7 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
122
135
  await (0, seek_to_frame_1.seekToFrame)({ frame, page: freePage });
123
136
  if (imageFormat !== 'none') {
124
137
  if (onFrameBuffer) {
138
+ const id = remotion_1.Internals.perf.startPerfMeasure('save');
125
139
  const buffer = await (0, provide_screenshot_1.provideScreenshot)({
126
140
  page: freePage,
127
141
  imageFormat,
@@ -131,6 +145,7 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
131
145
  output: undefined,
132
146
  },
133
147
  });
148
+ remotion_1.Internals.perf.stopPerfMeasure(id);
134
149
  onFrameBuffer(buffer, frame);
135
150
  }
136
151
  else {
@@ -175,18 +190,28 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
175
190
  freePage.off('error', errorCallbackOnFrame);
176
191
  return compressedAssets;
177
192
  }));
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;
193
+ const happyPath = progress.then(() => {
194
+ const returnValue = {
195
+ assetsInfo: {
196
+ assets,
197
+ downloadDir,
198
+ firstFrameIndex,
199
+ imageSequenceName: `element-%0${filePadLength}d.${imageFormat}`,
200
+ },
201
+ frameCount,
202
+ };
203
+ return returnValue;
204
+ });
205
+ return Promise.race([
206
+ happyPath,
207
+ new Promise((_resolve, reject) => {
208
+ cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
209
+ reject(new Error('renderFrames() got cancelled'));
210
+ });
211
+ }),
212
+ ]);
188
213
  };
189
- const renderFrames = async (options) => {
214
+ const renderFrames = (options) => {
190
215
  var _a, _b, _c, _d;
191
216
  const composition = getComposition(options);
192
217
  if (!composition) {
@@ -202,34 +227,44 @@ const renderFrames = async (options) => {
202
227
  const selectedServeUrl = (0, legacy_webpack_config_1.getServeUrlWithFallback)(options);
203
228
  remotion_1.Internals.validateQuality(options.quality);
204
229
  (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, {
230
+ const browserInstance = (_a = options.puppeteerInstance) !== null && _a !== void 0 ? _a : (0, open_browser_1.openBrowser)(remotion_1.Internals.DEFAULT_BROWSER, {
206
231
  shouldDumpIo: options.dumpBrowserLogs,
207
232
  browserExecutable: options.browserExecutable,
208
233
  chromiumOptions: options.chromiumOptions,
209
234
  forceDeviceScaleFactor: (_b = options.scale) !== null && _b !== void 0 ? _b : 1,
210
- }));
235
+ });
211
236
  const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
212
237
  const onDownload = (_c = options.onDownload) !== null && _c !== void 0 ? _c : (() => () => undefined);
213
238
  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
239
  const openedPages = [];
216
240
  return new Promise((resolve, reject) => {
217
- var _a, _b;
218
- let cleanup = null;
241
+ var _a, _b, _c;
242
+ const cleanup = [];
219
243
  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
- port: (_b = options.port) !== null && _b !== void 0 ? _b : null,
227
- })
228
- .then(({ serveUrl, closeServer, offthreadPort }) => {
229
- cleanup = closeServer;
230
- return innerRenderFrames({
244
+ Promise.all([
245
+ (0, prepare_server_1.prepareServer)({
246
+ webpackConfigOrServeUrl: selectedServeUrl,
247
+ downloadDir,
248
+ onDownload,
249
+ onError,
250
+ ffmpegExecutable: (_a = options.ffmpegExecutable) !== null && _a !== void 0 ? _a : null,
251
+ ffprobeExecutable: (_b = options.ffprobeExecutable) !== null && _b !== void 0 ? _b : null,
252
+ port: (_c = options.port) !== null && _c !== void 0 ? _c : null,
253
+ }),
254
+ browserInstance,
255
+ ])
256
+ .then(([{ serveUrl, closeServer, offthreadPort }, puppeteerInstance]) => {
257
+ var _a;
258
+ const { stopCycling } = (0, cycle_browser_tabs_1.cycleBrowserTabs)(puppeteerInstance, actualParallelism);
259
+ cleanup.push(stopCycling);
260
+ (_a = options.cancelSignal) === null || _a === void 0 ? void 0 : _a.call(options, () => {
261
+ stopCycling();
262
+ closeServer();
263
+ });
264
+ cleanup.push(closeServer);
265
+ const renderFramesProm = innerRenderFrames({
231
266
  ...options,
232
- puppeteerInstance: browserInstance,
267
+ puppeteerInstance,
233
268
  onError,
234
269
  pagesArray: openedPages,
235
270
  serveUrl,
@@ -239,6 +274,7 @@ const renderFrames = async (options) => {
239
274
  downloadDir,
240
275
  proxyPort: offthreadPort,
241
276
  });
277
+ return renderFramesProm;
242
278
  })
243
279
  .then((res) => resolve(res))
244
280
  .catch((err) => reject(err))
@@ -252,12 +288,17 @@ const renderFrames = async (options) => {
252
288
  });
253
289
  }
254
290
  else {
255
- browserInstance.close().catch((err) => {
291
+ Promise.resolve(browserInstance)
292
+ .then((puppeteerInstance) => {
293
+ return puppeteerInstance.close();
294
+ })
295
+ .catch((err) => {
256
296
  console.log('Unable to close browser', err);
257
297
  });
258
298
  }
259
- stopCycling();
260
- cleanup === null || cleanup === void 0 ? void 0 : cleanup();
299
+ cleanup.forEach((c) => {
300
+ c();
301
+ });
261
302
  });
262
303
  });
263
304
  };
@@ -3,6 +3,7 @@ import { BrowserExecutable, Codec, FfmpegExecutable, FrameRange, PixelFormat, Pr
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';
@@ -22,6 +23,7 @@ export declare type RenderMediaOptions = {
22
23
  crf?: number | null;
23
24
  imageFormat?: 'png' | 'jpeg' | 'none';
24
25
  ffmpegExecutable?: FfmpegExecutable;
26
+ ffprobeExecutable?: FfmpegExecutable;
25
27
  pixelFormat?: PixelFormat;
26
28
  envVariables?: Record<string, string>;
27
29
  quality?: number;
@@ -38,6 +40,7 @@ export declare type RenderMediaOptions = {
38
40
  chromiumOptions?: ChromiumOptions;
39
41
  scale?: number;
40
42
  port?: number | null;
43
+ cancelSignal?: CancelSignal;
41
44
  browserExecutable?: BrowserExecutable;
42
45
  } & ServeUrlOrWebpackBundle;
43
- 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, ...options }: RenderMediaOptions) => Promise<void>;
46
+ export declare const renderMedia: ({ parallelism, proResProfile, crf, composition, imageFormat, ffmpegExecutable, ffprobeExecutable, 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, browserExecutable, port, ...options }) => {
27
- var _a;
27
+ const renderMedia = ({ parallelism, proResProfile, crf, composition, imageFormat, ffmpegExecutable, ffprobeExecutable, 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,7 +134,12 @@ 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
+ }
140
+ const id = remotion_1.Internals.perf.startPerfMeasure('piping');
113
141
  (_a = stitcherFfmpeg === null || stitcherFfmpeg === void 0 ? void 0 : stitcherFfmpeg.stdin) === null || _a === void 0 ? void 0 : _a.write(buffer);
142
+ remotion_1.Internals.perf.stopPerfMeasure(id);
114
143
  setFrameToStitch(frame + 1);
115
144
  }
116
145
  : undefined,
@@ -122,57 +151,66 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
122
151
  chromiumOptions,
123
152
  scale,
124
153
  ffmpegExecutable,
154
+ ffprobeExecutable,
125
155
  browserExecutable,
126
156
  port,
157
+ cancelSignal: cancelRenderFrames.cancelSignal,
127
158
  });
128
- if (stitcherFfmpeg) {
129
- await waitForFinish();
130
- (_a = stitcherFfmpeg === null || stitcherFfmpeg === void 0 ? void 0 : stitcherFfmpeg.stdin) === null || _a === void 0 ? void 0 : _a.end();
131
- try {
132
- await stitcherFfmpeg;
133
- }
134
- catch (err) {
135
- throw new Error(preStitcher === null || preStitcher === void 0 ? void 0 : preStitcher.getLogs());
136
- }
137
- }
159
+ return renderFramesProc;
160
+ })
161
+ .then((renderFramesReturn) => {
162
+ return Promise.all([renderFramesReturn, waitForPrestitcherIfNecessary()]);
163
+ })
164
+ .then(([{ assetsInfo }]) => {
138
165
  renderedDoneIn = Date.now() - renderStart;
139
166
  callUpdate();
140
167
  (0, ensure_output_directory_1.ensureOutputDirectory)(outputLocation);
141
168
  const stitchStart = Date.now();
142
- await (0, stitch_frames_to_video_1.stitchFramesToVideo)({
143
- width: composition.width * (scale !== null && scale !== void 0 ? scale : 1),
144
- height: composition.height * (scale !== null && scale !== void 0 ? scale : 1),
145
- fps: composition.fps,
146
- outputLocation,
147
- internalOptions: {
148
- preEncodedFileLocation,
149
- imageFormat: actualImageFormat,
150
- },
151
- force: overwrite !== null && overwrite !== void 0 ? overwrite : remotion_1.Internals.DEFAULT_OVERWRITE,
152
- pixelFormat,
153
- codec,
154
- proResProfile,
155
- crf,
156
- assetsInfo,
157
- ffmpegExecutable,
158
- onProgress: (frame) => {
159
- stitchStage = 'muxing';
160
- encodedFrames = frame;
161
- callUpdate();
162
- },
163
- onDownload,
164
- verbose: remotion_1.Internals.Logging.isEqualOrBelowLogLevel(remotion_1.Internals.Logging.getLogLevel(), 'verbose'),
165
- dir: outputDir !== null && outputDir !== void 0 ? outputDir : undefined,
166
- });
169
+ return Promise.all([
170
+ (0, stitch_frames_to_video_1.stitchFramesToVideo)({
171
+ width: composition.width * (scale !== null && scale !== void 0 ? scale : 1),
172
+ height: composition.height * (scale !== null && scale !== void 0 ? scale : 1),
173
+ fps: composition.fps,
174
+ outputLocation,
175
+ internalOptions: {
176
+ preEncodedFileLocation,
177
+ imageFormat: actualImageFormat,
178
+ },
179
+ force: overwrite !== null && overwrite !== void 0 ? overwrite : remotion_1.Internals.DEFAULT_OVERWRITE,
180
+ pixelFormat,
181
+ codec,
182
+ proResProfile,
183
+ crf,
184
+ assetsInfo,
185
+ ffmpegExecutable,
186
+ ffprobeExecutable,
187
+ onProgress: (frame) => {
188
+ stitchStage = 'muxing';
189
+ encodedFrames = frame;
190
+ callUpdate();
191
+ },
192
+ onDownload,
193
+ verbose: remotion_1.Internals.Logging.isEqualOrBelowLogLevel(remotion_1.Internals.Logging.getLogLevel(), 'verbose'),
194
+ dir: outputDir !== null && outputDir !== void 0 ? outputDir : undefined,
195
+ cancelSignal: cancelStitcher.cancelSignal,
196
+ }),
197
+ stitchStart,
198
+ ]);
199
+ })
200
+ .then(([, stitchStart]) => {
167
201
  encodedFrames = (0, get_duration_from_frame_range_1.getDurationFromFrameRange)(frameRange !== null && frameRange !== void 0 ? frameRange : null, composition.durationInFrames);
168
202
  encodedDoneIn = Date.now() - stitchStart;
169
203
  callUpdate();
170
- }
171
- catch (err) {
204
+ })
205
+ .catch((err) => {
172
206
  /**
173
207
  * 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.
174
208
  * Therefore we first kill the FFMPEG process before deleting the file
175
209
  */
210
+ cancelled = true;
211
+ cancelRenderFrames.cancel();
212
+ cancelStitcher.cancel();
213
+ cancelPrestitcher.cancel();
176
214
  if (stitcherFfmpeg !== undefined && stitcherFfmpeg.exitCode === null) {
177
215
  const promise = new Promise((resolve) => {
178
216
  setTimeout(() => {
@@ -181,15 +219,25 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
181
219
  stitcherFfmpeg.on('close', resolve);
182
220
  });
183
221
  stitcherFfmpeg.kill();
184
- await promise;
222
+ return promise.then(() => {
223
+ throw err;
224
+ });
185
225
  }
186
226
  throw err;
187
- }
188
- finally {
227
+ })
228
+ .finally(() => {
189
229
  if (preEncodedFileLocation !== null &&
190
230
  fs_1.default.existsSync(preEncodedFileLocation)) {
191
231
  fs_1.default.unlinkSync(preEncodedFileLocation);
192
232
  }
193
- }
233
+ });
234
+ return Promise.race([
235
+ happyPath,
236
+ new Promise((_resolve, reject) => {
237
+ cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
238
+ reject(new Error('renderMedia() got cancelled'));
239
+ });
240
+ }),
241
+ ]);
194
242
  };
195
243
  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,7 +20,9 @@ declare type InnerStillOptions = {
19
20
  chromiumOptions?: ChromiumOptions;
20
21
  scale?: number;
21
22
  onDownload?: RenderMediaOnDownload;
23
+ cancelSignal?: CancelSignal;
22
24
  ffmpegExecutable?: FfmpegExecutable;
25
+ ffprobeExecutable?: FfmpegExecutable;
23
26
  };
24
27
  declare type RenderStillOptions = InnerStillOptions & ServeUrlOrWebpackBundle & {
25
28
  port?: number | null;
@@ -42,7 +42,7 @@ const seek_to_frame_1 = require("./seek-to-frame");
42
42
  const set_props_and_env_1 = require("./set-props-and-env");
43
43
  const validate_puppeteer_timeout_1 = require("./validate-puppeteer-timeout");
44
44
  const validate_scale_1 = require("./validate-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, }) => {
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, }) => {
46
46
  remotion_1.Internals.validateDimension(composition.height, 'height', 'in the `config` object passed to `renderStill()`');
47
47
  remotion_1.Internals.validateDimension(composition.width, 'width', 'in the `config` object passed to `renderStill()`');
48
48
  remotion_1.Internals.validateFps(composition.fps, 'in the `config` object of `renderStill()`');
@@ -76,11 +76,20 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
76
76
  forceDeviceScaleFactor: scale !== null && scale !== void 0 ? scale : 1,
77
77
  }));
78
78
  const page = await browserInstance.newPage();
79
- page.setViewport({
79
+ await page.setViewport({
80
80
  width: composition.width,
81
81
  height: composition.height,
82
82
  deviceScaleFactor: scale !== null && scale !== void 0 ? scale : 1,
83
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
+ });
84
93
  const cleanup = async () => {
85
94
  cleanUpJSException();
86
95
  if (puppeteerInstance) {
@@ -92,14 +101,8 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
92
101
  });
93
102
  }
94
103
  };
95
- const errorCallback = (err) => {
96
- onError(err);
104
+ cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
97
105
  cleanup();
98
- };
99
- const cleanUpJSException = (0, handle_javascript_exception_1.handleJavascriptException)({
100
- page,
101
- onError: errorCallback,
102
- frame: null,
103
106
  });
104
107
  await (0, set_props_and_env_1.setPropsAndEnv)({
105
108
  inputProps,
@@ -109,6 +112,7 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
109
112
  initialFrame: frame,
110
113
  timeoutInMilliseconds,
111
114
  proxyPort,
115
+ retriesRemaining: 2,
112
116
  });
113
117
  await (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
114
118
  pageFunction: (id) => {
@@ -141,8 +145,8 @@ const renderStill = (options) => {
141
145
  const selectedServeUrl = (0, legacy_webpack_config_1.getServeUrlWithFallback)(options);
142
146
  const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
143
147
  const onDownload = (_a = options.onDownload) !== null && _a !== void 0 ? _a : (() => () => undefined);
144
- return new Promise((resolve, reject) => {
145
- var _a, _b;
148
+ const happyPath = new Promise((resolve, reject) => {
149
+ var _a, _b, _c;
146
150
  const onError = (err) => reject(err);
147
151
  let close = null;
148
152
  (0, prepare_server_1.prepareServer)({
@@ -151,7 +155,8 @@ const renderStill = (options) => {
151
155
  onDownload,
152
156
  onError,
153
157
  ffmpegExecutable: (_a = options.ffmpegExecutable) !== null && _a !== void 0 ? _a : null,
154
- port: (_b = options.port) !== null && _b !== void 0 ? _b : null,
158
+ ffprobeExecutable: (_b = options.ffprobeExecutable) !== null && _b !== void 0 ? _b : null,
159
+ port: (_c = options.port) !== null && _c !== void 0 ? _c : null,
155
160
  })
156
161
  .then(({ serveUrl, closeServer, offthreadPort }) => {
157
162
  close = closeServer;
@@ -166,5 +171,14 @@ const renderStill = (options) => {
166
171
  .catch((err) => reject(err))
167
172
  .finally(() => close === null || close === void 0 ? void 0 : close());
168
173
  });
174
+ return Promise.race([
175
+ happyPath,
176
+ new Promise((_resolve, reject) => {
177
+ var _a;
178
+ (_a = options.cancelSignal) === null || _a === void 0 ? void 0 : _a.call(options, () => {
179
+ reject(new Error('renderStill() got cancelled'));
180
+ });
181
+ }),
182
+ ]);
169
183
  };
170
184
  exports.renderStill = renderStill;
@@ -3,6 +3,7 @@ import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file'
3
3
  export declare const serveStatic: (path: string | null, options: {
4
4
  port: number | null;
5
5
  ffmpegExecutable: FfmpegExecutable;
6
+ ffprobeExecutable: FfmpegExecutable;
6
7
  downloadDir: string;
7
8
  onDownload: RenderMediaOnDownload;
8
9
  onError: (err: Error) => void;