@remotion/renderer 4.0.0-preload.13 → 4.0.0-preload.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.
@@ -2,6 +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
6
  export declare const markAllAssetsAsDownloaded: () => void;
6
7
  export declare const getSanitizedFilenameForAssetUrl: ({ src, downloadDir, }: {
7
8
  src: string;
@@ -12,3 +13,8 @@ export declare const downloadAndMapAssetsToFileUrl: ({ asset, downloadDir, onDow
12
13
  downloadDir: string;
13
14
  onDownload: RenderMediaOnDownload;
14
15
  }) => Promise<TAsset>;
16
+ export declare const startDownloadForSrc: ({ src, downloadDir, onDownload, }: {
17
+ src: string;
18
+ downloadDir: string;
19
+ onDownload: RenderMediaOnDownload;
20
+ }) => Promise<string>;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.downloadAndMapAssetsToFileUrl = exports.getSanitizedFilenameForAssetUrl = exports.markAllAssetsAsDownloaded = void 0;
6
+ exports.startDownloadForSrc = exports.downloadAndMapAssetsToFileUrl = exports.getSanitizedFilenameForAssetUrl = exports.markAllAssetsAsDownloaded = exports.waitForAssetToBeDownloaded = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const remotion_1 = require("remotion");
@@ -14,20 +14,24 @@ const isDownloadingMap = {};
14
14
  const hasBeenDownloadedMap = {};
15
15
  const listeners = {};
16
16
  const waitForAssetToBeDownloaded = (src) => {
17
+ if (hasBeenDownloadedMap[src]) {
18
+ return Promise.resolve(hasBeenDownloadedMap[src]);
19
+ }
17
20
  if (!listeners[src]) {
18
21
  listeners[src] = [];
19
22
  }
20
23
  return new Promise((resolve) => {
21
- listeners[src].push(() => resolve());
24
+ listeners[src].push((to) => resolve(to));
22
25
  });
23
26
  };
24
- const notifyAssetIsDownloaded = (src) => {
27
+ exports.waitForAssetToBeDownloaded = waitForAssetToBeDownloaded;
28
+ const notifyAssetIsDownloaded = (src, to) => {
25
29
  if (!listeners[src]) {
26
30
  listeners[src] = [];
27
31
  }
28
- listeners[src].forEach((fn) => fn());
32
+ listeners[src].forEach((fn) => fn(to));
29
33
  isDownloadingMap[src] = false;
30
- hasBeenDownloadedMap[src] = true;
34
+ hasBeenDownloadedMap[src] = to;
31
35
  };
32
36
  const validateMimeType = (mimeType, src) => {
33
37
  if (!mimeType.includes('/')) {
@@ -73,7 +77,7 @@ const downloadAsset = async (src, to, onDownload) => {
73
77
  return;
74
78
  }
75
79
  if (isDownloadingMap[src]) {
76
- return waitForAssetToBeDownloaded(src);
80
+ return (0, exports.waitForAssetToBeDownloaded)(src);
77
81
  }
78
82
  isDownloadingMap[src] = true;
79
83
  const onProgress = onDownload(src);
@@ -94,7 +98,7 @@ const downloadAsset = async (src, to, onDownload) => {
94
98
  validateBufferEncoding(encoding, src);
95
99
  const buff = Buffer.from(assetData, encoding);
96
100
  await fs_1.default.promises.writeFile(to, buff);
97
- notifyAssetIsDownloaded(src);
101
+ notifyAssetIsDownloaded(src, to);
98
102
  return;
99
103
  }
100
104
  await (0, download_file_1.downloadFile)(src, to, ({ progress }) => {
@@ -102,7 +106,7 @@ const downloadAsset = async (src, to, onDownload) => {
102
106
  percent: progress,
103
107
  });
104
108
  });
105
- notifyAssetIsDownloaded(src);
109
+ notifyAssetIsDownloaded(src, to);
106
110
  };
107
111
  const markAllAssetsAsDownloaded = () => {
108
112
  Object.keys(hasBeenDownloadedMap).forEach((key) => {
@@ -127,16 +131,22 @@ const getSanitizedFilenameForAssetUrl = ({ src, downloadDir, }) => {
127
131
  };
128
132
  exports.getSanitizedFilenameForAssetUrl = getSanitizedFilenameForAssetUrl;
129
133
  const downloadAndMapAssetsToFileUrl = async ({ asset, downloadDir, onDownload, }) => {
130
- const newSrc = (0, exports.getSanitizedFilenameForAssetUrl)({
134
+ const newSrc = await (0, exports.startDownloadForSrc)({
131
135
  src: asset.src,
132
136
  downloadDir,
137
+ onDownload,
133
138
  });
134
- if (!remotion_1.Internals.AssetCompression.isAssetCompressed(newSrc)) {
135
- await downloadAsset(asset.src, newSrc, onDownload);
136
- }
137
139
  return {
138
140
  ...asset,
139
141
  src: newSrc,
140
142
  };
141
143
  };
142
144
  exports.downloadAndMapAssetsToFileUrl = downloadAndMapAssetsToFileUrl;
145
+ const startDownloadForSrc = async ({ src, downloadDir, onDownload, }) => {
146
+ const newSrc = (0, exports.getSanitizedFilenameForAssetUrl)({ downloadDir, src });
147
+ if (!remotion_1.Internals.AssetCompression.isAssetCompressed(newSrc)) {
148
+ await downloadAsset(src, newSrc, onDownload);
149
+ }
150
+ return newSrc;
151
+ };
152
+ exports.startDownloadForSrc = startDownloadForSrc;
@@ -4,9 +4,10 @@ declare type FfmpegVolumeExpression = {
4
4
  eval: FfmpegEval;
5
5
  value: string;
6
6
  };
7
- export declare const ffmpegVolumeExpression: ({ volume, startInVideo, fps, }: {
7
+ export declare const ffmpegVolumeExpression: ({ volume, startInVideo, fps, trimLeft, }: {
8
8
  volume: AssetVolume;
9
9
  startInVideo: number;
10
+ trimLeft: number;
10
11
  fps: number;
11
12
  }) => FfmpegVolumeExpression;
12
13
  export {};
@@ -12,12 +12,12 @@ const FFMPEG_TIME_VARIABLE = 't';
12
12
  const ffmpegIfOrElse = (condition, then, elseDo) => {
13
13
  return `if(${condition},${then},${elseDo})`;
14
14
  };
15
- const ffmpegIsOneOfFrames = (frames, fps) => {
15
+ const ffmpegIsOneOfFrames = ({ frames, trimLeft, fps, }) => {
16
16
  const consecutiveArrays = [];
17
17
  for (let i = 0; i < frames.length; i++) {
18
18
  const previousFrame = frames[i - 1];
19
19
  const frame = frames[i];
20
- if (!previousFrame || frame !== previousFrame + 1) {
20
+ if (previousFrame === undefined || frame !== previousFrame + 1) {
21
21
  consecutiveArrays.push([]);
22
22
  }
23
23
  consecutiveArrays[consecutiveArrays.length - 1].push(frame);
@@ -28,25 +28,22 @@ const ffmpegIsOneOfFrames = (frames, fps) => {
28
28
  const lastFrame = f[f.length - 1];
29
29
  const before = (firstFrame - 0.5) / fps;
30
30
  const after = (lastFrame + 0.5) / fps;
31
- return `between(${FFMPEG_TIME_VARIABLE},${before.toFixed(4)},${after.toFixed(4)})`;
31
+ return `between(${FFMPEG_TIME_VARIABLE},${(before + trimLeft).toFixed(4)},${(after + trimLeft).toFixed(4)})`;
32
32
  })
33
33
  .join('+');
34
34
  };
35
- const ffmpegBuildVolumeExpression = (arr, fps) => {
35
+ const ffmpegBuildVolumeExpression = (arr, delay, fps) => {
36
36
  if (arr.length === 0) {
37
37
  throw new Error('Volume array expression should never have length 0');
38
38
  }
39
39
  if (arr.length === 1) {
40
- // FFMpeg tends to request volume for frames outside the range
41
- // where the audio actually plays.
42
- // If this is the case, we just return volume 0 to clip it.
43
- return ffmpegIfOrElse(ffmpegIsOneOfFrames(arr[0][1], fps), String(arr[0][0]), String(0));
40
+ return String(arr[0][0]);
44
41
  }
45
42
  const [first, ...rest] = arr;
46
43
  const [volume, frames] = first;
47
- return ffmpegIfOrElse(ffmpegIsOneOfFrames(frames, fps), String(volume), ffmpegBuildVolumeExpression(rest, fps));
44
+ return ffmpegIfOrElse(ffmpegIsOneOfFrames({ frames, trimLeft: delay, fps }), String(volume), ffmpegBuildVolumeExpression(rest, delay, fps));
48
45
  };
49
- const ffmpegVolumeExpression = ({ volume, startInVideo, fps, }) => {
46
+ const ffmpegVolumeExpression = ({ volume, startInVideo, fps, trimLeft, }) => {
50
47
  // If it's a static volume, we return it and tell
51
48
  // FFMPEG it only has to evaluate it once
52
49
  if (typeof volume === 'number') {
@@ -60,14 +57,20 @@ const ffmpegVolumeExpression = ({ volume, startInVideo, fps, }) => {
60
57
  volume: volume[0],
61
58
  startInVideo,
62
59
  fps,
60
+ trimLeft,
63
61
  });
64
62
  }
63
+ // A 1 sec video with frames 0-29 would mean that
64
+ // frame 29 corresponds to timestamp 0.966666...
65
+ // but the audio is actually 1 sec long. For that reason we pad the last
66
+ // timestamp.
67
+ const paddedVolume = [...volume, volume[volume.length - 1]];
65
68
  // Otherwise, we construct an FFMPEG expression. First step:
66
69
  // Make a map of all possible volumes
67
70
  // {possibleVolume1} => [frame1, frame2]
68
71
  // {possibleVolume2} => [frame3, frame4]
69
72
  const volumeMap = {};
70
- volume.forEach((baseVolume, frame) => {
73
+ paddedVolume.forEach((baseVolume, frame) => {
71
74
  // Adjust volume based on how many other tracks have not yet finished
72
75
  const actualVolume = (0, round_volume_to_avoid_stack_overflow_1.roundVolumeToAvoidStackOverflow)(Math.min(1, baseVolume));
73
76
  if (!volumeMap[actualVolume]) {
@@ -81,7 +84,7 @@ const ffmpegVolumeExpression = ({ volume, startInVideo, fps, }) => {
81
84
  .map((key) => [Number(key), volumeMap[key]])
82
85
  .sort((a, b) => a[1].length - b[1].length);
83
86
  // Construct and tell FFMPEG it has to evaluate expression on each frame
84
- const expression = ffmpegBuildVolumeExpression(volumeArray, fps);
87
+ const expression = ffmpegBuildVolumeExpression(volumeArray, trimLeft, fps);
85
88
  return {
86
89
  eval: 'frame',
87
90
  value: `'${expression}'`,
@@ -28,6 +28,8 @@ const combineVideos = async ({ files, filelistDir, output, onProgress, numberOfF
28
28
  remotion_1.Internals.isAudioCodec(codec) ? null : 'copy',
29
29
  '-c:a',
30
30
  (0, get_audio_codec_name_1.getAudioCodecName)(codec),
31
+ codec === 'h264' ? '-movflags' : null,
32
+ codec === 'h264' ? 'faststart' : null,
31
33
  '-shortest',
32
34
  '-y',
33
35
  output,
@@ -0,0 +1,17 @@
1
+ /// <reference types="node" />
2
+ import { FfmpegExecutable } from 'remotion';
3
+ import { Readable } from 'stream';
4
+ export declare function streamToString(stream: Readable): Promise<string>;
5
+ export declare const getLastFrameOfVideo: ({ ffmpegExecutable, offset, src, }: {
6
+ ffmpegExecutable: FfmpegExecutable;
7
+ offset: number;
8
+ src: string;
9
+ }) => Promise<Buffer>;
10
+ declare type Options = {
11
+ time: number;
12
+ src: string;
13
+ ffmpegExecutable: FfmpegExecutable;
14
+ };
15
+ export declare const extractFrameFromVideoFn: ({ time, src, ffmpegExecutable, }: Options) => Promise<Buffer>;
16
+ export declare const extractFrameFromVideo: (options: Options) => Promise<Buffer>;
17
+ export {};
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.extractFrameFromVideo = exports.extractFrameFromVideoFn = exports.getLastFrameOfVideo = exports.streamToString = void 0;
7
+ const execa_1 = __importDefault(require("execa"));
8
+ const frame_to_ffmpeg_timestamp_1 = require("./frame-to-ffmpeg-timestamp");
9
+ const p_limit_1 = require("./p-limit");
10
+ function streamToString(stream) {
11
+ const chunks = [];
12
+ return new Promise((resolve, reject) => {
13
+ stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
14
+ stream.on('error', (err) => reject(err));
15
+ stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
16
+ });
17
+ }
18
+ exports.streamToString = streamToString;
19
+ const getLastFrameOfVideo = async ({ ffmpegExecutable, offset, src, }) => {
20
+ if (offset > 100) {
21
+ throw new Error('could not get last frame of ' +
22
+ src +
23
+ '. Tried to seek 100ms before the end of the video and no frame was found. The video container has a duration that is longer than it contains video.');
24
+ }
25
+ const actualOffset = `-${offset + 10}ms`;
26
+ const { stdout, stderr } = (0, execa_1.default)(ffmpegExecutable !== null && ffmpegExecutable !== void 0 ? ffmpegExecutable : 'ffmpeg', [
27
+ '-sseof',
28
+ actualOffset,
29
+ '-i',
30
+ src,
31
+ '-frames:v',
32
+ '1',
33
+ '-f',
34
+ 'image2pipe',
35
+ '-',
36
+ ]);
37
+ if (!stderr) {
38
+ throw new Error('unexpectedly did not get stderr');
39
+ }
40
+ if (!stdout) {
41
+ throw new Error('unexpectedly did not get stdout');
42
+ }
43
+ const stderrChunks = [];
44
+ const stdoutChunks = [];
45
+ const stdErrString = new Promise((resolve, reject) => {
46
+ stderr.on('data', (d) => stderrChunks.push(d));
47
+ stderr.on('error', (err) => reject(err));
48
+ stderr.on('end', () => resolve(Buffer.concat(stderrChunks).toString('utf-8')));
49
+ });
50
+ const stdoutChunk = new Promise((resolve, reject) => {
51
+ stdout.on('data', (d) => {
52
+ stdoutChunks.push(d);
53
+ });
54
+ stdout.on('error', (err) => {
55
+ reject(err);
56
+ });
57
+ stdout.on('end', () => {
58
+ resolve(Buffer.concat(stdoutChunks));
59
+ });
60
+ });
61
+ const [stdErr, stdoutBuffer] = await Promise.all([stdErrString, stdoutChunk]);
62
+ const isEmpty = stdErr.includes('Output file is empty');
63
+ if (isEmpty) {
64
+ return (0, exports.getLastFrameOfVideo)({ ffmpegExecutable, offset: offset + 10, src });
65
+ }
66
+ return stdoutBuffer;
67
+ };
68
+ exports.getLastFrameOfVideo = getLastFrameOfVideo;
69
+ const limit = (0, p_limit_1.pLimit)(5);
70
+ const extractFrameFromVideoFn = async ({ time, src, ffmpegExecutable, }) => {
71
+ const ffmpegTimestamp = (0, frame_to_ffmpeg_timestamp_1.frameToFfmpegTimestamp)(time);
72
+ const { stdout, stderr } = (0, execa_1.default)(ffmpegExecutable !== null && ffmpegExecutable !== void 0 ? ffmpegExecutable : 'ffmpeg', [
73
+ '-ss',
74
+ ffmpegTimestamp,
75
+ '-i',
76
+ src,
77
+ '-frames:v',
78
+ '1',
79
+ '-f',
80
+ 'image2pipe',
81
+ '-',
82
+ ], {
83
+ buffer: false,
84
+ });
85
+ if (!stderr) {
86
+ throw new Error('unexpectedly did not get stderr');
87
+ }
88
+ if (!stdout) {
89
+ throw new Error('unexpectedly did not get stdout');
90
+ }
91
+ const stdoutChunks = [];
92
+ const stderrChunks = [];
93
+ const stderrStringProm = new Promise((resolve, reject) => {
94
+ stderr.on('data', (d) => {
95
+ stderrChunks.push(d);
96
+ });
97
+ stderr.on('error', (err) => {
98
+ reject(err);
99
+ });
100
+ stderr.on('end', () => {
101
+ resolve(Buffer.concat(stderrChunks).toString('utf8'));
102
+ });
103
+ });
104
+ const stdoutBuffer = new Promise((resolve, reject) => {
105
+ stdout.on('data', (d) => {
106
+ stdoutChunks.push(d);
107
+ });
108
+ stdout.on('error', (err) => {
109
+ reject(err);
110
+ });
111
+ stdout.on('end', () => {
112
+ resolve(Buffer.concat(stdoutChunks));
113
+ });
114
+ });
115
+ const [stderrStr, stdOut] = await Promise.all([
116
+ stderrStringProm,
117
+ stdoutBuffer,
118
+ ]);
119
+ if (stderrStr.includes('Output file is empty')) {
120
+ return (0, exports.getLastFrameOfVideo)({
121
+ ffmpegExecutable,
122
+ offset: 0,
123
+ src,
124
+ });
125
+ }
126
+ return stdOut;
127
+ };
128
+ exports.extractFrameFromVideoFn = extractFrameFromVideoFn;
129
+ const extractFrameFromVideo = (options) => {
130
+ return limit(exports.extractFrameFromVideoFn, options);
131
+ };
132
+ exports.extractFrameFromVideo = extractFrameFromVideo;
@@ -0,0 +1 @@
1
+ export declare const frameToFfmpegTimestamp: (time: number) => string;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.frameToFfmpegTimestamp = void 0;
4
+ const frameToFfmpegTimestamp = (time) => {
5
+ // We ceil because FFMPEG seeks to the closest frame BEFORE the position
6
+ return (Math.ceil(time * 1000000) / 1000).toFixed(3) + 'ms';
7
+ };
8
+ exports.frameToFfmpegTimestamp = frameToFfmpegTimestamp;
@@ -1,5 +1,5 @@
1
1
  import { Browser } from 'puppeteer-core';
2
- import { BrowserExecutable, TCompMetadata } from 'remotion';
2
+ import { BrowserExecutable, FfmpegExecutable, TCompMetadata } from 'remotion';
3
3
  import { BrowserLog } from './browser-log';
4
4
  import { ChromiumOptions } from './open-browser';
5
5
  declare type GetCompositionsConfig = {
@@ -10,6 +10,8 @@ declare type GetCompositionsConfig = {
10
10
  browserExecutable?: BrowserExecutable;
11
11
  timeoutInMilliseconds?: number;
12
12
  chromiumOptions?: ChromiumOptions;
13
+ ffmpegExecutable?: FfmpegExecutable;
14
+ port?: number | null;
13
15
  };
14
16
  export declare const getCompositions: (serveUrlOrWebpackUrl: string, config?: GetCompositionsConfig | undefined) => Promise<TCompMetadata[]>;
15
17
  export {};
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getCompositions = void 0;
4
4
  const handle_javascript_exception_1 = require("./error-handling/handle-javascript-exception");
5
5
  const get_browser_instance_1 = require("./get-browser-instance");
6
+ const make_assets_download_dir_1 = require("./make-assets-download-dir");
6
7
  const prepare_server_1 = require("./prepare-server");
7
8
  const puppeteer_evaluate_1 = require("./puppeteer-evaluate");
8
9
  const set_props_and_env_1 = require("./set-props-and-env");
9
10
  const validate_puppeteer_timeout_1 = require("./validate-puppeteer-timeout");
10
- const innerGetCompositions = async (serveUrl, page, config) => {
11
+ const innerGetCompositions = async (serveUrl, page, config, proxyPort) => {
11
12
  if (config === null || config === void 0 ? void 0 : config.onBrowserLog) {
12
13
  page.on('console', (log) => {
13
14
  var _a;
@@ -26,6 +27,7 @@ const innerGetCompositions = async (serveUrl, page, config) => {
26
27
  serveUrl,
27
28
  initialFrame: 0,
28
29
  timeoutInMilliseconds: config === null || config === void 0 ? void 0 : config.timeoutInMilliseconds,
30
+ proxyPort,
29
31
  });
30
32
  await (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
31
33
  page,
@@ -50,26 +52,40 @@ const innerGetCompositions = async (serveUrl, page, config) => {
50
52
  };
51
53
  const getCompositions = async (serveUrlOrWebpackUrl, config) => {
52
54
  var _a, _b;
53
- const { serveUrl, closeServer } = await (0, prepare_server_1.prepareServer)(serveUrlOrWebpackUrl);
55
+ const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
54
56
  const { page, cleanup } = await (0, get_browser_instance_1.getPageAndCleanupFn)({
55
57
  passedInInstance: config === null || config === void 0 ? void 0 : config.puppeteerInstance,
56
58
  browserExecutable: (_a = config === null || config === void 0 ? void 0 : config.browserExecutable) !== null && _a !== void 0 ? _a : null,
57
59
  chromiumOptions: (_b = config === null || config === void 0 ? void 0 : config.chromiumOptions) !== null && _b !== void 0 ? _b : {},
58
60
  });
59
61
  return new Promise((resolve, reject) => {
62
+ var _a, _b;
63
+ const onError = (err) => reject(err);
60
64
  const cleanupPageError = (0, handle_javascript_exception_1.handleJavascriptException)({
61
65
  page,
62
66
  frame: null,
63
- onError: (err) => reject(err),
67
+ onError,
64
68
  });
65
- innerGetCompositions(serveUrl, page, config !== null && config !== void 0 ? config : {})
69
+ let close = null;
70
+ (0, prepare_server_1.prepareServer)({
71
+ webpackConfigOrServeUrl: serveUrlOrWebpackUrl,
72
+ downloadDir,
73
+ onDownload: () => undefined,
74
+ onError,
75
+ ffmpegExecutable: (_a = config === null || config === void 0 ? void 0 : config.ffmpegExecutable) !== null && _a !== void 0 ? _a : null,
76
+ port: (_b = config === null || config === void 0 ? void 0 : config.port) !== null && _b !== void 0 ? _b : null,
77
+ })
78
+ .then(({ serveUrl, closeServer, offthreadPort }) => {
79
+ close = closeServer;
80
+ return innerGetCompositions(serveUrl, page, config !== null && config !== void 0 ? config : {}, offthreadPort);
81
+ })
66
82
  .then((comp) => resolve(comp))
67
83
  .catch((err) => {
68
84
  reject(err);
69
85
  })
70
86
  .finally(() => {
71
87
  cleanup();
72
- closeServer();
88
+ close === null || close === void 0 ? void 0 : close();
73
89
  cleanupPageError();
74
90
  });
75
91
  });
package/dist/index.d.ts CHANGED
@@ -29,9 +29,13 @@ export declare const RenderInternals: {
29
29
  getFfmpegBuildInfo: (options: {
30
30
  ffmpegExecutable: string | null;
31
31
  }) => Promise<string>;
32
- serveStatic: (path: string, options?: {
33
- port?: number | undefined;
34
- } | undefined) => Promise<{
32
+ serveStatic: (path: string | null, options: {
33
+ port: number | null;
34
+ ffmpegExecutable: import("remotion").FfmpegExecutable;
35
+ downloadDir: string;
36
+ onDownload: import("./assets/download-and-map-assets-to-file").RenderMediaOnDownload;
37
+ onError: (err: Error) => void;
38
+ }) => Promise<{
35
39
  port: number;
36
40
  close: () => Promise<void>;
37
41
  }>;
@@ -50,10 +54,6 @@ export declare const RenderInternals: {
50
54
  makeAssetsDownloadTmpDir: () => string;
51
55
  tmpDir: (str: string) => string;
52
56
  deleteDirectory: (directory: string) => Promise<void>;
53
- prepareServer: (webpackConfigOrServeUrl: string) => Promise<{
54
- serveUrl: string;
55
- closeServer: () => Promise<void>;
56
- }>;
57
57
  isServeUrl: (potentialUrl: string) => boolean;
58
58
  ensureOutputDirectory: (outputLocation: string) => void;
59
59
  getRealFrameRange: (durationInFrames: number, frameRange: import("remotion").FrameRange | null) => [number, number];
package/dist/index.js CHANGED
@@ -18,7 +18,6 @@ const make_assets_download_dir_1 = require("./make-assets-download-dir");
18
18
  const normalize_serve_url_1 = require("./normalize-serve-url");
19
19
  const open_browser_1 = require("./open-browser");
20
20
  const parse_browser_error_stack_1 = require("./parse-browser-error-stack");
21
- const prepare_server_1 = require("./prepare-server");
22
21
  const serve_static_1 = require("./serve-static");
23
22
  const stitch_frames_to_video_1 = require("./stitch-frames-to-video");
24
23
  const tmp_dir_1 = require("./tmp-dir");
@@ -58,7 +57,6 @@ exports.RenderInternals = {
58
57
  makeAssetsDownloadTmpDir: make_assets_download_dir_1.makeAssetsDownloadTmpDir,
59
58
  tmpDir: tmp_dir_1.tmpDir,
60
59
  deleteDirectory: delete_directory_1.deleteDirectory,
61
- prepareServer: prepare_server_1.prepareServer,
62
60
  isServeUrl: is_serve_url_1.isServeUrl,
63
61
  ensureOutputDirectory: ensure_output_directory_1.ensureOutputDirectory,
64
62
  getRealFrameRange: get_frame_to_render_1.getRealFrameRange,
@@ -0,0 +1,13 @@
1
+ import { RequestListener } from 'http';
2
+ import { FfmpegExecutable } from 'remotion';
3
+ import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
4
+ export declare const extractUrlAndSourceFromUrl: (url: string) => {
5
+ src: string;
6
+ time: number;
7
+ };
8
+ export declare const startOffthreadVideoServer: ({ ffmpegExecutable, downloadDir, onDownload, onError, }: {
9
+ ffmpegExecutable: FfmpegExecutable;
10
+ downloadDir: string;
11
+ onDownload: RenderMediaOnDownload;
12
+ onError: (err: Error) => void;
13
+ }) => RequestListener;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startOffthreadVideoServer = exports.extractUrlAndSourceFromUrl = void 0;
4
+ const url_1 = require("url");
5
+ const download_and_map_assets_to_file_1 = require("./assets/download-and-map-assets-to-file");
6
+ const extract_frame_from_video_1 = require("./extract-frame-from-video");
7
+ const extractUrlAndSourceFromUrl = (url) => {
8
+ const parsed = new URL(url, 'http://localhost');
9
+ const query = parsed.search;
10
+ if (!query.trim()) {
11
+ throw new Error('Expected query from ' + url);
12
+ }
13
+ const params = new url_1.URLSearchParams(query);
14
+ const src = params.get('src');
15
+ if (!src) {
16
+ throw new Error('Did not pass `src` parameter');
17
+ }
18
+ const time = params.get('time');
19
+ if (!time) {
20
+ throw new Error('Did not get `time` parameter');
21
+ }
22
+ return { src, time: parseFloat(time) };
23
+ };
24
+ exports.extractUrlAndSourceFromUrl = extractUrlAndSourceFromUrl;
25
+ const startOffthreadVideoServer = ({ ffmpegExecutable, downloadDir, onDownload, onError, }) => {
26
+ return (req, res) => {
27
+ if (!req.url) {
28
+ throw new Error('Request came in without URL');
29
+ }
30
+ if (!req.url.startsWith('/proxy')) {
31
+ res.writeHead(404);
32
+ res.end();
33
+ return;
34
+ }
35
+ res.setHeader('access-control-allow-origin', '*');
36
+ res.setHeader('content-type', 'image/jpg');
37
+ const { src, time } = (0, exports.extractUrlAndSourceFromUrl)(req.url);
38
+ (0, download_and_map_assets_to_file_1.startDownloadForSrc)({ src, downloadDir, onDownload }).catch((err) => {
39
+ onError(new Error(`Error while downloading asset: ${err.stack}`));
40
+ });
41
+ (0, download_and_map_assets_to_file_1.waitForAssetToBeDownloaded)(src)
42
+ .then((newSrc) => {
43
+ return (0, extract_frame_from_video_1.extractFrameFromVideo)({
44
+ time,
45
+ src: newSrc,
46
+ ffmpegExecutable,
47
+ });
48
+ })
49
+ .then((readable) => {
50
+ if (!readable) {
51
+ throw new Error('no readable from ffmpeg');
52
+ }
53
+ res.writeHead(200);
54
+ res.write(readable);
55
+ res.end();
56
+ })
57
+ .catch((err) => {
58
+ res.writeHead(500);
59
+ res.end();
60
+ console.log('Error occurred', err);
61
+ });
62
+ };
63
+ };
64
+ exports.startOffthreadVideoServer = startOffthreadVideoServer;
@@ -1,4 +1,14 @@
1
- export declare const prepareServer: (webpackConfigOrServeUrl: string) => Promise<{
1
+ import { FfmpegExecutable } from 'remotion';
2
+ import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
3
+ export declare const prepareServer: ({ downloadDir, ffmpegExecutable, onDownload, onError, webpackConfigOrServeUrl, port, }: {
4
+ webpackConfigOrServeUrl: string;
5
+ downloadDir: string;
6
+ onDownload: RenderMediaOnDownload;
7
+ onError: (err: Error) => void;
8
+ ffmpegExecutable: FfmpegExecutable;
9
+ port: number | null;
10
+ }) => Promise<{
2
11
  serveUrl: string;
3
- closeServer: () => Promise<void>;
12
+ closeServer: () => Promise<unknown>;
13
+ offthreadPort: number;
4
14
  }>;
@@ -3,17 +3,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prepareServer = void 0;
4
4
  const is_serve_url_1 = require("./is-serve-url");
5
5
  const serve_static_1 = require("./serve-static");
6
- const prepareServer = async (webpackConfigOrServeUrl) => {
6
+ const prepareServer = async ({ downloadDir, ffmpegExecutable, onDownload, onError, webpackConfigOrServeUrl, port, }) => {
7
7
  if ((0, is_serve_url_1.isServeUrl)(webpackConfigOrServeUrl)) {
8
+ const { port: offthreadPort, close: closeProxy } = await (0, serve_static_1.serveStatic)(null, {
9
+ downloadDir,
10
+ onDownload,
11
+ onError,
12
+ ffmpegExecutable,
13
+ port,
14
+ });
8
15
  return Promise.resolve({
9
16
  serveUrl: webpackConfigOrServeUrl,
10
- closeServer: () => Promise.resolve(undefined),
17
+ closeServer: () => closeProxy(),
18
+ offthreadPort,
11
19
  });
12
20
  }
13
- const { port, close } = await (0, serve_static_1.serveStatic)(webpackConfigOrServeUrl);
21
+ const { port: serverPort, close } = await (0, serve_static_1.serveStatic)(webpackConfigOrServeUrl, {
22
+ downloadDir,
23
+ onDownload,
24
+ onError,
25
+ ffmpegExecutable,
26
+ port,
27
+ });
14
28
  return Promise.resolve({
15
- closeServer: () => close(),
16
- serveUrl: `http://localhost:${port}`,
29
+ closeServer: () => {
30
+ return close();
31
+ },
32
+ serveUrl: `http://localhost:${serverPort}`,
33
+ offthreadPort: serverPort,
17
34
  });
18
35
  };
19
36
  exports.prepareServer = prepareServer;
@@ -57,15 +57,13 @@ const prespawnFfmpeg = async (options) => {
57
57
  // -c:v is the same as -vcodec as -codec:video
58
58
  // and specified the video codec.
59
59
  ['-c:v', encoderName],
60
- ...[
61
- proResProfileName ? ['-profile:v', proResProfileName] : null,
62
- supportsCrf ? ['-crf', String(crf)] : null,
63
- ['-pix_fmt', pixelFormat],
64
- // Without explicitly disabling auto-alt-ref,
65
- // transparent WebM generation doesn't work
66
- pixelFormat === 'yuva420p' ? ['-auto-alt-ref', '0'] : null,
67
- ['-b:v', '1M'],
68
- ],
60
+ proResProfileName ? ['-profile:v', proResProfileName] : null,
61
+ supportsCrf ? ['-crf', String(crf)] : null,
62
+ ['-pix_fmt', pixelFormat],
63
+ // Without explicitly disabling auto-alt-ref,
64
+ // transparent WebM generation doesn't work
65
+ pixelFormat === 'yuva420p' ? ['-auto-alt-ref', '0'] : null,
66
+ ['-b:v', '1M'],
69
67
  '-y',
70
68
  options.outputLocation,
71
69
  ];
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import { Browser as PuppeteerBrowser } from 'puppeteer-core';
3
- import { BrowserExecutable, FrameRange, ImageFormat, SmallTCompMetadata } from 'remotion';
3
+ import { BrowserExecutable, FfmpegExecutable, FrameRange, ImageFormat, SmallTCompMetadata } from 'remotion';
4
4
  import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
5
5
  import { BrowserLog } from './browser-log';
6
6
  import { ServeUrlOrWebpackBundle } from './legacy-webpack-config';
@@ -33,6 +33,8 @@ declare type RenderFramesOptions = {
33
33
  timeoutInMilliseconds?: number;
34
34
  chromiumOptions?: ChromiumOptions;
35
35
  scale?: number;
36
+ ffmpegExecutable?: FfmpegExecutable;
37
+ port?: number | null;
36
38
  } & ConfigOrComposition & ServeUrlOrWebpackBundle;
37
39
  export declare const renderFrames: (options: RenderFramesOptions) => Promise<RenderFramesOutput>;
38
40
  export {};
@@ -33,7 +33,7 @@ const getComposition = (others) => {
33
33
  }
34
34
  return undefined;
35
35
  };
36
- const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps, quality, imageFormat = image_format_1.DEFAULT_IMAGE_FORMAT, frameRange, puppeteerInstance, onError, envVariables, onBrowserLog, onFrameBuffer, onDownload, pagesArray, serveUrl, composition, timeoutInMilliseconds, scale, actualParallelism, }) => {
36
+ const 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, }) => {
37
37
  if (!puppeteerInstance) {
38
38
  throw new Error('weird');
39
39
  }
@@ -76,6 +76,7 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
76
76
  serveUrl,
77
77
  initialFrame,
78
78
  timeoutInMilliseconds,
79
+ proxyPort,
79
80
  });
80
81
  await (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
81
82
  pageFunction: (id) => {
@@ -101,7 +102,6 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
101
102
  onStart({
102
103
  frameCount,
103
104
  });
104
- const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
105
105
  const assets = new Array(frameCount).fill(undefined);
106
106
  await Promise.all(new Array(frameCount)
107
107
  .fill(Boolean)
@@ -163,7 +163,7 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
163
163
  (0, download_and_map_assets_to_file_1.downloadAndMapAssetsToFileUrl)({
164
164
  asset,
165
165
  downloadDir,
166
- onDownload: onDownload !== null && onDownload !== void 0 ? onDownload : (() => () => undefined),
166
+ onDownload,
167
167
  }).catch((err) => {
168
168
  onError(new Error(`Error while downloading asset: ${err.stack}`));
169
169
  });
@@ -187,7 +187,7 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, inputProps
187
187
  return returnValue;
188
188
  };
189
189
  const renderFrames = async (options) => {
190
- var _a, _b, _c;
190
+ var _a, _b, _c, _d;
191
191
  const composition = getComposition(options);
192
192
  if (!composition) {
193
193
  throw new Error('No `composition` option has been specified for renderFrames()');
@@ -202,25 +202,43 @@ const renderFrames = async (options) => {
202
202
  const selectedServeUrl = (0, legacy_webpack_config_1.getServeUrlWithFallback)(options);
203
203
  remotion_1.Internals.validateQuality(options.quality);
204
204
  (0, validate_scale_1.validateScale)(options.scale);
205
- const { closeServer, serveUrl } = await (0, prepare_server_1.prepareServer)(selectedServeUrl);
206
205
  const browserInstance = (_a = options.puppeteerInstance) !== null && _a !== void 0 ? _a : (await (0, open_browser_1.openBrowser)(remotion_1.Internals.DEFAULT_BROWSER, {
207
206
  shouldDumpIo: options.dumpBrowserLogs,
208
207
  browserExecutable: options.browserExecutable,
209
208
  chromiumOptions: options.chromiumOptions,
210
209
  forceDeviceScaleFactor: (_b = options.scale) !== null && _b !== void 0 ? _b : 1,
211
210
  }));
212
- const actualParallelism = (0, get_concurrency_1.getActualConcurrency)((_c = options.parallelism) !== null && _c !== void 0 ? _c : null);
211
+ const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
212
+ const onDownload = (_c = options.onDownload) !== null && _c !== void 0 ? _c : (() => () => undefined);
213
+ const actualParallelism = (0, get_concurrency_1.getActualConcurrency)((_d = options.parallelism) !== null && _d !== void 0 ? _d : null);
213
214
  const { stopCycling } = (0, cycle_browser_tabs_1.cycleBrowserTabs)(browserInstance, actualParallelism);
214
215
  const openedPages = [];
215
216
  return new Promise((resolve, reject) => {
216
- innerRenderFrames({
217
- ...options,
218
- puppeteerInstance: browserInstance,
219
- onError: (err) => reject(err),
220
- pagesArray: openedPages,
221
- serveUrl,
222
- composition,
223
- actualParallelism,
217
+ var _a, _b;
218
+ let cleanup = null;
219
+ 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({
231
+ ...options,
232
+ puppeteerInstance: browserInstance,
233
+ onError,
234
+ pagesArray: openedPages,
235
+ serveUrl,
236
+ composition,
237
+ actualParallelism,
238
+ onDownload,
239
+ downloadDir,
240
+ proxyPort: offthreadPort,
241
+ });
224
242
  })
225
243
  .then((res) => resolve(res))
226
244
  .catch((err) => reject(err))
@@ -239,7 +257,7 @@ const renderFrames = async (options) => {
239
257
  });
240
258
  }
241
259
  stopCycling();
242
- closeServer();
260
+ cleanup === null || cleanup === void 0 ? void 0 : cleanup();
243
261
  });
244
262
  });
245
263
  };
@@ -1,5 +1,5 @@
1
1
  import type { Browser as PuppeteerBrowser } from 'puppeteer-core';
2
- import { Codec, FfmpegExecutable, FrameRange, PixelFormat, ProResProfile, SmallTCompMetadata } from 'remotion';
2
+ import { BrowserExecutable, Codec, FfmpegExecutable, FrameRange, PixelFormat, ProResProfile, SmallTCompMetadata } from 'remotion';
3
3
  import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
4
4
  import { BrowserLog } from './browser-log';
5
5
  import { ServeUrlOrWebpackBundle } from './legacy-webpack-config';
@@ -37,5 +37,7 @@ export declare type RenderMediaOptions = {
37
37
  timeoutInMilliseconds?: number;
38
38
  chromiumOptions?: ChromiumOptions;
39
39
  scale?: number;
40
+ port?: number | null;
41
+ browserExecutable?: BrowserExecutable;
40
42
  } & ServeUrlOrWebpackBundle;
41
- export declare const renderMedia: ({ parallelism, proResProfile, crf, composition, imageFormat, ffmpegExecutable, inputProps, pixelFormat, codec, envVariables, quality, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, dumpBrowserLogs, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, ...options }: RenderMediaOptions) => Promise<void>;
43
+ 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>;
@@ -23,7 +23,7 @@ const tmp_dir_1 = require("./tmp-dir");
23
23
  const validate_even_dimensions_with_codec_1 = require("./validate-even-dimensions-with-codec");
24
24
  const validate_output_filename_1 = require("./validate-output-filename");
25
25
  const validate_scale_1 = require("./validate-scale");
26
- const renderMedia = async ({ parallelism, proResProfile, crf, composition, imageFormat, ffmpegExecutable, inputProps, pixelFormat, codec, envVariables, quality, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, dumpBrowserLogs, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, ...options }) => {
26
+ const renderMedia = 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
27
  var _a;
28
28
  remotion_1.Internals.validateQuality(quality);
29
29
  if (typeof crf !== 'undefined' && crf !== null) {
@@ -121,6 +121,9 @@ const renderMedia = async ({ parallelism, proResProfile, crf, composition, image
121
121
  timeoutInMilliseconds,
122
122
  chromiumOptions,
123
123
  scale,
124
+ ffmpegExecutable,
125
+ browserExecutable,
126
+ port,
124
127
  });
125
128
  if (stitcherFfmpeg) {
126
129
  await waitForFinish();
@@ -1,5 +1,6 @@
1
1
  import { Browser as PuppeteerBrowser } from 'puppeteer-core';
2
- import { BrowserExecutable, StillImageFormat, TCompMetadata } from 'remotion';
2
+ import { BrowserExecutable, FfmpegExecutable, StillImageFormat, TCompMetadata } from 'remotion';
3
+ import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
3
4
  import { ServeUrlOrWebpackBundle } from './legacy-webpack-config';
4
5
  import { ChromiumOptions } from './open-browser';
5
6
  declare type InnerStillOptions = {
@@ -17,8 +18,12 @@ declare type InnerStillOptions = {
17
18
  timeoutInMilliseconds?: number;
18
19
  chromiumOptions?: ChromiumOptions;
19
20
  scale?: number;
21
+ onDownload?: RenderMediaOnDownload;
22
+ ffmpegExecutable?: FfmpegExecutable;
23
+ };
24
+ declare type RenderStillOptions = InnerStillOptions & ServeUrlOrWebpackBundle & {
25
+ port?: number | null;
20
26
  };
21
- declare type RenderStillOptions = InnerStillOptions & ServeUrlOrWebpackBundle;
22
27
  /**
23
28
  * @description Render a still frame from a composition and returns an image path
24
29
  */
@@ -29,6 +29,7 @@ const remotion_1 = require("remotion");
29
29
  const ensure_output_directory_1 = require("./ensure-output-directory");
30
30
  const handle_javascript_exception_1 = require("./error-handling/handle-javascript-exception");
31
31
  const legacy_webpack_config_1 = require("./legacy-webpack-config");
32
+ const make_assets_download_dir_1 = require("./make-assets-download-dir");
32
33
  const open_browser_1 = require("./open-browser");
33
34
  const prepare_server_1 = require("./prepare-server");
34
35
  const provide_screenshot_1 = require("./provide-screenshot");
@@ -37,7 +38,7 @@ const seek_to_frame_1 = require("./seek-to-frame");
37
38
  const set_props_and_env_1 = require("./set-props-and-env");
38
39
  const validate_puppeteer_timeout_1 = require("./validate-puppeteer-timeout");
39
40
  const validate_scale_1 = require("./validate-scale");
40
- const innerRenderStill = async ({ composition, quality, imageFormat = 'png', serveUrl, puppeteerInstance, dumpBrowserLogs = false, onError, inputProps, envVariables, output, frame = 0, overwrite = true, browserExecutable, timeoutInMilliseconds, chromiumOptions, scale, }) => {
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, }) => {
41
42
  remotion_1.Internals.validateDimension(composition.height, 'height', 'in the `config` object passed to `renderStill()`');
42
43
  remotion_1.Internals.validateDimension(composition.width, 'width', 'in the `config` object passed to `renderStill()`');
43
44
  remotion_1.Internals.validateFps(composition.fps, 'in the `config` object of `renderStill()`');
@@ -103,6 +104,7 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
103
104
  serveUrl,
104
105
  initialFrame: frame,
105
106
  timeoutInMilliseconds,
107
+ proxyPort,
106
108
  });
107
109
  await (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
108
110
  pageFunction: (id) => {
@@ -130,18 +132,35 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
130
132
  /**
131
133
  * @description Render a still frame from a composition and returns an image path
132
134
  */
133
- const renderStill = async (options) => {
135
+ const renderStill = (options) => {
136
+ var _a;
134
137
  const selectedServeUrl = (0, legacy_webpack_config_1.getServeUrlWithFallback)(options);
135
- const { closeServer, serveUrl } = await (0, prepare_server_1.prepareServer)(selectedServeUrl);
138
+ const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
139
+ const onDownload = (_a = options.onDownload) !== null && _a !== void 0 ? _a : (() => () => undefined);
136
140
  return new Promise((resolve, reject) => {
137
- innerRenderStill({
138
- ...options,
139
- serveUrl,
140
- onError: (err) => reject(err),
141
+ var _a, _b;
142
+ const onError = (err) => reject(err);
143
+ let close = null;
144
+ (0, prepare_server_1.prepareServer)({
145
+ webpackConfigOrServeUrl: selectedServeUrl,
146
+ downloadDir,
147
+ onDownload,
148
+ onError,
149
+ ffmpegExecutable: (_a = options.ffmpegExecutable) !== null && _a !== void 0 ? _a : null,
150
+ port: (_b = options.port) !== null && _b !== void 0 ? _b : null,
151
+ })
152
+ .then(({ serveUrl, closeServer, offthreadPort }) => {
153
+ close = closeServer;
154
+ return innerRenderStill({
155
+ ...options,
156
+ serveUrl,
157
+ onError: (err) => reject(err),
158
+ proxyPort: offthreadPort,
159
+ });
141
160
  })
142
161
  .then((res) => resolve(res))
143
162
  .catch((err) => reject(err))
144
- .finally(() => closeServer());
163
+ .finally(() => close === null || close === void 0 ? void 0 : close());
145
164
  });
146
165
  };
147
166
  exports.renderStill = renderStill;
@@ -1,6 +1,12 @@
1
- export declare const serveStatic: (path: string, options?: {
2
- port?: number | undefined;
3
- } | undefined) => Promise<{
1
+ import { FfmpegExecutable } from 'remotion';
2
+ import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
3
+ export declare const serveStatic: (path: string | null, options: {
4
+ port: number | null;
5
+ ffmpegExecutable: FfmpegExecutable;
6
+ downloadDir: string;
7
+ onDownload: RenderMediaOnDownload;
8
+ onError: (err: Error) => void;
9
+ }) => Promise<{
4
10
  port: number;
5
11
  close: () => Promise<void>;
6
12
  }>;
@@ -8,12 +8,28 @@ const http_1 = __importDefault(require("http"));
8
8
  const remotion_1 = require("remotion");
9
9
  const serve_handler_1 = __importDefault(require("serve-handler"));
10
10
  const get_port_1 = require("./get-port");
11
+ const offthread_video_server_1 = require("./offthread-video-server");
11
12
  const serveStatic = async (path, options) => {
12
13
  var _a, _b;
13
14
  const port = await (0, get_port_1.getDesiredPort)((_b = (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : remotion_1.Internals.getServerPort()) !== null && _b !== void 0 ? _b : undefined, 3000, 3100);
15
+ const offthreadRequest = (0, offthread_video_server_1.startOffthreadVideoServer)({
16
+ ffmpegExecutable: options.ffmpegExecutable,
17
+ downloadDir: options.downloadDir,
18
+ onDownload: options.onDownload,
19
+ onError: options.onError,
20
+ });
14
21
  try {
15
22
  const server = http_1.default
16
23
  .createServer((request, response) => {
24
+ var _a;
25
+ if ((_a = request.url) === null || _a === void 0 ? void 0 : _a.startsWith('/proxy')) {
26
+ return offthreadRequest(request, response);
27
+ }
28
+ if (path === null) {
29
+ response.writeHead(404);
30
+ response.end('Server only supports /proxy');
31
+ return;
32
+ }
17
33
  (0, serve_handler_1.default)(request, response, {
18
34
  public: path,
19
35
  directoryListing: false,
@@ -1,9 +1,10 @@
1
1
  import { Page } from 'puppeteer-core';
2
- export declare const setPropsAndEnv: ({ inputProps, envVariables, page, serveUrl, initialFrame, timeoutInMilliseconds, }: {
2
+ export declare const setPropsAndEnv: ({ inputProps, envVariables, page, serveUrl, initialFrame, timeoutInMilliseconds, proxyPort, }: {
3
3
  inputProps: unknown;
4
4
  envVariables: Record<string, string> | undefined;
5
5
  page: Page;
6
6
  serveUrl: string;
7
7
  initialFrame: number;
8
8
  timeoutInMilliseconds: number | undefined;
9
+ proxyPort: number;
9
10
  }) => 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, }) => {
8
+ const setPropsAndEnv = async ({ inputProps, envVariables, page, serveUrl, initialFrame, timeoutInMilliseconds, proxyPort, }) => {
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);
@@ -27,6 +27,9 @@ const setPropsAndEnv = async ({ inputProps, envVariables, page, serveUrl, initia
27
27
  await page.evaluateOnNewDocument((key) => {
28
28
  window.remotion_initialFrame = key;
29
29
  }, [initialFrame]);
30
+ await page.evaluateOnNewDocument((port) => {
31
+ window.remotion_proxyPort = port;
32
+ }, [proxyPort]);
30
33
  const pageRes = await page.goto(urlToVisit);
31
34
  const status = pageRes.status();
32
35
  if (status !== 200 &&
@@ -55,8 +58,8 @@ const setPropsAndEnv = async ({ inputProps, envVariables, page, serveUrl, initia
55
58
  frame: null,
56
59
  page,
57
60
  });
58
- if (siteVersion !== '2') {
59
- throw new Error(`Incompatible site: When visiting ${urlToVisit}, a bundle was found, but one that is not compatible with this version of Remotion. The bundle format changed in versions from March 2022 onwards. To resolve this error, please bundle and deploy again.`);
61
+ if (siteVersion !== '3') {
62
+ throw new Error(`Incompatible site: When visiting ${urlToVisit}, a bundle was found, but one that is not compatible with this version of Remotion. The bundle format changed in version 3.0.11. To resolve this error, please bundle and deploy again.`);
60
63
  }
61
64
  };
62
65
  exports.setPropsAndEnv = setPropsAndEnv;
@@ -160,6 +160,7 @@ const spawnFfmpeg = async (options) => {
160
160
  pixelFormat === 'yuva420p' ? ['-auto-alt-ref', '0'] : null,
161
161
  ['-b:v', '1M'],
162
162
  ]),
163
+ codec === 'h264' ? ['-movflags', 'faststart'] : null,
163
164
  audioCodecName ? ['-c:a', audioCodecName] : null,
164
165
  // Ignore metadata that may come from remote media
165
166
  ['-map_metadata', '-1'],
@@ -11,6 +11,7 @@ const stringifyFfmpegFilter = ({ trimLeft, trimRight, channels, startInVideo, vo
11
11
  volume,
12
12
  startInVideo,
13
13
  fps,
14
+ trimLeft,
14
15
  });
15
16
  // Avoid setting filters if possible, as combining them can create noise
16
17
  const chunkLength = durationInFrames / fps;
@@ -37,6 +38,8 @@ const stringifyFfmpegFilter = ({ trimLeft, trimRight, channels, startInVideo, vo
37
38
  .fill((startInVideoSeconds * 1000).toFixed(0))
38
39
  .join('|')}`,
39
40
  // set the volume if needed
41
+ // The timings for volume must include whatever is in atrim, unless the volume
42
+ // filter gets applied before atrim
40
43
  volumeFilter.value === '1'
41
44
  ? null
42
45
  : `volume=${volumeFilter.value}:eval=${volumeFilter.eval}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/renderer",
3
- "version": "4.0.0-preload.13+f7b159495",
3
+ "version": "4.0.0-preload.17+14cd6033f",
4
4
  "description": "Renderer for Remotion",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,10 +20,9 @@
20
20
  "url": "https://github.com/remotion-dev/remotion/issues"
21
21
  },
22
22
  "dependencies": {
23
- "@remotion/bundler": "4.0.0-preload.13+f7b159495",
24
23
  "execa": "5.1.1",
25
24
  "puppeteer-core": "13.5.1",
26
- "remotion": "4.0.0-preload.13+f7b159495",
25
+ "remotion": "4.0.0-preload.17+14cd6033f",
27
26
  "serve-handler": "6.1.3",
28
27
  "source-map": "^0.8.0-beta.0"
29
28
  },
@@ -60,5 +59,5 @@
60
59
  "publishConfig": {
61
60
  "access": "public"
62
61
  },
63
- "gitHead": "f7b1594951e881444050f2f9d4af8d442bbec942"
62
+ "gitHead": "14cd6033f4ad0fa75af142d54a4cd1a23aff3f21"
64
63
  }