@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.
- package/dist/assets/download-and-map-assets-to-file.d.ts +6 -0
- package/dist/assets/download-and-map-assets-to-file.js +22 -12
- package/dist/assets/ffmpeg-volume-expression.d.ts +2 -1
- package/dist/assets/ffmpeg-volume-expression.js +15 -12
- package/dist/combine-videos.js +2 -0
- package/dist/extract-frame-from-video.d.ts +17 -0
- package/dist/extract-frame-from-video.js +132 -0
- package/dist/frame-to-ffmpeg-timestamp.d.ts +1 -0
- package/dist/frame-to-ffmpeg-timestamp.js +8 -0
- package/dist/get-compositions.d.ts +3 -1
- package/dist/get-compositions.js +21 -5
- package/dist/index.d.ts +7 -7
- package/dist/index.js +0 -2
- package/dist/offthread-video-server.d.ts +13 -0
- package/dist/offthread-video-server.js +64 -0
- package/dist/prepare-server.d.ts +12 -2
- package/dist/prepare-server.js +22 -5
- package/dist/prespawn-ffmpeg.js +7 -9
- package/dist/render-frames.d.ts +3 -1
- package/dist/render-frames.js +33 -15
- package/dist/render-media.d.ts +4 -2
- package/dist/render-media.js +4 -1
- package/dist/render-still.d.ts +7 -2
- package/dist/render-still.js +27 -8
- package/dist/serve-static.d.ts +9 -3
- package/dist/serve-static.js +16 -0
- package/dist/set-props-and-env.d.ts +2 -1
- package/dist/set-props-and-env.js +6 -3
- package/dist/stitch-frames-to-video.js +1 -0
- package/dist/stringify-ffmpeg-filter.js +3 -0
- package/package.json +3 -4
|
@@ -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
|
-
|
|
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] =
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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}'`,
|
package/dist/combine-videos.js
CHANGED
|
@@ -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 {};
|
package/dist/get-compositions.js
CHANGED
|
@@ -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
|
|
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
|
|
67
|
+
onError,
|
|
64
68
|
});
|
|
65
|
-
|
|
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
|
-
|
|
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
|
|
34
|
-
|
|
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;
|
package/dist/prepare-server.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
|
|
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<
|
|
12
|
+
closeServer: () => Promise<unknown>;
|
|
13
|
+
offthreadPort: number;
|
|
4
14
|
}>;
|
package/dist/prepare-server.js
CHANGED
|
@@ -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: () =>
|
|
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: () =>
|
|
16
|
-
|
|
29
|
+
closeServer: () => {
|
|
30
|
+
return close();
|
|
31
|
+
},
|
|
32
|
+
serveUrl: `http://localhost:${serverPort}`,
|
|
33
|
+
offthreadPort: serverPort,
|
|
17
34
|
});
|
|
18
35
|
};
|
|
19
36
|
exports.prepareServer = prepareServer;
|
package/dist/prespawn-ffmpeg.js
CHANGED
|
@@ -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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
];
|
package/dist/render-frames.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/render-frames.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
260
|
+
cleanup === null || cleanup === void 0 ? void 0 : cleanup();
|
|
243
261
|
});
|
|
244
262
|
});
|
|
245
263
|
};
|
package/dist/render-media.d.ts
CHANGED
|
@@ -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>;
|
package/dist/render-media.js
CHANGED
|
@@ -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();
|
package/dist/render-still.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/render-still.js
CHANGED
|
@@ -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 =
|
|
135
|
+
const renderStill = (options) => {
|
|
136
|
+
var _a;
|
|
134
137
|
const selectedServeUrl = (0, legacy_webpack_config_1.getServeUrlWithFallback)(options);
|
|
135
|
-
const
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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(() =>
|
|
163
|
+
.finally(() => close === null || close === void 0 ? void 0 : close());
|
|
145
164
|
});
|
|
146
165
|
};
|
|
147
166
|
exports.renderStill = renderStill;
|
package/dist/serve-static.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
}>;
|
package/dist/serve-static.js
CHANGED
|
@@ -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 !== '
|
|
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
|
|
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.
|
|
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.
|
|
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": "
|
|
62
|
+
"gitHead": "14cd6033f4ad0fa75af142d54a4cd1a23aff3f21"
|
|
64
63
|
}
|