@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
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { FfmpegExecutable } from 'remotion';
|
|
2
|
+
export declare function getAudioChannelsAndDuration(path: string, ffprobeExecutable: FfmpegExecutable): Promise<{
|
|
2
3
|
channels: number;
|
|
3
4
|
duration: number | null;
|
|
4
5
|
}>;
|
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getAudioChannelsAndDuration = void 0;
|
|
7
7
|
const execa_1 = __importDefault(require("execa"));
|
|
8
|
-
async function getAudioChannelsAndDuration(path) {
|
|
8
|
+
async function getAudioChannelsAndDuration(path, ffprobeExecutable) {
|
|
9
9
|
const args = [
|
|
10
10
|
['-v', 'error'],
|
|
11
11
|
['-show_entries', 'stream=channels:format=duration'],
|
|
@@ -14,7 +14,7 @@ async function getAudioChannelsAndDuration(path) {
|
|
|
14
14
|
]
|
|
15
15
|
.reduce((acc, val) => acc.concat(val), [])
|
|
16
16
|
.filter(Boolean);
|
|
17
|
-
const task = await (0, execa_1.default)('ffprobe', args);
|
|
17
|
+
const task = await (0, execa_1.default)(ffprobeExecutable !== null && ffprobeExecutable !== void 0 ? ffprobeExecutable : 'ffprobe', args);
|
|
18
18
|
const channels = task.stdout.match(/channels=([0-9]+)/);
|
|
19
19
|
const duration = task.stdout.match(/duration=([0-9.]+)/);
|
|
20
20
|
return {
|
|
@@ -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 =
|
|
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,
|
package/dist/combine-videos.js
CHANGED
|
@@ -30,6 +30,9 @@ const combineVideos = async ({ files, filelistDir, output, onProgress, numberOfF
|
|
|
30
30
|
remotion_1.Internals.isAudioCodec(codec) ? null : 'copy',
|
|
31
31
|
'-c:a',
|
|
32
32
|
(0, get_audio_codec_name_1.getAudioCodecName)(codec),
|
|
33
|
+
// Set max bitrate up to 1024kbps, will choose lower if that's too much
|
|
34
|
+
'-b:a',
|
|
35
|
+
'512K',
|
|
33
36
|
codec === 'h264' ? '-movflags' : null,
|
|
34
37
|
codec === 'h264' ? 'faststart' : null,
|
|
35
38
|
'-shortest',
|
|
@@ -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;
|
|
@@ -2,17 +2,15 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { FfmpegExecutable } from 'remotion';
|
|
4
4
|
import { Readable } from 'stream';
|
|
5
|
+
import { LastFrameOptions } from './last-frame-from-video-cache';
|
|
5
6
|
export declare function streamToString(stream: Readable): Promise<string>;
|
|
6
|
-
export declare const getLastFrameOfVideo: (
|
|
7
|
-
ffmpegExecutable: FfmpegExecutable;
|
|
8
|
-
offset: number;
|
|
9
|
-
src: string;
|
|
10
|
-
}) => Promise<Buffer>;
|
|
7
|
+
export declare const getLastFrameOfVideo: (options: LastFrameOptions) => Promise<Buffer>;
|
|
11
8
|
declare type Options = {
|
|
12
9
|
time: number;
|
|
13
10
|
src: string;
|
|
14
11
|
ffmpegExecutable: FfmpegExecutable;
|
|
12
|
+
ffprobeExecutable: FfmpegExecutable;
|
|
15
13
|
};
|
|
16
|
-
export declare const extractFrameFromVideoFn: ({ time, src, ffmpegExecutable, }: Options) => Promise<Buffer>;
|
|
14
|
+
export declare const extractFrameFromVideoFn: ({ time, src, ffmpegExecutable, ffprobeExecutable, }: Options) => Promise<Buffer>;
|
|
17
15
|
export declare const extractFrameFromVideo: (options: Options) => Promise<Buffer>;
|
|
18
16
|
export {};
|
|
@@ -5,7 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.extractFrameFromVideo = exports.extractFrameFromVideoFn = exports.getLastFrameOfVideo = exports.streamToString = void 0;
|
|
7
7
|
const execa_1 = __importDefault(require("execa"));
|
|
8
|
+
const remotion_1 = require("remotion");
|
|
8
9
|
const frame_to_ffmpeg_timestamp_1 = require("./frame-to-ffmpeg-timestamp");
|
|
10
|
+
const get_duration_of_asset_1 = require("./get-duration-of-asset");
|
|
11
|
+
const is_beyond_last_frame_1 = require("./is-beyond-last-frame");
|
|
12
|
+
const last_frame_from_video_cache_1 = require("./last-frame-from-video-cache");
|
|
9
13
|
const p_limit_1 = require("./p-limit");
|
|
10
14
|
function streamToString(stream) {
|
|
11
15
|
const chunks = [];
|
|
@@ -16,15 +20,71 @@ function streamToString(stream) {
|
|
|
16
20
|
});
|
|
17
21
|
}
|
|
18
22
|
exports.streamToString = streamToString;
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const lastFrameLimit = (0, p_limit_1.pLimit)(1);
|
|
24
|
+
const mainLimit = (0, p_limit_1.pLimit)(5);
|
|
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;
|
|
72
|
+
}
|
|
73
|
+
const duration = await (0, get_duration_of_asset_1.getDurationOfAsset)({
|
|
74
|
+
src,
|
|
75
|
+
ffprobeExecutable,
|
|
76
|
+
});
|
|
77
|
+
if (offset > 40) {
|
|
78
|
+
const last = await getLastFrameOfVideoSlow({
|
|
79
|
+
duration,
|
|
80
|
+
ffmpegExecutable,
|
|
81
|
+
src,
|
|
82
|
+
});
|
|
83
|
+
return last;
|
|
24
84
|
}
|
|
25
|
-
const actualOffset =
|
|
85
|
+
const actualOffset = `${duration * 1000 - offset - 10}ms`;
|
|
26
86
|
const { stdout, stderr } = (0, execa_1.default)(ffmpegExecutable !== null && ffmpegExecutable !== void 0 ? ffmpegExecutable : 'ffmpeg', [
|
|
27
|
-
'-
|
|
87
|
+
'-ss',
|
|
28
88
|
actualOffset,
|
|
29
89
|
'-i',
|
|
30
90
|
src,
|
|
@@ -61,13 +121,32 @@ const getLastFrameOfVideo = async ({ ffmpegExecutable, offset, src, }) => {
|
|
|
61
121
|
const [stdErr, stdoutBuffer] = await Promise.all([stdErrString, stdoutChunk]);
|
|
62
122
|
const isEmpty = stdErr.includes('Output file is empty');
|
|
63
123
|
if (isEmpty) {
|
|
64
|
-
|
|
124
|
+
const unlimited = await getLastFrameOfVideoFastUnlimited({
|
|
125
|
+
ffmpegExecutable,
|
|
126
|
+
offset: offset + 10,
|
|
127
|
+
src,
|
|
128
|
+
ffprobeExecutable,
|
|
129
|
+
});
|
|
130
|
+
return unlimited;
|
|
65
131
|
}
|
|
66
132
|
return stdoutBuffer;
|
|
67
133
|
};
|
|
134
|
+
const getLastFrameOfVideo = async (options) => {
|
|
135
|
+
const result = await lastFrameLimit(getLastFrameOfVideoFastUnlimited, options);
|
|
136
|
+
(0, last_frame_from_video_cache_1.setLastFrameInCache)(options, result);
|
|
137
|
+
return result;
|
|
138
|
+
};
|
|
68
139
|
exports.getLastFrameOfVideo = getLastFrameOfVideo;
|
|
69
|
-
const
|
|
70
|
-
|
|
140
|
+
const extractFrameFromVideoFn = async ({ time, src, ffmpegExecutable, ffprobeExecutable, }) => {
|
|
141
|
+
if ((0, is_beyond_last_frame_1.isBeyondLastFrame)(src, time)) {
|
|
142
|
+
const lastFrame = await (0, exports.getLastFrameOfVideo)({
|
|
143
|
+
ffmpegExecutable,
|
|
144
|
+
ffprobeExecutable,
|
|
145
|
+
offset: 0,
|
|
146
|
+
src,
|
|
147
|
+
});
|
|
148
|
+
return lastFrame;
|
|
149
|
+
}
|
|
71
150
|
const ffmpegTimestamp = (0, frame_to_ffmpeg_timestamp_1.frameToFfmpegTimestamp)(time);
|
|
72
151
|
const { stdout, stderr } = (0, execa_1.default)(ffmpegExecutable !== null && ffmpegExecutable !== void 0 ? ffmpegExecutable : 'ffmpeg', [
|
|
73
152
|
'-ss',
|
|
@@ -91,42 +170,36 @@ const extractFrameFromVideoFn = async ({ time, src, ffmpegExecutable, }) => {
|
|
|
91
170
|
const stdoutChunks = [];
|
|
92
171
|
const stderrChunks = [];
|
|
93
172
|
const stderrStringProm = new Promise((resolve, reject) => {
|
|
94
|
-
stderr.on('data', (d) =>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
stderr.on('error', (err) => {
|
|
98
|
-
reject(err);
|
|
99
|
-
});
|
|
100
|
-
stderr.on('end', () => {
|
|
101
|
-
resolve(Buffer.concat(stderrChunks).toString('utf8'));
|
|
102
|
-
});
|
|
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')));
|
|
103
176
|
});
|
|
104
177
|
const stdoutBuffer = new Promise((resolve, reject) => {
|
|
105
|
-
stdout.on('data', (d) =>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
stdout.on('error', (err) => {
|
|
109
|
-
reject(err);
|
|
110
|
-
});
|
|
111
|
-
stdout.on('end', () => {
|
|
112
|
-
resolve(Buffer.concat(stdoutChunks));
|
|
113
|
-
});
|
|
178
|
+
stdout.on('data', (d) => stdoutChunks.push(d));
|
|
179
|
+
stdout.on('error', (err) => reject(err));
|
|
180
|
+
stdout.on('end', () => resolve(Buffer.concat(stdoutChunks)));
|
|
114
181
|
});
|
|
115
182
|
const [stderrStr, stdOut] = await Promise.all([
|
|
116
183
|
stderrStringProm,
|
|
117
184
|
stdoutBuffer,
|
|
118
185
|
]);
|
|
119
186
|
if (stderrStr.includes('Output file is empty')) {
|
|
120
|
-
|
|
187
|
+
(0, is_beyond_last_frame_1.markAsBeyondLastFrame)(src, time);
|
|
188
|
+
const last = await (0, exports.getLastFrameOfVideo)({
|
|
121
189
|
ffmpegExecutable,
|
|
190
|
+
ffprobeExecutable,
|
|
122
191
|
offset: 0,
|
|
123
192
|
src,
|
|
124
193
|
});
|
|
194
|
+
return last;
|
|
125
195
|
}
|
|
126
196
|
return stdOut;
|
|
127
197
|
};
|
|
128
198
|
exports.extractFrameFromVideoFn = extractFrameFromVideoFn;
|
|
129
|
-
const extractFrameFromVideo = (options) => {
|
|
130
|
-
|
|
199
|
+
const extractFrameFromVideo = async (options) => {
|
|
200
|
+
const perf = remotion_1.Internals.perf.startPerfMeasure('extract-frame');
|
|
201
|
+
const res = await mainLimit(exports.extractFrameFromVideoFn, options);
|
|
202
|
+
remotion_1.Internals.perf.stopPerfMeasure(perf);
|
|
203
|
+
return res;
|
|
131
204
|
};
|
|
132
205
|
exports.extractFrameFromVideo = extractFrameFromVideo;
|
|
@@ -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,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;
|