@remotion/renderer 3.0.11 → 3.0.15

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.
@@ -0,0 +1,7 @@
1
+ declare type Callback = () => void;
2
+ export declare type CancelSignal = (callback: Callback) => void;
3
+ export declare const getCancelSignal: () => {
4
+ cancelSignal: CancelSignal;
5
+ cancel: () => void;
6
+ };
7
+ export {};
package/dist/abort.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCancelSignal = void 0;
4
+ const getCancelSignal = () => {
5
+ const callbacks = [];
6
+ let cancelled = false;
7
+ return {
8
+ cancelSignal: (callback) => callbacks.push(callback),
9
+ cancel: () => {
10
+ if (cancelled) {
11
+ return;
12
+ }
13
+ callbacks.forEach((cb) => {
14
+ cb();
15
+ });
16
+ cancelled = true;
17
+ },
18
+ };
19
+ };
20
+ exports.getCancelSignal = getCancelSignal;
@@ -2,7 +2,7 @@ import { TAsset } from 'remotion';
2
2
  export declare type RenderMediaOnDownload = (src: string) => ((progress: {
3
3
  percent: number;
4
4
  }) => void) | undefined | void;
5
- export declare const waitForAssetToBeDownloaded: (src: string) => Promise<string>;
5
+ export declare const waitForAssetToBeDownloaded: (src: string, to: string) => Promise<void>;
6
6
  export declare const markAllAssetsAsDownloaded: () => void;
