@remotion/renderer 3.0.18 → 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.
Files changed (34) hide show
  1. package/dist/assets/cleanup-assets.d.ts +2 -0
  2. package/dist/assets/cleanup-assets.js +2 -0
  3. package/dist/calculate-ffmpeg-filters.js +2 -2
  4. package/dist/create-ffmpeg-complex-filter.d.ts +1 -4
  5. package/dist/ensure-faststart.d.ts +1 -0
  6. package/dist/ensure-faststart.js +14 -0
  7. package/dist/extract-frame-from-video.d.ts +1 -0
  8. package/dist/extract-frame-from-video.js +72 -45
  9. package/dist/faststart/atom.d.ts +35 -0
  10. package/dist/faststart/atom.js +138 -0
  11. package/dist/faststart/index.d.ts +0 -0
  12. package/dist/faststart/index.js +1 -0
  13. package/dist/faststart/options.d.ts +6 -0
  14. package/dist/faststart/options.js +2 -0
  15. package/dist/faststart/qt-faststart.d.ts +18 -0
  16. package/dist/faststart/qt-faststart.js +66 -0
  17. package/dist/faststart/update-chunk-offsets.d.ts +10 -0
  18. package/dist/faststart/update-chunk-offsets.js +114 -0
  19. package/dist/faststart/util.d.ts +9 -0
  20. package/dist/faststart/util.js +34 -0
  21. package/dist/get-duration-of-asset.d.ts +7 -0
  22. package/dist/get-duration-of-asset.js +36 -0
  23. package/dist/last-frame-from-video-cache.d.ts +1 -0
  24. package/dist/make-assets-download-dir.js +6 -1
  25. package/dist/offthread-video-server.js +1 -0
  26. package/dist/provide-screenshot.d.ts +1 -0
  27. package/dist/puppeteer-screenshot.d.ts +1 -0
  28. package/dist/render-media.d.ts +5 -0
  29. package/dist/render-media.js +5 -0
  30. package/dist/render-still.d.ts +3 -1
  31. package/dist/render-still.js +3 -1
  32. package/dist/screenshot-dom-element.d.ts +1 -0
  33. package/dist/screenshot-task.d.ts +1 -0
  34. package/package.json +3 -3
