@remotion/renderer 3.0.16 → 3.0.19
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/cleanup-assets.d.ts +2 -0
- package/dist/assets/cleanup-assets.js +2 -0
- package/dist/assets/get-audio-channels.d.ts +2 -1
- package/dist/assets/get-audio-channels.js +2 -2
- package/dist/calculate-ffmpeg-filters.js +2 -2
- package/dist/combine-videos.js +3 -0
- package/dist/ensure-faststart.d.ts +1 -0
- package/dist/ensure-faststart.js +14 -0
- package/dist/extract-frame-from-video.d.ts +4 -6
- package/dist/extract-frame-from-video.js +104 -31
- package/dist/faststart/atom.d.ts +35 -0
- package/dist/faststart/atom.js +138 -0
- package/dist/faststart/index.d.ts +0 -0
- package/dist/faststart/index.js +1 -0
- package/dist/faststart/options.d.ts +6 -0
- package/dist/faststart/options.js +2 -0
- package/dist/faststart/qt-faststart.d.ts +18 -0
- package/dist/faststart/qt-faststart.js +66 -0
- package/dist/faststart/update-chunk-offsets.d.ts +10 -0
- package/dist/faststart/update-chunk-offsets.js +114 -0
- package/dist/faststart/util.d.ts +9 -0
- package/dist/faststart/util.js +34 -0
- package/dist/get-compositions.d.ts +1 -0
- package/dist/get-compositions.js +3 -2
- package/dist/get-duration-of-asset.d.ts +7 -0
- package/dist/get-duration-of-asset.js +36 -0
- package/dist/get-port.js +26 -24
- package/dist/index.d.ts +1 -0
- package/dist/is-beyond-last-frame.d.ts +2 -0
- package/dist/is-beyond-last-frame.js +12 -0
- package/dist/last-frame-from-video-cache.d.ts +13 -0
- package/dist/last-frame-from-video-cache.js +52 -0
- package/dist/make-assets-download-dir.js +6 -1
- package/dist/offthread-video-server.d.ts +2 -1
- package/dist/offthread-video-server.js +3 -1
- package/dist/prepare-server.d.ts +2 -1
- package/dist/prepare-server.js +3 -1
- package/dist/preprocess-audio-track.d.ts +1 -0
- package/dist/preprocess-audio-track.js +2 -2
- package/dist/provide-screenshot.js +1 -1
- package/dist/render-frames.d.ts +1 -0
- package/dist/render-frames.js +6 -3
- package/dist/render-gif.d.ts +2 -0
- package/dist/render-gif.js +242 -0
- package/dist/render-media.d.ts +7 -1
- package/dist/render-media.js +10 -1
- package/dist/render-still.d.ts +4 -1
- package/dist/render-still.js +7 -4
- package/dist/serve-handler/glob-slash.d.ts +1 -0
- package/dist/serve-handler/glob-slash.js +12 -0
- package/dist/serve-handler/index.d.ts +4 -0
- package/dist/serve-handler/index.js +212 -0
- package/dist/serve-handler/is-path-inside.d.ts +1 -0
- package/dist/serve-handler/is-path-inside.js +27 -0
- package/dist/serve-handler/range-parser.d.ts +13 -0
- package/dist/serve-handler/range-parser.js +57 -0
- package/dist/serve-static.d.ts +1 -0
- package/dist/serve-static.js +3 -4
- package/dist/stitch-frames-to-gif.d.ts +8 -0
- package/dist/stitch-frames-to-gif.js +128 -0
- package/dist/stitch-frames-to-video.d.ts +1 -0
- package/dist/stitch-frames-to-video.js +17 -10
- package/dist/validate-fps-for-gif.d.ts +2 -0
- package/dist/validate-fps-for-gif.js +9 -0
- package/package.json +5 -5
|
@@ -0,0 +1,242 @@
|
|
|
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.renderGif = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const remotion_1 = require("remotion");
|
|
11
|
+
const can_use_parallel_encoding_1 = require("./can-use-parallel-encoding");
|
|
12
|
+
const ensure_frames_in_order_1 = require("./ensure-frames-in-order");
|
|
13
|
+
const ensure_output_directory_1 = require("./ensure-output-directory");
|
|
14
|
+
const get_duration_from_frame_range_1 = require("./get-duration-from-frame-range");
|
|
15
|
+
const get_extension_from_codec_1 = require("./get-extension-from-codec");
|
|
16
|
+
const get_extension_of_filename_1 = require("./get-extension-of-filename");
|
|
17
|
+
const get_frame_to_render_1 = require("./get-frame-to-render");
|
|
18
|
+
const legacy_webpack_config_1 = require("./legacy-webpack-config");
|
|
19
|
+
const make_cancel_signal_1 = require("./make-cancel-signal");
|
|
20
|
+
const prespawn_ffmpeg_1 = require("./prespawn-ffmpeg");
|
|
21
|
+
const render_frames_1 = require("./render-frames");
|
|
22
|
+
const stitch_frames_to_gif_1 = require("./stitch-frames-to-gif");
|
|
23
|
+
const tmp_dir_1 = require("./tmp-dir");
|
|
24
|
+
const validate_even_dimensions_with_codec_1 = require("./validate-even-dimensions-with-codec");
|
|
25
|
+
const validate_output_filename_1 = require("./validate-output-filename");
|
|
26
|
+
const validate_scale_1 = require("./validate-scale");
|
|
27
|
+
const renderGif = ({ parallelism, proResProfile, crf, composition, imageFormat, ffmpegExecutable, inputProps, pixelFormat, codec, envVariables, quality, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, loop, skipNFrames, dumpBrowserLogs, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, browserExecutable, port, cancelSignal, ...options }) => {
|
|
28
|
+
remotion_1.Internals.validateQuality(quality);
|
|
29
|
+
if (typeof crf !== 'undefined' && crf !== null) {
|
|
30
|
+
remotion_1.Internals.validateSelectedCrfAndCodecCombination(crf, codec);
|
|
31
|
+
}
|
|
32
|
+
(0, validate_output_filename_1.validateOutputFilename)(codec, (0, get_extension_of_filename_1.getExtensionOfFilename)(outputLocation));
|
|
33
|
+
(0, validate_scale_1.validateScale)(scale);
|
|
34
|
+
const serveUrl = (0, legacy_webpack_config_1.getServeUrlWithFallback)(options);
|
|
35
|
+
let stitchStage = 'encoding';
|
|
36
|
+
let stitcherFfmpeg;
|
|
37
|
+
let preStitcher = null;
|
|
38
|
+
let encodedFrames = 0;
|
|
39
|
+
let renderedFrames = 0;
|
|
40
|
+
let renderedDoneIn = null;
|
|
41
|
+
let encodedDoneIn = null;
|
|
42
|
+
let cancelled = false;
|
|
43
|
+
const renderStart = Date.now();
|
|
44
|
+
const tmpdir = (0, tmp_dir_1.tmpDir)('pre-encode');
|
|
45
|
+
const parallelEncoding = (0, can_use_parallel_encoding_1.canUseParallelEncoding)(codec);
|
|
46
|
+
const actualImageFormat = imageFormat !== null && imageFormat !== void 0 ? imageFormat : 'jpeg';
|
|
47
|
+
const preEncodedFileLocation = parallelEncoding
|
|
48
|
+
? path_1.default.join(tmpdir, 'pre-encode.' + (0, get_extension_from_codec_1.getFileExtensionFromCodec)(codec, 'chunk'))
|
|
49
|
+
: null;
|
|
50
|
+
const outputDir = parallelEncoding
|
|
51
|
+
? null
|
|
52
|
+
: fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'react-motion-render'));
|
|
53
|
+
(0, validate_even_dimensions_with_codec_1.validateEvenDimensionsWithCodec)({
|
|
54
|
+
codec,
|
|
55
|
+
height: composition.height,
|
|
56
|
+
scale: scale !== null && scale !== void 0 ? scale : 1,
|
|
57
|
+
width: composition.width,
|
|
58
|
+
});
|
|
59
|
+
const callUpdate = () => {
|
|
60
|
+
onProgress === null || onProgress === void 0 ? void 0 : onProgress({
|
|
61
|
+
encodedDoneIn,
|
|
62
|
+
encodedFrames,
|
|
63
|
+
renderedDoneIn,
|
|
64
|
+
renderedFrames,
|
|
65
|
+
stitchStage,
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const realFrameRange = (0, get_frame_to_render_1.getRealFrameRange)(composition.durationInFrames, frameRange !== null && frameRange !== void 0 ? frameRange : null);
|
|
69
|
+
const cancelRenderFrames = (0, make_cancel_signal_1.makeCancelSignal)();
|
|
70
|
+
const cancelPrestitcher = (0, make_cancel_signal_1.makeCancelSignal)();
|
|
71
|
+
const cancelStitcher = (0, make_cancel_signal_1.makeCancelSignal)();
|
|
72
|
+
cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
|
|
73
|
+
cancelRenderFrames.cancel();
|
|
74
|
+
});
|
|
75
|
+
const { waitForRightTimeOfFrameToBeInserted, setFrameToStitch, waitForFinish } = (0, ensure_frames_in_order_1.ensureFramesInOrder)(realFrameRange);
|
|
76
|
+
const actualGifFps = Math.floor(composition.fps / (skipNFrames + 1));
|
|
77
|
+
const createPrestitcherIfNecessary = async () => {
|
|
78
|
+
if (preEncodedFileLocation) {
|
|
79
|
+
preStitcher = await (0, prespawn_ffmpeg_1.prespawnFfmpeg)({
|
|
80
|
+
width: composition.width * (scale !== null && scale !== void 0 ? scale : 1),
|
|
81
|
+
height: composition.height * (scale !== null && scale !== void 0 ? scale : 1),
|
|
82
|
+
fps: actualGifFps,
|
|
83
|
+
outputLocation: preEncodedFileLocation,
|
|
84
|
+
pixelFormat,
|
|
85
|
+
codec,
|
|
86
|
+
proResProfile,
|
|
87
|
+
crf,
|
|
88
|
+
onProgress: (frame) => {
|
|
89
|
+
encodedFrames = frame;
|
|
90
|
+
callUpdate();
|
|
91
|
+
},
|
|
92
|
+
verbose: remotion_1.Internals.Logging.isEqualOrBelowLogLevel(remotion_1.Internals.Logging.getLogLevel(), 'verbose'),
|
|
93
|
+
ffmpegExecutable,
|
|
94
|
+
imageFormat: actualImageFormat,
|
|
95
|
+
signal: cancelPrestitcher.cancelSignal,
|
|
96
|
+
});
|
|
97
|
+
stitcherFfmpeg = preStitcher.task;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const waitForPrestitcherIfNecessary = async () => {
|
|
101
|
+
var _a;
|
|
102
|
+
if (stitcherFfmpeg) {
|
|
103
|
+
await waitForFinish();
|
|
104
|
+
(_a = stitcherFfmpeg === null || stitcherFfmpeg === void 0 ? void 0 : stitcherFfmpeg.stdin) === null || _a === void 0 ? void 0 : _a.end();
|
|
105
|
+
try {
|
|
106
|
+
await stitcherFfmpeg;
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
throw new Error(preStitcher === null || preStitcher === void 0 ? void 0 : preStitcher.getLogs());
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const happyPath = createPrestitcherIfNecessary()
|
|
114
|
+
.then(() => {
|
|
115
|
+
const renderFramesProc = (0, render_frames_1.renderFrames)({
|
|
116
|
+
config: composition,
|
|
117
|
+
onFrameUpdate: (frame) => {
|
|
118
|
+
stitchStage = 'gif';
|
|
119
|
+
renderedFrames = frame;
|
|
120
|
+
callUpdate();
|
|
121
|
+
},
|
|
122
|
+
parallelism,
|
|
123
|
+
outputDir,
|
|
124
|
+
skipNFrames,
|
|
125
|
+
onStart: (data) => {
|
|
126
|
+
renderedFrames = 0;
|
|
127
|
+
callUpdate();
|
|
128
|
+
onStart === null || onStart === void 0 ? void 0 : onStart(data);
|
|
129
|
+
},
|
|
130
|
+
inputProps,
|
|
131
|
+
envVariables,
|
|
132
|
+
imageFormat: actualImageFormat,
|
|
133
|
+
quality,
|
|
134
|
+
frameRange: frameRange !== null && frameRange !== void 0 ? frameRange : null,
|
|
135
|
+
puppeteerInstance,
|
|
136
|
+
onFrameBuffer: parallelEncoding
|
|
137
|
+
? async (buffer, frame) => {
|
|
138
|
+
var _a;
|
|
139
|
+
await waitForRightTimeOfFrameToBeInserted(frame);
|
|
140
|
+
if (cancelled) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
(_a = stitcherFfmpeg === null || stitcherFfmpeg === void 0 ? void 0 : stitcherFfmpeg.stdin) === null || _a === void 0 ? void 0 : _a.write(buffer);
|
|
144
|
+
setFrameToStitch(frame + 1);
|
|
145
|
+
}
|
|
146
|
+
: undefined,
|
|
147
|
+
serveUrl,
|
|
148
|
+
dumpBrowserLogs,
|
|
149
|
+
onBrowserLog,
|
|
150
|
+
onDownload,
|
|
151
|
+
timeoutInMilliseconds,
|
|
152
|
+
chromiumOptions,
|
|
153
|
+
scale,
|
|
154
|
+
ffmpegExecutable,
|
|
155
|
+
browserExecutable,
|
|
156
|
+
port,
|
|
157
|
+
cancelSignal: cancelRenderFrames.cancelSignal,
|
|
158
|
+
});
|
|
159
|
+
return renderFramesProc;
|
|
160
|
+
})
|
|
161
|
+
.then((renderFramesReturn) => {
|
|
162
|
+
return Promise.all([renderFramesReturn, waitForPrestitcherIfNecessary()]);
|
|
163
|
+
})
|
|
164
|
+
.then(([{ assetsInfo }]) => {
|
|
165
|
+
renderedDoneIn = Date.now() - renderStart;
|
|
166
|
+
callUpdate();
|
|
167
|
+
(0, ensure_output_directory_1.ensureOutputDirectory)(outputLocation);
|
|
168
|
+
const stitchStart = Date.now();
|
|
169
|
+
return Promise.all([
|
|
170
|
+
(0, stitch_frames_to_gif_1.stitchFramesToGif)({
|
|
171
|
+
width: composition.width * (scale !== null && scale !== void 0 ? scale : 1),
|
|
172
|
+
height: composition.height * (scale !== null && scale !== void 0 ? scale : 1),
|
|
173
|
+
fps: actualGifFps,
|
|
174
|
+
outputLocation,
|
|
175
|
+
internalOptions: {
|
|
176
|
+
preEncodedFileLocation,
|
|
177
|
+
imageFormat: actualImageFormat,
|
|
178
|
+
},
|
|
179
|
+
force: overwrite !== null && overwrite !== void 0 ? overwrite : remotion_1.Internals.DEFAULT_OVERWRITE,
|
|
180
|
+
pixelFormat,
|
|
181
|
+
codec,
|
|
182
|
+
proResProfile,
|
|
183
|
+
crf,
|
|
184
|
+
loop,
|
|
185
|
+
assetsInfo,
|
|
186
|
+
ffmpegExecutable,
|
|
187
|
+
onProgress: (frame) => {
|
|
188
|
+
stitchStage = 'gif';
|
|
189
|
+
encodedFrames = frame;
|
|
190
|
+
callUpdate();
|
|
191
|
+
},
|
|
192
|
+
verbose: remotion_1.Internals.Logging.isEqualOrBelowLogLevel(remotion_1.Internals.Logging.getLogLevel(), 'verbose'),
|
|
193
|
+
dir: outputDir !== null && outputDir !== void 0 ? outputDir : undefined,
|
|
194
|
+
cancelSignal: cancelStitcher.cancelSignal,
|
|
195
|
+
}),
|
|
196
|
+
stitchStart,
|
|
197
|
+
]);
|
|
198
|
+
})
|
|
199
|
+
.then(([, stitchStart]) => {
|
|
200
|
+
encodedFrames = (0, get_duration_from_frame_range_1.getDurationFromFrameRange)(frameRange !== null && frameRange !== void 0 ? frameRange : null, composition.durationInFrames, skipNFrames);
|
|
201
|
+
encodedDoneIn = Date.now() - stitchStart;
|
|
202
|
+
callUpdate();
|
|
203
|
+
})
|
|
204
|
+
.catch((err) => {
|
|
205
|
+
/**
|
|
206
|
+
* When an error is thrown in renderFrames(...) (e.g., when delayRender() is used incorrectly), fs.unlinkSync(...) throws an error that the file is locked because ffmpeg is still running, and renderMedia returns it.
|
|
207
|
+
* Therefore we first kill the FFMPEG process before deleting the file
|
|
208
|
+
*/
|
|
209
|
+
cancelled = true;
|
|
210
|
+
cancelRenderFrames.cancel();
|
|
211
|
+
cancelStitcher.cancel();
|
|
212
|
+
cancelPrestitcher.cancel();
|
|
213
|
+
if (stitcherFfmpeg !== undefined && stitcherFfmpeg.exitCode === null) {
|
|
214
|
+
const promise = new Promise((resolve) => {
|
|
215
|
+
setTimeout(() => {
|
|
216
|
+
resolve();
|
|
217
|
+
}, 2000);
|
|
218
|
+
stitcherFfmpeg.on('close', resolve);
|
|
219
|
+
});
|
|
220
|
+
stitcherFfmpeg.kill();
|
|
221
|
+
return promise.then(() => {
|
|
222
|
+
throw err;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
throw err;
|
|
226
|
+
})
|
|
227
|
+
.finally(() => {
|
|
228
|
+
if (preEncodedFileLocation !== null &&
|
|
229
|
+
fs_1.default.existsSync(preEncodedFileLocation)) {
|
|
230
|
+
fs_1.default.unlinkSync(preEncodedFileLocation);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
return Promise.race([
|
|
234
|
+
happyPath,
|
|
235
|
+
new Promise((_resolve, reject) => {
|
|
236
|
+
cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
|
|
237
|
+
reject(new Error('renderMedia() got cancelled'));
|
|
238
|
+
});
|
|
239
|
+
}),
|
|
240
|
+
]);
|
|
241
|
+
};
|
|
242
|
+
exports.renderGif = renderGif;
|
package/dist/render-media.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export declare type RenderMediaOptions = {
|
|
|
23
23
|
crf?: number | null;
|
|
24
24
|
imageFormat?: 'png' | 'jpeg' | 'none';
|
|
25
25
|
ffmpegExecutable?: FfmpegExecutable;
|
|
26
|
+
ffprobeExecutable?: FfmpegExecutable;
|
|
26
27
|
pixelFormat?: PixelFormat;
|
|
27
28
|
envVariables?: Record<string, string>;
|
|
28
29
|
quality?: number;
|
|
@@ -42,4 +43,9 @@ export declare type RenderMediaOptions = {
|
|
|
42
43
|
cancelSignal?: CancelSignal;
|
|
43
44
|
browserExecutable?: BrowserExecutable;
|
|
44
45
|
} & ServeUrlOrWebpackBundle;
|
|
45
|
-
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @description Render a video from a composition
|
|
49
|
+
* @link https://www.remotion.dev/docs/renderer/render-media
|
|
50
|
+
*/
|
|
51
|
+
export declare const renderMedia: ({ parallelism, proResProfile, crf, composition, imageFormat, ffmpegExecutable, ffprobeExecutable, inputProps, pixelFormat, codec, envVariables, quality, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, dumpBrowserLogs, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, browserExecutable, port, cancelSignal, ...options }: RenderMediaOptions) => Promise<void>;
|
package/dist/render-media.js
CHANGED
|
@@ -24,7 +24,12 @@ const tmp_dir_1 = require("./tmp-dir");
|
|
|
24
24
|
const validate_even_dimensions_with_codec_1 = require("./validate-even-dimensions-with-codec");
|
|
25
25
|
const validate_output_filename_1 = require("./validate-output-filename");
|
|
26
26
|
const validate_scale_1 = require("./validate-scale");
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
*
|
|
29
|
+
* @description Render a video from a composition
|
|
30
|
+
* @link https://www.remotion.dev/docs/renderer/render-media
|
|
31
|
+
*/
|
|
32
|
+
const renderMedia = ({ parallelism, proResProfile, crf, composition, imageFormat, ffmpegExecutable, ffprobeExecutable, inputProps, pixelFormat, codec, envVariables, quality, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, dumpBrowserLogs, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, browserExecutable, port, cancelSignal, ...options }) => {
|
|
28
33
|
remotion_1.Internals.validateQuality(quality);
|
|
29
34
|
if (typeof crf !== 'undefined' && crf !== null) {
|
|
30
35
|
remotion_1.Internals.validateSelectedCrfAndCodecCombination(crf, codec);
|
|
@@ -137,7 +142,9 @@ const renderMedia = ({ parallelism, proResProfile, crf, composition, imageFormat
|
|
|
137
142
|
if (cancelled) {
|
|
138
143
|
return;
|
|
139
144
|
}
|
|
145
|
+
const id = remotion_1.Internals.perf.startPerfMeasure('piping');
|
|
140
146
|
(_a = stitcherFfmpeg === null || stitcherFfmpeg === void 0 ? void 0 : stitcherFfmpeg.stdin) === null || _a === void 0 ? void 0 : _a.write(buffer);
|
|
147
|
+
remotion_1.Internals.perf.stopPerfMeasure(id);
|
|
141
148
|
setFrameToStitch(frame + 1);
|
|
142
149
|
}
|
|
143
150
|
: undefined,
|
|
@@ -149,6 +156,7 @@ const renderMedia = ({ parallelism, proResProfile, crf, composition, imageFormat
|
|
|
149
156
|
chromiumOptions,
|
|
150
157
|
scale,
|
|
151
158
|
ffmpegExecutable,
|
|
159
|
+
ffprobeExecutable,
|
|
152
160
|
browserExecutable,
|
|
153
161
|
port,
|
|
154
162
|
cancelSignal: cancelRenderFrames.cancelSignal,
|
|
@@ -180,6 +188,7 @@ const renderMedia = ({ parallelism, proResProfile, crf, composition, imageFormat
|
|
|
180
188
|
crf,
|
|
181
189
|
assetsInfo,
|
|
182
190
|
ffmpegExecutable,
|
|
191
|
+
ffprobeExecutable,
|
|
183
192
|
onProgress: (frame) => {
|
|
184
193
|
stitchStage = 'muxing';
|
|
185
194
|
encodedFrames = frame;
|
package/dist/render-still.d.ts
CHANGED
|
@@ -22,12 +22,15 @@ declare type InnerStillOptions = {
|
|
|
22
22
|
onDownload?: RenderMediaOnDownload;
|
|
23
23
|
cancelSignal?: CancelSignal;
|
|
24
24
|
ffmpegExecutable?: FfmpegExecutable;
|
|
25
|
+
ffprobeExecutable?: FfmpegExecutable;
|
|
25
26
|
};
|
|
26
27
|
declare type RenderStillOptions = InnerStillOptions & ServeUrlOrWebpackBundle & {
|
|
27
28
|
port?: number | null;
|
|
28
29
|
};
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
+
*
|
|
32
|
+
* @description Render a still frame from a composition
|
|
33
|
+
* @link https://www.remotion.dev/docs/renderer/render-still
|
|
31
34
|
*/
|
|
32
35
|
export declare const renderStill: (options: RenderStillOptions) => Promise<void>;
|
|
33
36
|
export {};
|
package/dist/render-still.js
CHANGED
|
@@ -76,7 +76,7 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
|
|
|
76
76
|
forceDeviceScaleFactor: scale !== null && scale !== void 0 ? scale : 1,
|
|
77
77
|
}));
|
|
78
78
|
const page = await browserInstance.newPage();
|
|
79
|
-
page.setViewport({
|
|
79
|
+
await page.setViewport({
|
|
80
80
|
width: composition.width,
|
|
81
81
|
height: composition.height,
|
|
82
82
|
deviceScaleFactor: scale !== null && scale !== void 0 ? scale : 1,
|
|
@@ -138,7 +138,9 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
|
|
|
138
138
|
await cleanup();
|
|
139
139
|
};
|
|
140
140
|
/**
|
|
141
|
-
*
|
|
141
|
+
*
|
|
142
|
+
* @description Render a still frame from a composition
|
|
143
|
+
* @link https://www.remotion.dev/docs/renderer/render-still
|
|
142
144
|
*/
|
|
143
145
|
const renderStill = (options) => {
|
|
144
146
|
var _a;
|
|
@@ -146,7 +148,7 @@ const renderStill = (options) => {
|
|
|
146
148
|
const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
|
|
147
149
|
const onDownload = (_a = options.onDownload) !== null && _a !== void 0 ? _a : (() => () => undefined);
|
|
148
150
|
const happyPath = new Promise((resolve, reject) => {
|
|
149
|
-
var _a, _b;
|
|
151
|
+
var _a, _b, _c;
|
|
150
152
|
const onError = (err) => reject(err);
|
|
151
153
|
let close = null;
|
|
152
154
|
(0, prepare_server_1.prepareServer)({
|
|
@@ -155,7 +157,8 @@ const renderStill = (options) => {
|
|
|
155
157
|
onDownload,
|
|
156
158
|
onError,
|
|
157
159
|
ffmpegExecutable: (_a = options.ffmpegExecutable) !== null && _a !== void 0 ? _a : null,
|
|
158
|
-
|
|
160
|
+
ffprobeExecutable: (_b = options.ffprobeExecutable) !== null && _b !== void 0 ? _b : null,
|
|
161
|
+
port: (_c = options.port) !== null && _c !== void 0 ? _c : null,
|
|
159
162
|
})
|
|
160
163
|
.then(({ serveUrl, closeServer, offthreadPort }) => {
|
|
161
164
|
close = closeServer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const slasher: (value: string) => string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* ! The MIT License (MIT) Copyright (c) 2014 Scott Corgan */
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.slasher = void 0;
|
|
8
|
+
// This is adopted from https://github.com/scottcorgan/glob-slash/
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const normalize = (value) => path_1.default.posix.normalize(path_1.default.posix.join('/', value));
|
|
11
|
+
const slasher = (value) => value.charAt(0) === '!' ? `!${normalize(value.substr(1))}` : normalize(value);
|
|
12
|
+
exports.slasher = slasher;
|
|
@@ -0,0 +1,212 @@
|
|
|
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.serveHandler = void 0;
|
|
7
|
+
// Native
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const url_1 = __importDefault(require("url"));
|
|
11
|
+
// Packages
|
|
12
|
+
const mime_types_1 = __importDefault(require("mime-types"));
|
|
13
|
+
const is_path_inside_1 = require("./is-path-inside");
|
|
14
|
+
const range_parser_1 = require("./range-parser");
|
|
15
|
+
const getHeaders = (absolutePath, stats) => {
|
|
16
|
+
const related = {};
|
|
17
|
+
const { base } = path_1.default.parse(absolutePath);
|
|
18
|
+
let defaultHeaders = {};
|
|
19
|
+
if (stats) {
|
|
20
|
+
defaultHeaders = {
|
|
21
|
+
'Content-Length': String(stats.size),
|
|
22
|
+
'Accept-Ranges': 'bytes',
|
|
23
|
+
};
|
|
24
|
+
defaultHeaders['Last-Modified'] = stats.mtime.toUTCString();
|
|
25
|
+
const contentType = mime_types_1.default.contentType(base);
|
|
26
|
+
if (contentType) {
|
|
27
|
+
defaultHeaders['Content-Type'] = contentType;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const headers = Object.assign(defaultHeaders, related);
|
|
31
|
+
for (const key in headers) {
|
|
32
|
+
if (headers[key] === null) {
|
|
33
|
+
delete headers[key];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return headers;
|
|
37
|
+
};
|
|
38
|
+
const getPossiblePaths = (relativePath, extension) => [
|
|
39
|
+
path_1.default.join(relativePath, `index${extension}`),
|
|
40
|
+
relativePath.endsWith('/')
|
|
41
|
+
? relativePath.replace(/\/$/g, extension)
|
|
42
|
+
: relativePath + extension,
|
|
43
|
+
].filter((item) => path_1.default.basename(item) !== extension);
|
|
44
|
+
const findRelated = async (current, relativePath) => {
|
|
45
|
+
const possible = getPossiblePaths(relativePath, '.html');
|
|
46
|
+
let stats = null;
|
|
47
|
+
for (let index = 0; index < possible.length; index++) {
|
|
48
|
+
const related = possible[index];
|
|
49
|
+
const absolutePath = path_1.default.join(current, related);
|
|
50
|
+
try {
|
|
51
|
+
stats = await fs_1.promises.lstat(absolutePath);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (err.code !== 'ENOENT' &&
|
|
55
|
+
err.code !== 'ENOTDIR') {
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (stats) {
|
|
60
|
+
return {
|
|
61
|
+
stats,
|
|
62
|
+
absolutePath,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
};
|
|
68
|
+
const sendError = (absolutePath, response, spec) => {
|
|
69
|
+
const { message, statusCode } = spec;
|
|
70
|
+
response.statusCode = statusCode;
|
|
71
|
+
const headers = getHeaders(absolutePath, null);
|
|
72
|
+
response.writeHead(statusCode, headers);
|
|
73
|
+
response.setHeader('content-type', 'application/json');
|
|
74
|
+
response.end(JSON.stringify({ statusCode, message }));
|
|
75
|
+
};
|
|
76
|
+
const internalError = (absolutePath, response) => {
|
|
77
|
+
return sendError(absolutePath, response, {
|
|
78
|
+
statusCode: 500,
|
|
79
|
+
code: 'internal_server_error',
|
|
80
|
+
message: 'A server error has occurred',
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
const serveHandler = async (request, response, config) => {
|
|
84
|
+
const cwd = process.cwd();
|
|
85
|
+
const current = path_1.default.resolve(cwd, config.public);
|
|
86
|
+
let relativePath = null;
|
|
87
|
+
try {
|
|
88
|
+
relativePath = decodeURIComponent(url_1.default.parse(request.url).pathname);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
return sendError('/', response, {
|
|
92
|
+
statusCode: 400,
|
|
93
|
+
code: 'bad_request',
|
|
94
|
+
message: 'Bad Request',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
let absolutePath = path_1.default.join(current, relativePath);
|
|
98
|
+
// Prevent path traversal vulnerabilities. We could do this
|
|
99
|
+
// by ourselves, but using the package covers all the edge cases.
|
|
100
|
+
if (!(0, is_path_inside_1.isPathInside)(absolutePath, current)) {
|
|
101
|
+
return sendError(absolutePath, response, {
|
|
102
|
+
statusCode: 400,
|
|
103
|
+
code: 'bad_request',
|
|
104
|
+
message: 'Bad Request',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
let stats = null;
|
|
108
|
+
// It's extremely important that we're doing multiple stat calls. This one
|
|
109
|
+
// right here could technically be removed, but then the program
|
|
110
|
+
// would be slower. Because for directories, we always want to see if a related file
|
|
111
|
+
// exists and then (after that), fetch the directory itself if no
|
|
112
|
+
// related file was found. However (for files, of which most have extensions), we should
|
|
113
|
+
// always stat right away.
|
|
114
|
+
//
|
|
115
|
+
// When simulating a file system without directory indexes, calculating whether a
|
|
116
|
+
// directory exists requires loading all the file paths and then checking if
|
|
117
|
+
// one of them includes the path of the directory. As that's a very
|
|
118
|
+
// performance-expensive thing to do, we need to ensure it's not happening if not really necessary.
|
|
119
|
+
if (path_1.default.extname(relativePath) !== '') {
|
|
120
|
+
try {
|
|
121
|
+
stats = await fs_1.promises.lstat(absolutePath);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
if (err.code !== 'ENOENT' &&
|
|
125
|
+
err.code !== 'ENOTDIR') {
|
|
126
|
+
return internalError(absolutePath, response);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!stats) {
|
|
131
|
+
try {
|
|
132
|
+
const related = await findRelated(current, relativePath);
|
|
133
|
+
if (related) {
|
|
134
|
+
({ stats, absolutePath } = related);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
if (err.code !== 'ENOENT' &&
|
|
139
|
+
err.code !== 'ENOTDIR') {
|
|
140
|
+
return internalError(absolutePath, response);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
stats = await fs_1.promises.lstat(absolutePath);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
if (err.code !== 'ENOENT' &&
|
|
148
|
+
err.code !== 'ENOTDIR') {
|
|
149
|
+
return internalError(absolutePath, response);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (stats === null || stats === void 0 ? void 0 : stats.isDirectory()) {
|
|
154
|
+
const directory = null;
|
|
155
|
+
const singleFile = null;
|
|
156
|
+
if (directory) {
|
|
157
|
+
const contentType = 'text/html; charset=utf-8';
|
|
158
|
+
response.statusCode = 200;
|
|
159
|
+
response.setHeader('Content-Type', contentType);
|
|
160
|
+
response.end('Is a directory');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (!singleFile) {
|
|
164
|
+
// The directory listing is disabled, so we want to
|
|
165
|
+
// render a 404 error.
|
|
166
|
+
stats = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const isSymLink = stats === null || stats === void 0 ? void 0 : stats.isSymbolicLink();
|
|
170
|
+
// There are two scenarios in which we want to reply with
|
|
171
|
+
// a 404 error: Either the path does not exist, or it is a
|
|
172
|
+
// symlink while the `symlinks` option is disabled (which it is by default).
|
|
173
|
+
if (!stats || isSymLink) {
|
|
174
|
+
// allow for custom 404 handling
|
|
175
|
+
return sendError(absolutePath, response, {
|
|
176
|
+
statusCode: 404,
|
|
177
|
+
code: 'not_found',
|
|
178
|
+
message: 'The requested path could not be found',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
let streamOpts = null;
|
|
182
|
+
if (request.headers.range && stats.size) {
|
|
183
|
+
const range = (0, range_parser_1.rangeParser)(stats.size, request.headers.range);
|
|
184
|
+
if (typeof range === 'object' && range.type === 'bytes') {
|
|
185
|
+
const { start, end } = range.ranges[0];
|
|
186
|
+
streamOpts = {
|
|
187
|
+
start,
|
|
188
|
+
end,
|
|
189
|
+
};
|
|
190
|
+
response.statusCode = 206;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
response.statusCode = 416;
|
|
194
|
+
response.setHeader('Content-Range', `bytes */${stats.size}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
let stream = null;
|
|
198
|
+
try {
|
|
199
|
+
stream = (0, fs_1.createReadStream)(absolutePath, streamOpts !== null && streamOpts !== void 0 ? streamOpts : {});
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
return internalError(absolutePath, response);
|
|
203
|
+
}
|
|
204
|
+
const headers = getHeaders(absolutePath, stats);
|
|
205
|
+
if (streamOpts !== null) {
|
|
206
|
+
headers['Content-Range'] = `bytes ${streamOpts.start}-${streamOpts.end}/${stats.size}`;
|
|
207
|
+
headers['Content-Length'] = String(streamOpts.end - streamOpts.start + 1);
|
|
208
|
+
}
|
|
209
|
+
response.writeHead(response.statusCode || 200, headers);
|
|
210
|
+
stream.pipe(response);
|
|
211
|
+
};
|
|
212
|
+
exports.serveHandler = serveHandler;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isPathInside: (thePath: string, potentialParent: string) => boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
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.isPathInside = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const isPathInside = function (thePath, potentialParent) {
|
|
9
|
+
// For inside-directory checking, we want to allow trailing slashes, so normalize.
|
|
10
|
+
thePath = stripTrailingSep(thePath);
|
|
11
|
+
potentialParent = stripTrailingSep(potentialParent);
|
|
12
|
+
// Node treats only Windows as case-insensitive in its path module; we follow those conventions.
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
thePath = thePath.toLowerCase();
|
|
15
|
+
potentialParent = potentialParent.toLowerCase();
|
|
16
|
+
}
|
|
17
|
+
return (thePath.lastIndexOf(potentialParent, 0) === 0 &&
|
|
18
|
+
(thePath[potentialParent.length] === path_1.default.sep ||
|
|
19
|
+
thePath[potentialParent.length] === undefined));
|
|
20
|
+
};
|
|
21
|
+
exports.isPathInside = isPathInside;
|
|
22
|
+
function stripTrailingSep(thePath) {
|
|
23
|
+
if (thePath[thePath.length - 1] === path_1.default.sep) {
|
|
24
|
+
return thePath.slice(0, -1);
|
|
25
|
+
}
|
|
26
|
+
return thePath;
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* range-parser
|
|
3
|
+
* Copyright(c) 2012-2014 TJ Holowaychuk
|
|
4
|
+
* Copyright(c) 2015-2016 Douglas Christopher Wilson
|
|
5
|
+
* MIT Licensed
|
|
6
|
+
*/
|
|
7
|
+
export declare const rangeParser: (size: number, str: string) => -1 | {
|
|
8
|
+
type: string;
|
|
9
|
+
ranges: {
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
}[];
|
|
13
|
+
} | -2;
|