7
7
  export declare const getSanitizedFilenameForAssetUrl: ({ src, downloadDir, }: {
8
8
  src: string;
@@ -13,25 +13,38 @@ const sanitize_filepath_1 = require("./sanitize-filepath");
13
13
  const isDownloadingMap = {};
14
14
  const hasBeenDownloadedMap = {};
15
15
  const listeners = {};
16
- const waitForAssetToBeDownloaded = (src) => {
17
- if (hasBeenDownloadedMap[src]) {
18
- return Promise.resolve(hasBeenDownloadedMap[src]);
16
+ const waitForAssetToBeDownloaded = (src, to) => {
17
+ var _a;
18
+ if ((_a = hasBeenDownloadedMap[src]) === null || _a === void 0 ? void 0 : _a[to]) {
19
+ return Promise.resolve();
19
20
  }
20
21
  if (!listeners[src]) {
21
- listeners[src] = [];
22
+ listeners[src] = {};
23
+ }
24
+ if (!listeners[src][to]) {
25
+ listeners[src][to] = [];
22
26
  }
23
27
  return new Promise((resolve) => {
24
- listeners[src].push((to) => resolve(to));
28
+ listeners[src][to].push(() => resolve());
25
29
  });
26
30
  };
27
31
  exports.waitForAssetToBeDownloaded = waitForAssetToBeDownloaded;
28
32
  const notifyAssetIsDownloaded = (src, to) => {
29
33
  if (!listeners[src]) {
30
- listeners[src] = [];
34
+ listeners[src] = {};
35
+ }
36
+ if (!listeners[src][to]) {
37
+ listeners[src][to] = [];
38
+ }
39
+ listeners[src][to].forEach((fn) => fn());
40
+ if (!isDownloadingMap[src]) {
41
+ isDownloadingMap[src] = {};
31
42
  }
32
- listeners[src].forEach((fn) => fn(to));
33
- isDownloadingMap[src] = false;
34
- hasBeenDownloadedMap[src] = to;
43
+ isDownloadingMap[src][to] = false;
44
+ if (!hasBeenDownloadedMap[src]) {
45
+ hasBeenDownloadedMap[src] = {};
46
+ }
47
+ hasBeenDownloadedMap[src][to] = true;
35
48
  };
36
49
  const validateMimeType = (mimeType, src) => {
37
50
  if (!mimeType.includes('/')) {
@@ -73,13 +86,17 @@ function validateBufferEncoding(potentialEncoding, dataUrl) {
73
86
  }
74
87
  }
75
88
  const downloadAsset = async (src, to, onDownload) => {
76
- if (hasBeenDownloadedMap[src]) {
89
+ var _a, _b;
90
+ if ((_a = hasBeenDownloadedMap[src]) === null || _a === void 0 ? void 0 : _a[to]) {
77
91
  return;
78
92
  }
79
- if (isDownloadingMap[src]) {
80
- return (0, exports.waitForAssetToBeDownloaded)(src);
93
+ if ((_b = isDownloadingMap[src]) === null || _b === void 0 ? void 0 : _b[to]) {
94
+ return (0, exports.waitForAssetToBeDownloaded)(src, to);
95
+ }
96
+ if (!isDownloadingMap[src]) {
97
+ isDownloadingMap[src] = {};
81
98
  }
82
- isDownloadingMap[src] = true;
99
+ isDownloadingMap[src][to] = true;
83
100
  const onProgress = onDownload(src);
84
101
  (0, ensure_output_directory_1.ensureOutputDirectory)(to);
85
102
  if (src.startsWith('data:')) {
@@ -0,0 +1,7 @@
1
+ declare type Callback = () => void;
2
+ export declare type CancelSignal = (callback: Callback) => void;
3
+ export declare const makeCancelSignal: () => {
4
+ cancelSignal: CancelSignal;
5
+ cancel: () => void;
6
+ };
7
+ export {};
package/dist/cancel.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeCancelSignal = void 0;
4
+ const makeCancelSignal = () => {
5
+ const callbacks = [];
6
+ let cancelled = false;
7
+ return {
8
+ cancelSignal: (callback) => {
9
+ callbacks.push(callback);
10
+ if (cancelled) {
11
+ callback();
12
+ }
13
+ },
14
+ cancel: () => {
15
+ if (cancelled) {
16
+ return;
17
+ }
18
+ callbacks.forEach((cb) => {
19
+ cb();
20
+ });
21
+ cancelled = true;
22
+ },
23
+ };
24
+ };
25
+ exports.makeCancelSignal = makeCancelSignal;
@@ -1,9 +1,10 @@
1
1
  import { Codec } from 'remotion';
2
- export declare const combineVideos: ({ files, filelistDir, output, onProgress, numberOfFrames, codec, }: {
2
+ export declare const combineVideos: ({ files, filelistDir, output, onProgress, numberOfFrames, codec, fps, }: {
3
3
  files: string[];
4
4
  filelistDir: string;
5
5
  output: string;
6
6
  onProgress: (p: number) => void;
7
7
  numberOfFrames: number;
8
8
  codec: Codec;
9
+ fps: number;
9
10
  }) => Promise<void>;
@@ -11,13 +11,15 @@ const path_1 = require("path");
11
11
  const remotion_1 = require("remotion");
12
12
  const get_audio_codec_name_1 = require("./get-audio-codec-name");
13
13
  const parse_ffmpeg_progress_1 = require("./parse-ffmpeg-progress");
14
- const combineVideos = async ({ files, filelistDir, output, onProgress, numberOfFrames, codec, }) => {
14
+ const combineVideos = async ({ files, filelistDir, output, onProgress, numberOfFrames, codec, fps, }) => {
15
15
  var _a;
16
16
  const fileList = files.map((p) => `file '${p}'`).join('\n');
17
17
  const fileListTxt = (0, path_1.join)(filelistDir, 'files.txt');
18
18
  (0, fs_1.writeFileSync)(fileListTxt, fileList);
19
19
  try {
20
20
  const task = (0, execa_1.default)('ffmpeg', [
21
+ remotion_1.Internals.isAudioCodec(codec) ? null : '-r',
22
+ remotion_1.Internals.isAudioCodec(codec) ? null : String(fps),
21
23
  '-f',
22
24
  'concat',
23
25
  '-safe',
@@ -1,6 +1,7 @@
1
1
  import { openBrowser } from './open-browser';
2
2
  declare type Await<T> = T extends PromiseLike<infer U> ? U : T;
3
- export declare const cycleBrowserTabs: (puppeteerInstance: Await<ReturnType<typeof openBrowser>>, concurrency: number) => {
3
+ declare type Browser = Await<ReturnType<typeof openBrowser>>;
4
+ export declare const cycleBrowserTabs: (puppeteerInstance: Browser, concurrency: number) => {
4
5
  stopCycling: () => void;
5
6
  };
6
7
  export {};
@@ -9,15 +9,21 @@ const cycleBrowserTabs = (puppeteerInstance, concurrency) => {
9
9
  }
10
10
  let interval = null;
11
11
  let i = 0;
12
+ let stopped = false;
12
13
  const set = () => {
13
14
  interval = setTimeout(() => {
14
15
  puppeteerInstance
15
16
  .pages()
16
17
  .then((pages) => {
17
- var _a, _b;
18
+ var _a;
19
+ if (pages.length === 0) {
20
+ return;
21
+ }
18
22
  const currentPage = pages[i % pages.length];
19
23
  i++;
20
- if (!((_b = (_a = currentPage === null || currentPage === void 0 ? void 0 : currentPage.isClosed) === null || _a === void 0 ? void 0 : _a.call(currentPage)) !== null && _b !== void 0 ? _b : true)) {
24
+ if (!((_a = currentPage === null || currentPage === void 0 ? void 0 : currentPage.isClosed) === null || _a === void 0 ? void 0 : _a.call(currentPage)) &&
25
+ !stopped &&
26
+ (currentPage === null || currentPage === void 0 ? void 0 : currentPage.url()) !== 'about:blank') {
21
27
  return currentPage.bringToFront();
22
28
  }
23
29
  })
@@ -33,6 +39,7 @@ const cycleBrowserTabs = (puppeteerInstance, concurrency) => {
33
39
  if (!interval) {
34
40
  return;
35
41
  }
42
+ stopped = true;
36
43
  return clearInterval(interval);
37
44
  },
38
45
  };
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  import { FfmpegExecutable } from 'remotion';
3
4
  import { Readable } from 'stream';
4
5
  export declare function streamToString(stream: Readable): Promise<string>;
@@ -13,5 +13,5 @@ declare type GetCompositionsConfig = {
13
13
  ffmpegExecutable?: FfmpegExecutable;
14
14
  port?: number | null;
15
15
  };
16
- export declare const getCompositions: (serveUrlOrWebpackUrl: string, config?: GetCompositionsConfig | undefined) => Promise<TCompMetadata[]>;
16
+ export declare const getCompositions: (serveUrlOrWebpackUrl: string, config?: GetCompositionsConfig) => Promise<TCompMetadata[]>;
17
17
  export {};
@@ -28,6 +28,7 @@ const innerGetCompositions = async (serveUrl, page, config, proxyPort) => {
28
28
  initialFrame: 0,
29
29
  timeoutInMilliseconds: config === null || config === void 0 ? void 0 : config.timeoutInMilliseconds,
30
30
  proxyPort,
31
+ retriesRemaining: 2,
31
32
  });
32
33
  await (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
33
34
  page,
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export { combineVideos } from './combine-videos';
5
5
  export { ErrorWithStackFrame } from './error-handling/handle-javascript-exception';
6
6
  export { FfmpegVersion } from './ffmpeg-flags';
7
7
  export { getCompositions } from './get-compositions';
8
+ export { CancelSignal, makeCancelSignal } from './make-cancel-signal';
8
9
  export { openBrowser } from './open-browser';
9
10
  export type { ChromiumOptions } from './open-browser';
10
11
  export { renderFrames } from './render-frames';
@@ -47,11 +48,10 @@ export declare const RenderInternals: {
47
48
  }) => void;
48
49
  normalizeServeUrl: (unnormalized: string) => string;
49
50
  spawnFfmpeg: (options: import("./stitch-frames-to-video").StitcherOptions) => Promise<{
50
- task: Promise<unknown>;
51
+ task: Promise<void>;
51
52
  getLogs: () => string;
52
53
  }>;
53
54
  getFileExtensionFromCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv", type: "chunk" | "final") => "mp3" | "aac" | "wav" | "mp4" | "mkv" | "mov" | "webm";
54
- makeAssetsDownloadTmpDir: () => string;
55
55
  tmpDir: (str: string) => string;
56
56
  deleteDirectory: (directory: string) => Promise<void>;
57
57
  isServeUrl: (potentialUrl: string) => boolean;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RenderInternals = exports.stitchFramesToVideo = exports.renderStill = exports.renderMedia = exports.renderFrames = exports.openBrowser = exports.getCompositions = exports.ErrorWithStackFrame = exports.combineVideos = void 0;
3
+ exports.RenderInternals = exports.stitchFramesToVideo = exports.renderStill = exports.renderMedia = exports.renderFrames = exports.openBrowser = exports.makeCancelSignal = exports.getCompositions = exports.ErrorWithStackFrame = exports.combineVideos = void 0;
4
4
  const download_file_1 = require("./assets/download-file");
5
5
  const delete_directory_1 = require("./delete-directory");
6
6
  const ensure_output_directory_1 = require("./ensure-output-directory");
@@ -14,7 +14,6 @@ const get_extension_of_filename_1 = require("./get-extension-of-filename");
14
14
  const get_frame_to_render_1 = require("./get-frame-to-render");
15
15
  const get_local_browser_executable_1 = require("./get-local-browser-executable");
16
16
  const is_serve_url_1 = require("./is-serve-url");
17
- const make_assets_download_dir_1 = require("./make-assets-download-dir");
18
17
  const normalize_serve_url_1 = require("./normalize-serve-url");
19
18
  const open_browser_1 = require("./open-browser");
20
19
  const parse_browser_error_stack_1 = require("./parse-browser-error-stack");
@@ -31,6 +30,8 @@ var handle_javascript_exception_1 = require("./error-handling/handle-javascript-
31
30
  Object.defineProperty(exports, "ErrorWithStackFrame", { enumerable: true, get: function () { return handle_javascript_exception_1.ErrorWithStackFrame; } });
32
31
  var get_compositions_1 = require("./get-compositions");
33
32
  Object.defineProperty(exports, "getCompositions", { enumerable: true, get: function () { return get_compositions_1.getCompositions; } });
33
+ var make_cancel_signal_1 = require("./make-cancel-signal");
34
+ Object.defineProperty(exports, "makeCancelSignal", { enumerable: true, get: function () { return make_cancel_signal_1.makeCancelSignal; } });
34
35
  var open_browser_2 = require("./open-browser");
35
36
  Object.defineProperty(exports, "openBrowser", { enumerable: true, get: function () { return open_browser_2.openBrowser; } });
36
37
  var render_frames_1 = require("./render-frames");
@@ -54,7 +55,6 @@ exports.RenderInternals = {
54
55
  normalizeServeUrl: normalize_serve_url_1.normalizeServeUrl,
55
56
  spawnFfmpeg: stitch_frames_to_video_1.spawnFfmpeg,
56
57
  getFileExtensionFromCodec: get_extension_from_codec_1.getFileExtensionFromCodec,
57
- makeAssetsDownloadTmpDir: make_assets_download_dir_1.makeAssetsDownloadTmpDir,
58
58
  tmpDir: tmp_dir_1.tmpDir,
59
59
  deleteDirectory: delete_directory_1.deleteDirectory,
60
60
  isServeUrl: is_serve_url_1.isServeUrl,
@@ -0,0 +1,7 @@
1
+ declare type Callback = () => void;
2
+ export declare type CancelSignal = (callback: Callback) => void;
3
+ export declare const makeCancelSignal: () => {
4
+ cancelSignal: CancelSignal;
5
+ cancel: () => void;
6
+ };
7
+ export {};
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeCancelSignal = void 0;
4
+ const makeCancelSignal = () => {
5
+ const callbacks = [];
6
+ let cancelled = false;
7
+ return {
8
+ cancelSignal: (callback) => {
9
+ callbacks.push(callback);
10
+ if (cancelled) {
11
+ callback();
12
+ }
13
+ },
14
+ cancel: () => {
15
+ if (cancelled) {
16
+ return;
17
+ }
18
+ callbacks.forEach((cb) => {
19
+ cb();
20
+ });
21
+ cancelled = true;
22
+ },
23
+ };
24
+ };
25
+ exports.makeCancelSignal = makeCancelSignal;
@@ -30,8 +30,8 @@ const mergeAudioTrackUnlimited = async ({ ffmpegExecutable, outName, files, numb
30
30
  });
31
31
  return;
32
32
  }
33
- // FFMPEG has a limit of 64 tracks that can be merged at once
34
- if (files.length > 64) {
33
+ // In FFMPEG, the total number of left and right tracks that can be merged at one time is limited to 64
34
+ if (files.length >= 32) {
35
35
  const chunked = (0, chunk_1.chunk)(files, 10);
36
36
  const tempPath = (0, tmp_dir_1.tmpDir)('remotion-large-audio-mixing');
37
37
  const chunkNames = await Promise.all(chunked.map(async (chunkFiles, i) => {
@@ -35,14 +35,15 @@ const startOffthreadVideoServer = ({ ffmpegExecutable, downloadDir, onDownload,
35
35
  res.setHeader('access-control-allow-origin', '*');
36
36
  res.setHeader('content-type', 'image/jpg');
37
37
  const { src, time } = (0, exports.extractUrlAndSourceFromUrl)(req.url);
38
+ const to = (0, download_and_map_assets_to_file_1.getSanitizedFilenameForAssetUrl)({ downloadDir, src });
38
39
  (0, download_and_map_assets_to_file_1.startDownloadForSrc)({ src, downloadDir, onDownload }).catch((err) => {
39
40
  onError(new Error(`Error while downloading asset: ${err.stack}`));
40
41
  });
41
- (0, download_and_map_assets_to_file_1.waitForAssetToBeDownloaded)(src)
42
- .then((newSrc) => {
42
+ (0, download_and_map_assets_to_file_1.waitForAssetToBeDownloaded)(src, to)
43
+ .then(() => {
43
44
  return (0, extract_frame_from_video_1.extractFrameFromVideo)({
44
45
  time,
45
- src: newSrc,
46
+ src: to,
46
47
  ffmpegExecutable,
47
48
  });
48
49
  })
@@ -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 {};
@@ -115,7 +115,7 @@ const openBrowser = async (browser, options) => {
115
115
  ].filter(Boolean),
116
116
  });
117
117
  const pages = await browserInstance.pages();
118
- pages.forEach((p) => p.close());
118
+ await pages[0].close();
119
119
  browserInstances.push(browserInstance);
120
120
  return browserInstance;
121
121
  };
@@ -1,5 +1,6 @@
1
1
  import execa from 'execa';
2
2
  import { Codec, FfmpegExecutable, ImageFormat, PixelFormat, ProResProfile } from 'remotion';
3
+ import { CancelSignal } from './make-cancel-signal';
3
4
  declare type PreSticherOptions = {
4
5
  fps: number;
5
6
  width: number;
@@ -13,6 +14,7 @@ declare type PreSticherOptions = {
13
14
  verbose: boolean;
14
15
  ffmpegExecutable: FfmpegExecutable | undefined;
15
16
  imageFormat: ImageFormat;
17
+ signal: CancelSignal;
16
18
  };
17
19
  export declare const prespawnFfmpeg: (options: PreSticherOptions) => Promise<{
18
20
  task: execa.ExecaChildProcess<string>;
@@ -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();
@@ -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];
@@ -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 = {
@@ -35,6 +36,7 @@ declare type RenderFramesOptions = {
35
36
  scale?: number;
36
37
  ffmpegExecutable?: FfmpegExecutable;
37
38
  port?: number | null;
39
+ cancelSignal?: CancelSignal;
38
40
  } & ConfigOrComposition & ServeUrlOrWebpackBundle;
39
41
  export declare const renderFrames: (options: RenderFramesOptions) => Promise<RenderFramesOutput>;
40
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)) {
@@ -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,34 +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
239
  var _a, _b;
218
- let cleanup = null;
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
- port: (_b = options.port) !== null && _b !== void 0 ? _b : null,
227
- })
228
- .then(({ serveUrl, closeServer, offthreadPort }) => {
229
- cleanup = closeServer;
230
- 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({
231
263
  ...options,
232
- puppeteerInstance: browserInstance,
264
+ puppeteerInstance,
233
265
  onError,
234
266
  pagesArray: openedPages,
235
267
  serveUrl,
@@ -239,6 +271,7 @@ const renderFrames = async (options) => {
239
271
  downloadDir,
240
272
  proxyPort: offthreadPort,
241
273
  });
274
+ return renderFramesProm;
242
275
  })
243
276
  .then((res) => resolve(res))
244
277
  .catch((err) => reject(err))
@@ -252,12 +285,17 @@ const renderFrames = async (options) => {
252
285
  });
253
286
  }
254
287
  else {
255
- browserInstance.close().catch((err) => {
288
+ Promise.resolve(browserInstance)
289
+ .then((puppeteerInstance) => {
290
+ return puppeteerInstance.close();
291
+ })
292
+ .catch((err) => {
256
293
  console.log('Unable to close browser', err);
257
294
  });
258
295
  }
259
- stopCycling();
260
- cleanup === null || cleanup === void 0 ? void 0 : cleanup();
296
+ cleanup.forEach((c) => {
297
+ c();
298
+ });
261
299
  });
262
300
  });
263
301
  };
@@ -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';
@@ -38,6 +39,7 @@ export declare type RenderMediaOptions = {
38
39
  chromiumOptions?: ChromiumOptions;
39
40
  scale?: number;
40
41
  port?: number | null;
42
+ cancelSignal?: CancelSignal;
41
43
  browserExecutable?: BrowserExecutable;
42
44
  } & 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>;
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, browserExecutable, port, ...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
  }
@@ -124,55 +151,89 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
124
151
  ffmpegExecutable,
125
152
  browserExecutable,
126
153
  port,
154
+ cancelSignal: cancelRenderFrames.cancelSignal,
127
155
  });
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
- }
156
+ return renderFramesProc;
157
+ })
158
+ .then((renderFramesReturn) => {
159
+ return Promise.all([renderFramesReturn, waitForPrestitcherIfNecessary()]);
160
+ })
161
+ .then(([{ assetsInfo }]) => {
138
162
  renderedDoneIn = Date.now() - renderStart;
139
163
  callUpdate();
140
164
  (0, ensure_output_directory_1.ensureOutputDirectory)(outputLocation);
141
165
  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
- });
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]) => {
167
197
  encodedFrames = (0, get_duration_from_frame_range_1.getDurationFromFrameRange)(frameRange !== null && frameRange !== void 0 ? frameRange : null, composition.durationInFrames);
168
198
  encodedDoneIn = Date.now() - stitchStart;
169
199
  callUpdate();
170
- }
171
- 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(() => {
172
225
  if (preEncodedFileLocation !== null &&
173
226
  fs_1.default.existsSync(preEncodedFileLocation)) {
174
227
  fs_1.default.unlinkSync(preEncodedFileLocation);
175
228
  }
176
- }
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
+ ]);
177
238
  };
178
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,6 +20,7 @@ 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
26
  declare type RenderStillOptions = InnerStillOptions & ServeUrlOrWebpackBundle & {
@@ -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()`');
@@ -77,6 +81,15 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
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,7 +145,7 @@ 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) => {
148
+ const happyPath = new Promise((resolve, reject) => {
141
149
  var _a, _b;
142
150
  const onError = (err) => reject(err);
143
151
  let close = null;
@@ -162,5 +170,14 @@ const renderStill = (options) => {
162
170
  .catch((err) => reject(err))
163
171
  .finally(() => close === null || close === void 0 ? void 0 : close());
164
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
+ ]);
165
182
  };
166
183
  exports.renderStill = renderStill;
@@ -44,6 +44,9 @@ const serveStatic = async (path, options) => {
44
44
  return new Promise((resolve, reject) => {
45
45
  server.close((err) => {
46
46
  if (err) {
47
+ if (err.code === 'ERR_SERVER_NOT_RUNNING') {
48
+ return resolve();
49
+ }
47
50
  reject(err);
48
51
  }
49
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>;
@@ -5,7 +5,7 @@ const remotion_1 = require("remotion");
5
5
  const normalize_serve_url_1 = require("./normalize-serve-url");
6
6
  const puppeteer_evaluate_1 = require("./puppeteer-evaluate");
7
7
  const validate_puppeteer_timeout_1 = require("./validate-puppeteer-timeout");
8
- const setPropsAndEnv = async ({ inputProps, envVariables, page, serveUrl, initialFrame, timeoutInMilliseconds, proxyPort, }) => {
8
+ const setPropsAndEnv = async ({ inputProps, envVariables, page, serveUrl, initialFrame, timeoutInMilliseconds, proxyPort, retriesRemaining, }) => {
9
9
  (0, validate_puppeteer_timeout_1.validatePuppeteerTimeout)(timeoutInMilliseconds);
10
10
  const actualTimeout = timeoutInMilliseconds !== null && timeoutInMilliseconds !== void 0 ? timeoutInMilliseconds : remotion_1.Internals.DEFAULT_PUPPETEER_TIMEOUT;
11
11
  page.setDefaultTimeout(actualTimeout);
@@ -32,6 +32,25 @@ const setPropsAndEnv = async ({ inputProps, envVariables, page, serveUrl, initia
32
32
  }, [proxyPort]);
33
33
  const pageRes = await page.goto(urlToVisit);
34
34
  const status = pageRes.status();
35
+ // S3 in rare occasions returns a 500 or 503 error code for GET operations.
36
+ // Usually it is fixed by retrying.
37
+ if (status >= 500 && status <= 504 && retriesRemaining > 0) {
38
+ await new Promise((resolve) => {
39
+ setTimeout(() => {
40
+ resolve();
41
+ }, 2000);
42
+ });
43
+ return (0, exports.setPropsAndEnv)({
44
+ envVariables,
45
+ initialFrame,
46
+ inputProps,
47
+ page,
48
+ proxyPort,
49
+ retriesRemaining: retriesRemaining - 1,
50
+ serveUrl,
51
+ timeoutInMilliseconds,
52
+ });
53
+ }
35
54
  if (status !== 200 &&
36
55
  status !== 301 &&
37
56
  status !== 302 &&
@@ -1,5 +1,6 @@
1
1
  import { Codec, FfmpegExecutable, ImageFormat, PixelFormat, ProResProfile, RenderAssetInfo } from 'remotion';
2
2
  import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
3
+ import { CancelSignal } from './make-cancel-signal';
3
4
  export declare type StitcherOptions = {
4
5
  fps: number;
5
6
  width: number;
@@ -16,13 +17,14 @@ export declare type StitcherOptions = {
16
17
  verbose?: boolean;
17
18
  ffmpegExecutable?: FfmpegExecutable;
18
19
  dir?: string;
20
+ cancelSignal?: CancelSignal;
19
21
  internalOptions?: {
20
22
  preEncodedFileLocation: string | null;
21
23
  imageFormat: ImageFormat;
22
24
  };
23
25
  };
24
26
  declare type ReturnType = {
25
- task: Promise<unknown>;
27
+ task: Promise<void>;
26
28
  getLogs: () => string;
27
29
  };
28
30
  export declare const spawnFfmpeg: (options: StitcherOptions) => Promise<ReturnType>;
@@ -68,7 +68,7 @@ const getAssetsData = async ({ assets, downloadDir, onDownload, fps, expectedFra
68
68
  return outName;
69
69
  };
70
70
  const spawnFfmpeg = async (options) => {
71
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
71
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
72
72
  remotion_1.Internals.validateDimension(options.height, 'height', 'passed to `stitchFramesToVideo()`');
73
73
  remotion_1.Internals.validateDimension(options.width, 'width', 'passed to `stitchFramesToVideo()`');
74
74
  remotion_1.Internals.validateFps(options.fps, 'passed to `stitchFramesToVideo()`');
@@ -121,7 +121,7 @@ const spawnFfmpeg = async (options) => {
121
121
  if (!audioCodecName) {
122
122
  throw new TypeError('exporting audio but has no audio codec name. Report this in the Remotion repo.');
123
123
  }
124
- await (0, execa_1.default)('ffmpeg', [
124
+ const ffmpegTask = (0, execa_1.default)('ffmpeg', [
125
125
  '-i',
126
126
  audio,
127
127
  '-c:a',
@@ -129,7 +129,11 @@ const spawnFfmpeg = async (options) => {
129
129
  options.force ? '-y' : null,
130
130
  options.outputLocation,
131
131
  ].filter(remotion_1.Internals.truthy));
132
- (_h = options.onProgress) === null || _h === void 0 ? void 0 : _h.call(options, expectedFrames);
132
+ (_h = options.cancelSignal) === null || _h === void 0 ? void 0 : _h.call(options, () => {
133
+ ffmpegTask.kill();
134
+ });
135
+ await ffmpegTask;
136
+ (_j = options.onProgress) === null || _j === void 0 ? void 0 : _j.call(options, expectedFrames);
133
137
  return {
134
138
  getLogs: () => '',
135
139
  task: Promise.resolve(),
@@ -137,8 +141,8 @@ const spawnFfmpeg = async (options) => {
137
141
  }
138
142
  const ffmpegArgs = [
139
143
  ['-r', String(options.fps)],
140
- ...(((_j = options.internalOptions) === null || _j === void 0 ? void 0 : _j.preEncodedFileLocation)
141
- ? [['-i', (_k = options.internalOptions) === null || _k === void 0 ? void 0 : _k.preEncodedFileLocation]]
144
+ ...(((_k = options.internalOptions) === null || _k === void 0 ? void 0 : _k.preEncodedFileLocation)
145
+ ? [['-i', (_l = options.internalOptions) === null || _l === void 0 ? void 0 : _l.preEncodedFileLocation]]
142
146
  : [
143
147
  ['-f', 'image2'],
144
148
  ['-s', `${options.width}x${options.height}`],
@@ -149,7 +153,7 @@ const spawnFfmpeg = async (options) => {
149
153
  // -c:v is the same as -vcodec as -codec:video
150
154
  // and specified the video codec.
151
155
  ['-c:v', encoderName],
152
- ...(((_l = options.internalOptions) === null || _l === void 0 ? void 0 : _l.preEncodedFileLocation)
156
+ ...(((_m = options.internalOptions) === null || _m === void 0 ? void 0 : _m.preEncodedFileLocation)
153
157
  ? []
154
158
  : [
155
159
  proResProfileName ? ['-profile:v', proResProfileName] : null,
@@ -177,12 +181,15 @@ const spawnFfmpeg = async (options) => {
177
181
  console.log(ffmpegArgs);
178
182
  }
179
183
  const ffmpegString = ffmpegArgs.flat(2).filter(Boolean);
180
- const task = (0, execa_1.default)((_m = options.ffmpegExecutable) !== null && _m !== void 0 ? _m : 'ffmpeg', ffmpegString, {
184
+ const task = (0, execa_1.default)((_o = options.ffmpegExecutable) !== null && _o !== void 0 ? _o : 'ffmpeg', ffmpegString, {
181
185
  cwd: options.dir,
182
186
  });
187
+ (_p = options.cancelSignal) === null || _p === void 0 ? void 0 : _p.call(options, () => {
188
+ task.kill();
189
+ });
183
190
  let ffmpegOutput = '';
184
191
  let isFinished = false;
185
- (_o = task.stderr) === null || _o === void 0 ? void 0 : _o.on('data', (data) => {
192
+ (_q = task.stderr) === null || _q === void 0 ? void 0 : _q.on('data', (data) => {
186
193
  var _a;
187
194
  const str = data.toString();
188
195
  ffmpegOutput += str;
@@ -204,16 +211,22 @@ const spawnFfmpeg = async (options) => {
204
211
  }
205
212
  }
206
213
  });
207
- return { task, getLogs: () => ffmpegOutput };
214
+ return { task: task.then(() => undefined), getLogs: () => ffmpegOutput };
208
215
  };
209
216
  exports.spawnFfmpeg = spawnFfmpeg;
210
217
  const stitchFramesToVideo = async (options) => {
211
218
  const { task, getLogs } = await (0, exports.spawnFfmpeg)(options);
212
- try {
213
- await task;
214
- }
215
- catch (err) {
219
+ const happyPath = task.catch(() => {
216
220
  throw new Error(getLogs());
217
- }
221
+ });
222
+ return Promise.race([
223
+ happyPath,
224
+ new Promise((_resolve, reject) => {
225
+ var _a;
226
+ (_a = options.cancelSignal) === null || _a === void 0 ? void 0 : _a.call(options, () => {
227
+ reject(new Error('stitchFramesToVideo() got cancelled'));
228
+ });
229
+ }),
230
+ ]);
218
231
  };
219
232
  exports.stitchFramesToVideo = stitchFramesToVideo;
package/dist/tmp-dir.js CHANGED
@@ -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];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/renderer",
3
- "version": "3.0.11",
3
+ "version": "3.0.15",
4
4
  "description": "Renderer for Remotion",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "execa": "5.1.1",
24
24
  "puppeteer-core": "13.5.1",
25
- "remotion": "3.0.11",
25
+ "remotion": "3.0.15",
26
26
  "serve-handler": "6.1.3",
27
27
  "source-map": "^0.8.0-beta.0"
28
28
  },
@@ -46,7 +46,7 @@
46
46
  "react": "18.0.0",
47
47
  "react-dom": "18.0.0",
48
48
  "ts-jest": "^27.0.5",
49
- "typescript": "^4.5.5"
49
+ "typescript": "^4.7.0"
50
50
  },
51
51
  "keywords": [
52
52
  "remotion",
@@ -59,5 +59,5 @@
59
59
  "publishConfig": {
60
60
  "access": "public"
61
61
  },
62
- "gitHead": "7bdd2de3305f669cefecfdbda7f555df1480d9c9"
62
+ "gitHead": "955b43714a53713963f862d3e9aec86de85f1ebd"
63
63
  }