@@ -0,0 +1,2 @@
1
+ export declare type RenderCleanupFn = () => Promise<void>;
2
+ export declare type AddRenderCleanupFunction = (cleanup: RenderCleanupFn) => void;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -7,8 +7,8 @@ const calculateFfmpegFilter = ({ asset, fps, durationInFrames, channels, assetDu
7
7
  if (channels === 0) {
8
8
  return null;
9
9
  }
10
- const assetTrimLeft = asset.trimLeft / fps;
11
- const assetTrimRight = (asset.trimLeft + asset.duration * asset.playbackRate) / fps;
10
+ const assetTrimLeft = (asset.trimLeft * asset.playbackRate) / fps;
11
+ const assetTrimRight = assetTrimLeft + (asset.duration * asset.playbackRate) / fps;
12
12
  return (0, stringify_ffmpeg_filter_1.stringifyFfmpegFilter)({
13
13
  channels,
14
14
  startInVideo: asset.startInVideo,
@@ -1,7 +1,4 @@
1
1
  export declare const createFfmpegComplexFilter: (filters: number) => Promise<{
2
- complexFilterFlag: [
3
- string,
4
- string
5
- ] | null;
2
+ complexFilterFlag: [string, string] | null;
6
3
  cleanup: () => void;
7
4
  }>;
@@ -0,0 +1 @@
1
+ export declare const ensureFastStart: () => void;
@@ -0,0 +1,14 @@
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.ensureFastStart = void 0;
7
+ const fs_1 = require("fs");
8
+ const qt_faststart_1 = __importDefault(require("./faststart/qt-faststart"));
9
+ const ensureFastStart = () => {
10
+ const buffer = (0, fs_1.readFileSync)('/Users/jonathanburger/remotion/packages/example/public/offthread2.mp4');
11
+ const newBuffer = (0, qt_faststart_1.default)(buffer);
12
+ (0, fs_1.writeFileSync)('/Users/jonathanburger/remotion/packages/example/public/offthread-faststart.mp4', newBuffer);
13
+ };
14
+ exports.ensureFastStart = ensureFastStart;
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  import { FfmpegExecutable } from 'remotion';
3
4
  import { Readable } from 'stream';
4
5
  import { LastFrameOptions } from './last-frame-from-video-cache';
@@ -7,6 +7,7 @@ exports.extractFrameFromVideo = exports.extractFrameFromVideoFn = exports.getLas
7
7
  const execa_1 = __importDefault(require("execa"));
8
8
  const remotion_1 = require("remotion");
9
9
  const frame_to_ffmpeg_timestamp_1 = require("./frame-to-ffmpeg-timestamp");
10
+ const get_duration_of_asset_1 = require("./get-duration-of-asset");
10
11
  const is_beyond_last_frame_1 = require("./is-beyond-last-frame");
11
12
  const last_frame_from_video_cache_1 = require("./last-frame-from-video-cache");
12
13
  const p_limit_1 = require("./p-limit");
@@ -19,28 +20,67 @@ function streamToString(stream) {
19
20
  });
20
21
  }
21
22
  exports.streamToString = streamToString;
22
- const lastFrameLimit = (0, p_limit_1.pLimit)(5);
23
+ const lastFrameLimit = (0, p_limit_1.pLimit)(1);
23
24
  const mainLimit = (0, p_limit_1.pLimit)(5);
24
- const getLastFrameOfVideoUnlimited = async ({ ffmpegExecutable, ffprobeExecutable, offset, src, }) => {
25
- if (offset > 100) {
26
- throw new Error('could not get last frame of ' +
27
- src +
28
- '. 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.');
25
+ // Uses no seeking, therefore the whole video has to be decoded. This is a last resort and should only happen
26
+ // if the video is corrupted
27
+ const getLastFrameOfVideoSlow = async ({ src, duration, ffmpegExecutable, }) => {
28
+ console.warn(`\nUsing a slow method to determine the last frame of ${src}. The render can be sped up by re-encoding the video properly.`);
29
+ const actualOffset = `-${duration * 1000}ms`;
30
+ const command = [
31
+ '-itsoffset',
32
+ actualOffset,
33
+ '-i',
34
+ src,
35
+ '-frames:v',
36
+ '1',
37
+ '-f',
38
+ 'image2pipe',
39
+ '-',
40
+ ];
41
+ const { stdout, stderr } = (0, execa_1.default)(ffmpegExecutable !== null && ffmpegExecutable !== void 0 ? ffmpegExecutable : 'ffmpeg', command);
42
+ if (!stderr) {
43
+ throw new Error('unexpectedly did not get stderr');
44
+ }
45
+ if (!stdout) {
46
+ throw new Error('unexpectedly did not get stdout');
47
+ }
48
+ const stderrChunks = [];
49
+ const stdoutChunks = [];
50
+ const stdErrString = new Promise((resolve, reject) => {
51
+ stderr.on('data', (d) => stderrChunks.push(d));
52
+ stderr.on('error', (err) => reject(err));
53
+ stderr.on('end', () => resolve(Buffer.concat(stderrChunks).toString('utf-8')));
54
+ });
55
+ const stdoutChunk = new Promise((resolve, reject) => {
56
+ stdout.on('data', (d) => stdoutChunks.push(d));
57
+ stdout.on('error', (err) => reject(err));
58
+ stdout.on('end', () => resolve(Buffer.concat(stdoutChunks)));
59
+ });
60
+ const [stdErr, stdoutBuffer] = await Promise.all([stdErrString, stdoutChunk]);
61
+ const isEmpty = stdErr.includes('Output file is empty');
62
+ if (isEmpty) {
63
+ throw new Error(`Could not get last frame of ${src}. Tried to seek to the end using the command "ffmpeg ${command.join(' ')}" but got no frame. Most likely this video is corrupted.`);
64
+ }
65
+ return stdoutBuffer;
66
+ };
67
+ const getLastFrameOfVideoFastUnlimited = async (options) => {
68
+ const { ffmpegExecutable, ffprobeExecutable, offset, src } = options;
69
+ const fromCache = (0, last_frame_from_video_cache_1.getLastFrameFromCache)({ ...options, offset: 0 });
70
+ if (fromCache) {
71
+ return fromCache;
29
72
  }
30
- const durationCmd = await (0, execa_1.default)(ffprobeExecutable !== null && ffprobeExecutable !== void 0 ? ffprobeExecutable : 'ffprobe', [
31
- '-v',
32
- 'error',
33
- '-select_streams',
34
- 'v:0',
35
- '-show_entries',
36
- 'stream=duration',
37
- '-of',
38
- 'default=noprint_wrappers=1:nokey=1',
73
+ const duration = await (0, get_duration_of_asset_1.getDurationOfAsset)({
39
74
  src,
40
- ]);
41
- const duration = parseFloat(durationCmd.stdout);
42
- if (Number.isNaN(duration)) {
43
- throw new TypeError(`Could not get duration of ${src}: ${durationCmd.stdout}`);
75
+ ffprobeExecutable,
76
+ });
77
+ if (offset > 40) {
78
+ const last = await getLastFrameOfVideoSlow({
79
+ duration,
80
+ ffmpegExecutable,
81
+ src,
82
+ });
83
+ return last;
44
84
  }
45
85
  const actualOffset = `${duration * 1000 - offset - 10}ms`;
46
86
  const { stdout, stderr } = (0, execa_1.default)(ffmpegExecutable !== null && ffmpegExecutable !== void 0 ? ffmpegExecutable : 'ffmpeg', [
@@ -81,33 +121,31 @@ const getLastFrameOfVideoUnlimited = async ({ ffmpegExecutable, ffprobeExecutabl
81
121
  const [stdErr, stdoutBuffer] = await Promise.all([stdErrString, stdoutChunk]);
82
122
  const isEmpty = stdErr.includes('Output file is empty');
83
123
  if (isEmpty) {
84
- return getLastFrameOfVideoUnlimited({
124
+ const unlimited = await getLastFrameOfVideoFastUnlimited({
85
125
  ffmpegExecutable,
86
126
  offset: offset + 10,
87
127
  src,
88
128
  ffprobeExecutable,
89
129
  });
130
+ return unlimited;
90
131
  }
91
132
  return stdoutBuffer;
92
133
  };
93
134
  const getLastFrameOfVideo = async (options) => {
94
- const fromCache = (0, last_frame_from_video_cache_1.getLastFrameFromCache)(options);
95
- if (fromCache) {
96
- return fromCache;
97
- }
98
- const result = await lastFrameLimit(getLastFrameOfVideoUnlimited, options);
135
+ const result = await lastFrameLimit(getLastFrameOfVideoFastUnlimited, options);
99
136
  (0, last_frame_from_video_cache_1.setLastFrameInCache)(options, result);
100
137
  return result;
101
138
  };
102
139
  exports.getLastFrameOfVideo = getLastFrameOfVideo;
103
140
  const extractFrameFromVideoFn = async ({ time, src, ffmpegExecutable, ffprobeExecutable, }) => {
104
141
  if ((0, is_beyond_last_frame_1.isBeyondLastFrame)(src, time)) {
105
- return (0, exports.getLastFrameOfVideo)({
142
+ const lastFrame = await (0, exports.getLastFrameOfVideo)({
106
143
  ffmpegExecutable,
107
144
  ffprobeExecutable,
108
145
  offset: 0,
109
146
  src,
110
147
  });
148
+ return lastFrame;
111
149
  }
112
150
  const ffmpegTimestamp = (0, frame_to_ffmpeg_timestamp_1.frameToFfmpegTimestamp)(time);
113
151
  const { stdout, stderr } = (0, execa_1.default)(ffmpegExecutable !== null && ffmpegExecutable !== void 0 ? ffmpegExecutable : 'ffmpeg', [
@@ -132,26 +170,14 @@ const extractFrameFromVideoFn = async ({ time, src, ffmpegExecutable, ffprobeExe
132
170
  const stdoutChunks = [];
133
171
  const stderrChunks = [];
134
172
  const stderrStringProm = new Promise((resolve, reject) => {
135
- stderr.on('data', (d) => {
136
- stderrChunks.push(d);
137
- });
138
- stderr.on('error', (err) => {
139
- reject(err);
140
- });
141
- stderr.on('end', () => {
142
- resolve(Buffer.concat(stderrChunks).toString('utf8'));
143
- });
173
+ stderr.on('data', (d) => stderrChunks.push(d));
174
+ stderr.on('error', (err) => reject(err));
175
+ stderr.on('end', () => resolve(Buffer.concat(stderrChunks).toString('utf8')));
144
176
  });
145
177
  const stdoutBuffer = new Promise((resolve, reject) => {
146
- stdout.on('data', (d) => {
147
- stdoutChunks.push(d);
148
- });
149
- stdout.on('error', (err) => {
150
- reject(err);
151
- });
152
- stdout.on('end', () => {
153
- resolve(Buffer.concat(stdoutChunks));
154
- });
178
+ stdout.on('data', (d) => stdoutChunks.push(d));
179
+ stdout.on('error', (err) => reject(err));
180
+ stdout.on('end', () => resolve(Buffer.concat(stdoutChunks)));
155
181
  });
156
182
  const [stderrStr, stdOut] = await Promise.all([
157
183
  stderrStringProm,
@@ -159,12 +185,13 @@ const extractFrameFromVideoFn = async ({ time, src, ffmpegExecutable, ffprobeExe
159
185
  ]);
160
186
  if (stderrStr.includes('Output file is empty')) {
161
187
  (0, is_beyond_last_frame_1.markAsBeyondLastFrame)(src, time);
162
- return (0, exports.getLastFrameOfVideo)({
188
+ const last = await (0, exports.getLastFrameOfVideo)({
163
189
  ffmpegExecutable,
164
190
  ffprobeExecutable,
165
191
  offset: 0,
166
192
  src,
167
193
  });
194
+ return last;
168
195
  }
169
196
  return stdOut;
170
197
  };
@@ -0,0 +1,35 @@
1
+ /// <reference types="node" />
2
+ export declare const FREE_ATOM: number;
3
+ export declare const JUNK_ATOM: number;
4
+ export declare const MDAT_ATOM: number;
5
+ export declare const MOOV_ATOM: number;
6
+ export declare const PNOT_ATOM: number;
7
+ export declare const SKIP_ATOM: number;
8
+ export declare const WIDE_ATOM: number;
9
+ export declare const PICT_ATOM: number;
10
+ export declare const FTYP_ATOM: number;
11
+ export declare const UUID_ATOM: number;
12
+ export declare const CMOV_ATOM: number;
13
+ export declare const TRAK_ATOM: number;
14
+ export declare const MDIA_ATOM: number;
15
+ export declare const MINF_ATOM: number;
16
+ export declare const STBL_ATOM: number;
17
+ export declare const STCO_ATOM: number;
18
+ export declare const CO64_ATOM: number;
19
+ export declare const ATOM_PREAMBLE_SIZE: bigint;
20
+ export declare const MAX_FTYP_ATOM_SIZE: bigint;
21
+ export declare enum SizeKind {
22
+ U32 = 0,
23
+ U64 = 1
24
+ }
25
+ export interface QtAtom {
26
+ kind: string;
27
+ size: BigInt;
28
+ sizeKind: SizeKind;
29
+ data: QtAtom[] | Buffer;
30
+ }
31
+ export declare function parseAtoms(infile: Buffer, depth?: number, shallow?: boolean): QtAtom[];
32
+ export declare function recurseFlattenAtoms(atoms: QtAtom[], depth?: number): Buffer;
33
+ export declare function traverseAtoms(atoms: QtAtom[], callback: (atom: QtAtom) => void): void;
34
+ export declare function isQtAtom(atomType: number): boolean;
35
+ export declare function hasSubatoms(atomType: number): boolean;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasSubatoms = exports.isQtAtom = exports.traverseAtoms = exports.recurseFlattenAtoms = exports.parseAtoms = exports.SizeKind = exports.MAX_FTYP_ATOM_SIZE = exports.ATOM_PREAMBLE_SIZE = exports.CO64_ATOM = exports.STCO_ATOM = exports.STBL_ATOM = exports.MINF_ATOM = exports.MDIA_ATOM = exports.TRAK_ATOM = exports.CMOV_ATOM = exports.UUID_ATOM = exports.FTYP_ATOM = exports.PICT_ATOM = exports.WIDE_ATOM = exports.SKIP_ATOM = exports.PNOT_ATOM = exports.MOOV_ATOM = exports.MDAT_ATOM = exports.JUNK_ATOM = exports.FREE_ATOM = void 0;
4
+ /* eslint-disable no-bitwise */
5
+ const util_1 = require("./util");
6
+ exports.FREE_ATOM = (0, util_1.asciiToU32Be)('free');
7
+ exports.JUNK_ATOM = (0, util_1.asciiToU32Be)('junk');
8
+ exports.MDAT_ATOM = (0, util_1.asciiToU32Be)('mdat');
9
+ exports.MOOV_ATOM = (0, util_1.asciiToU32Be)('moov');
10
+ exports.PNOT_ATOM = (0, util_1.asciiToU32Be)('pnot');
11
+ exports.SKIP_ATOM = (0, util_1.asciiToU32Be)('skip');
12
+ exports.WIDE_ATOM = (0, util_1.asciiToU32Be)('wide');
13
+ exports.PICT_ATOM = (0, util_1.asciiToU32Be)('PICT');
14
+ exports.FTYP_ATOM = (0, util_1.asciiToU32Be)('ftyp');
15
+ exports.UUID_ATOM = (0, util_1.asciiToU32Be)('uuid');
16
+ exports.CMOV_ATOM = (0, util_1.asciiToU32Be)('cmov');
17
+ exports.TRAK_ATOM = (0, util_1.asciiToU32Be)('trak');
18
+ exports.MDIA_ATOM = (0, util_1.asciiToU32Be)('mdia');
19
+ exports.MINF_ATOM = (0, util_1.asciiToU32Be)('minf');
20
+ exports.STBL_ATOM = (0, util_1.asciiToU32Be)('stbl');
21
+ exports.STCO_ATOM = (0, util_1.asciiToU32Be)('stco');
22
+ exports.CO64_ATOM = (0, util_1.asciiToU32Be)('co64');
23
+ exports.ATOM_PREAMBLE_SIZE = BigInt(8);
24
+ exports.MAX_FTYP_ATOM_SIZE = BigInt(1048576);
25
+ var SizeKind;
26
+ (function (SizeKind) {
27
+ SizeKind[SizeKind["U32"] = 0] = "U32";
28
+ SizeKind[SizeKind["U64"] = 1] = "U64";
29
+ })(SizeKind = exports.SizeKind || (exports.SizeKind = {}));
30
+ function parseAtoms(infile, depth = 0, shallow = false) {
31
+ const atoms = [];
32
+ const cur = {
33
+ pos: BigInt(0),
34
+ };
35
+ const len = BigInt(infile.byteLength);
36
+ while (cur.pos < len) {
37
+ if (len - cur.pos < 8) {
38
+ break;
39
+ }
40
+ let fwd; // forward-seek counter
41
+ let atomSize = BigInt((0, util_1.readU32)(cur, infile));
42
+ const atomType = (0, util_1.readU32)(cur, infile);
43
+ let sizeKind = SizeKind.U32;
44
+ if (atomSize === BigInt(1)) {
45
+ // 64-bit atom size
46
+ atomSize = (0, util_1.readU64)(cur, infile);
47
+ if (atomSize > BigInt(Number.MAX_SAFE_INTEGER)) {
48
+ throw new Error(`"${atomType}" atom size is larger than MAX_SAFE_INTEGER!`);
49
+ }
50
+ fwd = atomSize - exports.ATOM_PREAMBLE_SIZE * BigInt(2);
51
+ sizeKind = SizeKind.U64;
52
+ }
53
+ else {
54
+ fwd = atomSize - exports.ATOM_PREAMBLE_SIZE;
55
+ }
56
+ const endOfAtom = cur.pos + fwd;
57
+ const subatoms = Buffer.from(infile.slice(Number(cur.pos), Number(endOfAtom)));
58
+ const data = hasSubatoms(atomType) && depth < 10 && !shallow
59
+ ? parseAtoms(subatoms, depth + 1)
60
+ : subatoms;
61
+ cur.pos = endOfAtom;
62
+ if (depth === 0 && !isQtAtom(atomType)) {
63
+ throw new Error(`Non-QT top-level atom found: ${(0, util_1.u32BeToAscii)(atomType)}`);
64
+ }
65
+ atoms.push({
66
+ kind: (0, util_1.u32BeToAscii)(atomType),
67
+ size: atomSize,
68
+ sizeKind,
69
+ data,
70
+ });
71
+ }
72
+ return atoms;
73
+ }
74
+ exports.parseAtoms = parseAtoms;
75
+ function recurseFlattenAtoms(atoms, depth = 0) {
76
+ const buffers = [];
77
+ for (const atom of atoms) {
78
+ if (!Buffer.isBuffer(atom.data)) {
79
+ atom.data = recurseFlattenAtoms(atom.data, depth + 1);
80
+ }
81
+ let header;
82
+ const u64Size = Number(exports.ATOM_PREAMBLE_SIZE) + atom.data.byteLength > 2 ** 32 - 1;
83
+ if (u64Size) {
84
+ const u64Preamble = Number(exports.ATOM_PREAMBLE_SIZE) * 2;
85
+ header = Buffer.alloc(u64Preamble);
86
+ header.writeUInt32BE(1, 0);
87
+ header.writeUInt32BE((0, util_1.asciiToU32Be)(atom.kind), 4);
88
+ const newSize = u64Preamble + atom.data.byteLength;
89
+ header.writeUInt32BE((newSize >> 32) & 0xffffffff, 8);
90
+ header.writeUInt32BE(newSize & 0xffffffff, 12);
91
+ }
92
+ else {
93
+ header = Buffer.alloc(Number(exports.ATOM_PREAMBLE_SIZE));
94
+ const newSize = Number(exports.ATOM_PREAMBLE_SIZE) + atom.data.byteLength;
95
+ header.writeUInt32BE(newSize, 0);
96
+ header.writeUInt32BE((0, util_1.asciiToU32Be)(atom.kind), 4);
97
+ }
98
+ const buf = Buffer.concat([header, atom.data]);
99
+ buffers.push(buf);
100
+ }
101
+ return Buffer.concat(buffers);
102
+ }
103
+ exports.recurseFlattenAtoms = recurseFlattenAtoms;
104
+ function traverseAtoms(atoms, callback) {
105
+ for (const atom of atoms) {
106
+ if (!Buffer.isBuffer(atom.data)) {
107
+ traverseAtoms(atom.data, callback);
108
+ }
109
+ callback(atom);
110
+ }
111
+ }
112
+ exports.traverseAtoms = traverseAtoms;
113
+ function isQtAtom(atomType) {
114
+ return [
115
+ exports.FREE_ATOM,
116
+ exports.JUNK_ATOM,
117
+ exports.MDAT_ATOM,
118
+ exports.MOOV_ATOM,
119
+ exports.PNOT_ATOM,
120
+ exports.SKIP_ATOM,
121
+ exports.WIDE_ATOM,
122
+ exports.PICT_ATOM,
123
+ exports.FTYP_ATOM,
124
+ exports.UUID_ATOM,
125
+ exports.CMOV_ATOM,
126
+ exports.TRAK_ATOM,
127
+ exports.MDIA_ATOM,
128
+ exports.MINF_ATOM,
129
+ exports.STBL_ATOM,
130
+ exports.STCO_ATOM,
131
+ exports.CO64_ATOM,
132
+ ].includes(atomType);
133
+ }
134
+ exports.isQtAtom = isQtAtom;
135
+ function hasSubatoms(atomType) {
136
+ return [exports.MOOV_ATOM, exports.TRAK_ATOM, exports.MDIA_ATOM, exports.MINF_ATOM, exports.STBL_ATOM].includes(atomType);
137
+ }
138
+ exports.hasSubatoms = hasSubatoms;
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,6 @@
1
+ export interface FaststartOptions {
2
+ /**
3
+ * Forces all `stco` atoms to be upgraded to `co64` atoms
4
+ */
5
+ forceUpgradeToCo64?: boolean;
6
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,18 @@
1
+ /// <reference types="node" />
2
+ import { QtAtom } from './atom';
3
+ import { FaststartOptions } from './options';
4
+ export declare function supportStreaming(infile: Buffer): boolean;
5
+ /**
6
+ * Enables "faststart" for QuickTime files so that they can be streamed.
7
+ *
8
+ * @param infile QT/mp4 to faststart
9
+ * @returns Faststarted QT/mp4
10
+ */
11
+ export default function faststart(infile: Buffer, options?: FaststartOptions): Buffer;
12
+ /**
13
+ * Sorts an array of QT atoms so that the first two atoms are `ftyp`, then `moov`.
14
+ * Additionally updates all chunk offsets (`stco`/`co64` atoms) in `moov`.
15
+ *
16
+ * @param atoms QT atoms to sort
17
+ */
18
+ export declare function sortFaststartAtoms(atoms: QtAtom[], options: FaststartOptions): QtAtom[];
@@ -0,0 +1,66 @@
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.sortFaststartAtoms = exports.supportStreaming = void 0;
7
+ const atom_1 = require("./atom");
8
+ const update_chunk_offsets_1 = __importDefault(require("./update-chunk-offsets"));
9
+ function supportStreaming(infile) {
10
+ const file = Buffer.from(infile);
11
+ const atoms = (0, atom_1.parseAtoms)(file, 0, true);
12
+ const mdatIndex = atoms.findIndex((atom) => atom.kind === 'mdat');
13
+ const moovIndex = atoms.findIndex((atom) => atom.kind === 'moov');
14
+ if (moovIndex === -1 || mdatIndex === -1) {
15
+ return false;
16
+ }
17
+ return moovIndex < mdatIndex;
18
+ }
19
+ exports.supportStreaming = supportStreaming;
20
+ /**
21
+ * Enables "faststart" for QuickTime files so that they can be streamed.
22
+ *
23
+ * @param infile QT/mp4 to faststart
24
+ * @returns Faststarted QT/mp4
25
+ */
26
+ function faststart(infile, options = {}) {
27
+ const file = Buffer.from(infile);
28
+ const atoms = (0, atom_1.parseAtoms)(file);
29
+ console.log({ atoms });
30
+ const mdatIndex = atoms.findIndex((atom) => atom.kind === 'mdat');
31
+ if (mdatIndex === -1) {
32
+ throw new Error(`No mdat atom found!`);
33
+ }
34
+ const moovIndex = atoms.findIndex((atom) => atom.kind === 'moov');
35
+ if (moovIndex === -1) {
36
+ throw new Error(`No moov atom found!`);
37
+ }
38
+ const faststarted = sortFaststartAtoms(atoms, options);
39
+ return (0, atom_1.recurseFlattenAtoms)(faststarted);
40
+ }
41
+ exports.default = faststart;
42
+ /**
43
+ * Sorts an array of QT atoms so that the first two atoms are `ftyp`, then `moov`.
44
+ * Additionally updates all chunk offsets (`stco`/`co64` atoms) in `moov`.
45
+ *
46
+ * @param atoms QT atoms to sort
47
+ */
48
+ function sortFaststartAtoms(atoms, options) {
49
+ const faststarted = [];
50
+ const ftyp = atoms.find((atom) => atom.kind === 'ftyp');
51
+ if (!ftyp) {
52
+ throw new Error('Missing ftyp atom!');
53
+ }
54
+ if (ftyp.size > atom_1.MAX_FTYP_ATOM_SIZE) {
55
+ throw new Error(`ftyp atom is greater than ${atom_1.MAX_FTYP_ATOM_SIZE}`);
56
+ }
57
+ console.log('before', { atoms });
58
+ const moov = atoms.find((atom) => atom.kind === 'moov');
59
+ (0, update_chunk_offsets_1.default)(moov, options);
60
+ faststarted.push(ftyp, moov);
61
+ const rest = atoms.filter((atom) => !['ftyp', 'moov'].includes(atom.kind));
62
+ faststarted.push(...rest);
63
+ console.log('after', { faststarted });
64
+ return faststarted;
65
+ }
66
+ exports.sortFaststartAtoms = sortFaststartAtoms;
@@ -0,0 +1,10 @@
1
+ import { QtAtom } from './atom';
2
+ import { FaststartOptions } from './options';
3
+ /**
4
+ * Adds the specified offset to each entry in stco/co64 atoms
5
+ *
6
+ * @param atoms QT atoms to traverse
7
+ * @param offset offset to add
8
+ * @param forceUpgrade always upgrade stco atoms to co64
9
+ */
10
+ export default function updateChunkOffsets(moov: QtAtom, options: FaststartOptions): void;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /* eslint-disable no-bitwise */
4
+ const atom_1 = require("./atom");
5
+ /**
6
+ * Adds the specified offset to each entry in stco/co64 atoms
7
+ *
8
+ * @param atoms QT atoms to traverse
9
+ * @param offset offset to add
10
+ * @param forceUpgrade always upgrade stco atoms to co64
11
+ */
12
+ function updateChunkOffsets(moov, options) {
13
+ const atoms = moov.data;
14
+ const originalMoovSize = Number(moov.size);
15
+ let newChunksSize = 0;
16
+ let originalChunksSize = 0;
17
+ // First pass to count total entries, which is needed for co64 upgrades.
18
+ (0, atom_1.traverseAtoms)(atoms, (atom) => {
19
+ if (!['stco', 'co64'].includes(atom.kind) || !Buffer.isBuffer(atom.data)) {
20
+ return;
21
+ }
22
+ const entries = atom.data.readUInt32BE(4);
23
+ const originalEntrySize = isCo64(atom.kind) ? 8 : 4;
24
+ originalChunksSize += entries * originalEntrySize;
25
+ const newEntrySize = isCo64(atom.kind) || options.forceUpgradeToCo64 ? 8 : 4;
26
+ newChunksSize += entries * newEntrySize;
27
+ });
28
+ // Calculate new mdat offsets to add to stco/co64 chunk offset values
29
+ const totalOffset = originalMoovSize - originalChunksSize + newChunksSize;
30
+ // Second pass to actually update offsets.
31
+ (0, atom_1.traverseAtoms)(atoms, (atom) => {
32
+ if (!['stco', 'co64'].includes(atom.kind) || !Buffer.isBuffer(atom.data)) {
33
+ return;
34
+ }
35
+ const overflow = updateChunkAtom(atom, totalOffset, options);
36
+ if (!overflow) {
37
+ return;
38
+ }
39
+ upgradeStcoToCo64(atom, totalOffset);
40
+ });
41
+ }
42
+ exports.default = updateChunkOffsets;
43
+ /**
44
+ * Updates `stco` or `co64` atoms' chunk offsets.
45
+ *
46
+ * @param atom `stco` or `co64` to update chunk offsets of
47
+ * @param totalOffset the total offset value to add to the existing chunk offsets
48
+ * @param options
49
+ * @returns {boolean} `true` if overflowed (did not finish updating chunks), `false` if succeeded
50
+ */
51
+ function updateChunkAtom(atom, totalOffset, options) {
52
+ if (!Buffer.isBuffer(atom.data)) {
53
+ throw new Error(`${atom.kind} data is not a Buffer!`);
54
+ }
55
+ let overflow = false;
56
+ const entrySize = isCo64(atom.kind) ? 8 : 4;
57
+ const entries = atom.data.readUInt32BE(4);
58
+ const newData = Buffer.alloc(8 + entries * entrySize); // 8 byte header
59
+ atom.data.copy(newData, 0, 0, 8); // copy header
60
+ for (let i = 0; i < entries; i++) {
61
+ const cur = 8 + i * entrySize; // 8 byte header
62
+ if (isCo64(atom.kind)) {
63
+ const newVal64 = Number((BigInt(atom.data.readUInt32BE(cur)) << BigInt(32)) |
64
+ BigInt(atom.data.readUInt32BE(cur + 4))) + totalOffset;
65
+ newData.writeUInt32BE(Number((BigInt(newVal64) >> BigInt(32)) & BigInt(0xffffffff)), cur);
66
+ newData.writeUInt32BE(Number(BigInt(newVal64) & BigInt(0xffffffff)), cur + 4);
67
+ continue;
68
+ }
69
+ if (options.forceUpgradeToCo64) {
70
+ overflow = true;
71
+ break;
72
+ }
73
+ const newVal32 = atom.data.readUInt32BE(cur) + totalOffset;
74
+ if (newVal32 > 2 ** 32 - 1) {
75
+ overflow = true;
76
+ break;
77
+ }
78
+ newData.writeUInt32BE(newVal32, cur);
79
+ }
80
+ if (!overflow) {
81
+ atom.data = newData;
82
+ }
83
+ return overflow;
84
+ }
85
+ /**
86
+ * **Upgrades** `stco` atom to a `co64` atom and also updates the chunk offsets.
87
+ *
88
+ * @param atom `stco` or `co64` to update chunk offsets of
89
+ * @param totalOffset the total offset value to add to the existing chunk offsets
90
+ */
91
+ function upgradeStcoToCo64(atom, totalOffset) {
92
+ if (!Buffer.isBuffer(atom.data)) {
93
+ throw new Error(`${atom.kind} data is not a Buffer!`);
94
+ }
95
+ // Upgrade to stco atoms to co64 atoms
96
+ const entries = atom.data.readUInt32BE(4);
97
+ const upgradedData = Buffer.alloc(8 + entries * 8); // 8-byte header, 8-byte entry size
98
+ atom.data.copy(upgradedData, 0, 0, 8);
99
+ upgradedData.writeUInt32BE((entries >> 32) & 0xffffffff, 8); // MSB 64-bit size
100
+ upgradedData.writeUInt32BE(entries & 0xffffffff, 12); // LSB 64-bit size
101
+ for (let i = 0; i < entries; i++) {
102
+ const cur32 = 8 + i * 4; // 8-byte header, 4-byte entry size for READS (stco)
103
+ const newVal = atom.data.readUInt32BE(cur32) + totalOffset;
104
+ const cur64 = 8 + i * 8; // 8-byte header, 8-byte entry size for WRITES (co64)
105
+ upgradedData.writeUInt32BE(Number((BigInt(newVal) >> BigInt(32)) & BigInt(0xffffffff)), cur64);
106
+ upgradedData.writeUInt32BE(Number(BigInt(newVal) & BigInt(0xffffffff)), cur64 + 4);
107
+ }
108
+ atom.kind = 'co64';
109
+ atom.data = upgradedData;
110
+ atom.size = atom_1.ATOM_PREAMBLE_SIZE * BigInt(2) + BigInt(upgradedData.byteLength);
111
+ }
112
+ function isCo64(atomKind) {
113
+ return atomKind === 'co64';
114
+ }
@@ -0,0 +1,9 @@
1
+ /// <reference types="node" />
2
+ export interface Cursor {
3
+ pos: bigint;
4
+ }
5
+ export declare function numToHex(val: number): string;
6
+ export declare function asciiToU32Be(chars: string): number;
7
+ export declare function u32BeToAscii(u32: number): string;
8
+ export declare function readU32(cur: Cursor, buf: Buffer): number;
9
+ export declare function readU64(cur: Cursor, buf: Buffer): bigint;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readU64 = exports.readU32 = exports.u32BeToAscii = exports.asciiToU32Be = exports.numToHex = void 0;
4
+ function numToHex(val) {
5
+ return val.toString(16);
6
+ }
7
+ exports.numToHex = numToHex;
8
+ function asciiToU32Be(chars) {
9
+ return Buffer.from(chars.split('').map((char) => char.charCodeAt(0))).readUInt32BE(0);
10
+ }
11
+ exports.asciiToU32Be = asciiToU32Be;
12
+ function u32BeToAscii(u32) {
13
+ const buf = Buffer.alloc(4);
14
+ buf.writeUInt32BE(u32, 0);
15
+ return buf.toString('ascii');
16
+ }
17
+ exports.u32BeToAscii = u32BeToAscii;
18
+ function readU32(cur, buf) {
19
+ const u32 = buf.readUInt32BE(Number(cur.pos));
20
+ cur.pos += BigInt(4);
21
+ return u32;
22
+ }
23
+ exports.readU32 = readU32;
24
+ function readU64(cur, buf) {
25
+ const long = buf.slice(Number(cur.pos), Number(cur.pos + BigInt(8)));
26
+ const u64 =
27
+ // eslint-disable-next-line no-bitwise
28
+ (BigInt(long.readUInt32BE(0)) << BigInt(32)) |
29
+ // eslint-disable-next-line no-bitwise
30
+ (BigInt(long.readUInt32BE(4)) & BigInt(0xffffffff));
31
+ cur.pos += BigInt(8);
32
+ return u64;
33
+ }
34
+ exports.readU64 = readU64;
@@ -0,0 +1,7 @@
1
+ import { FfmpegExecutable } from 'remotion';
2
+ declare type Options = {
3
+ ffprobeExecutable: FfmpegExecutable;
4
+ src: string;
5
+ };
6
+ export declare const getDurationOfAsset: (options: Options) => Promise<number>;
7
+ export {};
@@ -0,0 +1,36 @@
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.getDurationOfAsset = void 0;
7
+ const execa_1 = __importDefault(require("execa"));
8
+ const p_limit_1 = require("./p-limit");
9
+ const durationOfAssetCache = {};
10
+ const limit = (0, p_limit_1.pLimit)(1);
11
+ const getDurationOfAssetUnlimited = async ({ ffprobeExecutable, src, }) => {
12
+ if (durationOfAssetCache[src]) {
13
+ return durationOfAssetCache[src];
14
+ }
15
+ const durationCmd = await (0, execa_1.default)(ffprobeExecutable !== null && ffprobeExecutable !== void 0 ? ffprobeExecutable : 'ffprobe', [
16
+ '-v',
17
+ 'error',
18
+ '-select_streams',
19
+ 'v:0',
20
+ '-show_entries',
21
+ 'stream=duration',
22
+ '-of',
23
+ 'default=noprint_wrappers=1:nokey=1',
24
+ src,
25
+ ]);
26
+ const duration = parseFloat(durationCmd.stdout);
27
+ if (Number.isNaN(duration)) {
28
+ throw new TypeError(`Could not get duration of ${src}: ${durationCmd.stdout}`);
29
+ }
30
+ durationOfAssetCache[src] = duration;
31
+ return duration;
32
+ };
33
+ const getDurationOfAsset = (options) => {
34
+ return limit(getDurationOfAssetUnlimited, options);
35
+ };
36
+ exports.getDurationOfAsset = getDurationOfAsset;
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { FfmpegExecutable } from 'remotion';
2
3
  export declare type LastFrameOptions = {
3
4
  ffmpegExecutable: FfmpegExecutable;
@@ -2,7 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.makeAssetsDownloadTmpDir = void 0;
4
4
  const tmp_dir_1 = require("./tmp-dir");
5
+ let dir = null;
5
6
  const makeAssetsDownloadTmpDir = () => {
6
- return (0, tmp_dir_1.tmpDir)('remotion-assets-dir');
7
+ if (dir) {
8
+ return dir;
9
+ }
10
+ dir = (0, tmp_dir_1.tmpDir)('remotion-assets-dir');
11
+ return dir;
7
12
  };
8
13
  exports.makeAssetsDownloadTmpDir = makeAssetsDownloadTmpDir;
@@ -59,6 +59,7 @@ const startOffthreadVideoServer = ({ ffmpegExecutable, ffprobeExecutable, downlo
59
59
  .catch((err) => {
60
60
  res.writeHead(500);
61
61
  res.end();
62
+ onError(err);
62
63
  console.log('Error occurred', err);
63
64
  });
64
65
  };
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import puppeteer from 'puppeteer-core';
2
3
  import { ImageFormat } from 'remotion';
3
4
  export declare const provideScreenshot: ({ page, imageFormat, options, quality, }: {
@@ -1,2 +1,3 @@
1
+ /// <reference types="node" />
1
2
  import { Page, ScreenshotOptions } from 'puppeteer-core';
2
3
  export declare const screenshot: (page: Page, options?: ScreenshotOptions) => Promise<Buffer | string | void>;
@@ -43,4 +43,9 @@ export declare type RenderMediaOptions = {
43
43
  cancelSignal?: CancelSignal;
44
44
  browserExecutable?: BrowserExecutable;
45
45
  } & ServeUrlOrWebpackBundle;
46
+ /**
47
+ *
48
+ * @description Render a video from a composition
49
+ * @link https://www.remotion.dev/docs/renderer/render-media
50
+ */
46
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>;
@@ -24,6 +24,11 @@ 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
+ /**
28
+ *
29
+ * @description Render a video from a composition
30
+ * @link https://www.remotion.dev/docs/renderer/render-media
31
+ */
27
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) {
@@ -28,7 +28,9 @@ declare type RenderStillOptions = InnerStillOptions & ServeUrlOrWebpackBundle &
28
28
  port?: number | null;
29
29
  };
30
30
  /**
31
- * @description Render a still frame from a composition and returns an image path
31
+ *
32
+ * @description Render a still frame from a composition
33
+ * @link https://www.remotion.dev/docs/renderer/render-still
32
34
  */
33
35
  export declare const renderStill: (options: RenderStillOptions) => Promise<void>;
34
36
  export {};
@@ -138,7 +138,9 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
138
138
  await cleanup();
139
139
  };
140
140
  /**
141
- * @description Render a still frame from a composition and returns an image path
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;
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import puppeteer from 'puppeteer-core';
2
3
  import { ImageFormat } from 'remotion';
3
4
  export declare const screenshotDOMElement: ({ page, imageFormat, quality, opts, }: {
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { Page, ScreenshotOptions } from 'puppeteer-core';
2
3
  import { StillImageFormat } from 'remotion';
3
4
  export declare const _screenshotTask: (page: Page, format: StillImageFormat, options: ScreenshotOptions) => Promise<Buffer | string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/renderer",
3
- "version": "3.0.18",
3
+ "version": "3.0.19",
4
4
  "description": "Renderer for Remotion",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "execa": "5.1.1",
24
24
  "mime-types": "2.1.35",
25
25
  "puppeteer-core": "13.5.1",
26
- "remotion": "3.0.18",
26
+ "remotion": "3.0.19",
27
27
  "source-map": "^0.8.0-beta.0"
28
28
  },
29
29
  "peerDependencies": {
@@ -59,5 +59,5 @@
59
59
  "publishConfig": {
60
60
  "access": "public"
61
61
  },
62
- "gitHead": "64eadd48b303d0f627ed5883f9200345af002ee2"
62
+ "gitHead": "388ef0a2eacf9dbadbc860fdb8d6b3e32acb10f3"
63
63
  }