@invintusmedia/tomp4 1.0.8 → 1.1.0
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/README.md +25 -1
- package/dist/tomp4.js +312 -363
- package/package.json +6 -3
- package/src/fmp4/converter.js +323 -0
- package/src/fmp4/index.js +25 -0
- package/src/fmp4/stitcher.js +615 -0
- package/src/fmp4/utils.js +201 -0
- package/src/index.js +41 -20
- package/src/mpegts/index.js +7 -0
- package/src/mpegts/stitcher.js +251 -0
- package/src/muxers/mp4.js +85 -85
- package/src/muxers/mpegts.js +101 -19
- package/src/parsers/mp4.js +691 -0
- package/src/parsers/mpegts.js +42 -42
- package/src/remote/index.js +444 -0
- package/src/transcode.js +20 -36
- package/src/ts-to-mp4.js +37 -37
- package/src/fmp4-to-mp4.js +0 -375
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@invintusmedia/tomp4",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Convert MPEG-TS, fMP4, and HLS streams to MP4 with clipping support - pure JavaScript, zero dependencies",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -20,8 +20,11 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "node build.js",
|
|
22
22
|
"dev": "npx serve . -p 3000",
|
|
23
|
-
"test": "
|
|
24
|
-
"
|
|
23
|
+
"test": "npm run test:clip && npm run test:mp4",
|
|
24
|
+
"test:clip": "node tests/clip.test.js",
|
|
25
|
+
"test:mp4": "node tests/mp4-parser.test.js",
|
|
26
|
+
"test:all": "npm run test",
|
|
27
|
+
"release": "npm test && npm run build && git add -A && git commit -m \"v$(node -p \"require('./package.json').version\")\" && git tag v$(node -p \"require('./package.json').version\") && git push && git push --tags",
|
|
25
28
|
"release:patch": "npm version patch --no-git-tag-version && npm run release",
|
|
26
29
|
"release:minor": "npm version minor --no-git-tag-version && npm run release",
|
|
27
30
|
"release:major": "npm version major --no-git-tag-version && npm run release",
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fMP4 to Standard MP4 Converter
|
|
3
|
+
*
|
|
4
|
+
* Converts a fragmented MP4 file to a standard MP4 container
|
|
5
|
+
* by extracting samples from fragments and rebuilding the moov box.
|
|
6
|
+
*
|
|
7
|
+
* @module fmp4/converter
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
parseBoxes, findBox, parseChildBoxes, createBox,
|
|
12
|
+
parseTfhd, parseTrun
|
|
13
|
+
} from './utils.js';
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// Moov Rebuilding Functions
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
function rebuildMvhd(mvhdBox, duration) {
|
|
20
|
+
const data = new Uint8Array(mvhdBox.data);
|
|
21
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
22
|
+
const version = data[8];
|
|
23
|
+
const durationOffset = version === 0 ? 24 : 32;
|
|
24
|
+
if (version === 0) view.setUint32(durationOffset, duration);
|
|
25
|
+
else { view.setUint32(durationOffset, 0); view.setUint32(durationOffset + 4, duration); }
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function rebuildTkhd(tkhdBox, trackInfo, maxDuration) {
|
|
30
|
+
const data = new Uint8Array(tkhdBox.data);
|
|
31
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
32
|
+
const version = data[8];
|
|
33
|
+
let trackDuration = maxDuration;
|
|
34
|
+
if (trackInfo) { trackDuration = 0; for (const s of trackInfo.samples) trackDuration += s.duration || 0; }
|
|
35
|
+
if (version === 0) view.setUint32(28, trackDuration);
|
|
36
|
+
else { view.setUint32(36, 0); view.setUint32(40, trackDuration); }
|
|
37
|
+
return data;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function rebuildMdhd(mdhdBox, trackInfo, maxDuration) {
|
|
41
|
+
const data = new Uint8Array(mdhdBox.data);
|
|
42
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
43
|
+
const version = data[8];
|
|
44
|
+
let trackDuration = 0;
|
|
45
|
+
if (trackInfo) for (const s of trackInfo.samples) trackDuration += s.duration || 0;
|
|
46
|
+
const durationOffset = version === 0 ? 24 : 32;
|
|
47
|
+
if (version === 0) view.setUint32(durationOffset, trackDuration);
|
|
48
|
+
else { view.setUint32(durationOffset, 0); view.setUint32(durationOffset + 4, trackDuration); }
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function rebuildStbl(stblBox, trackInfo) {
|
|
53
|
+
const stblChildren = parseChildBoxes(stblBox);
|
|
54
|
+
const newParts = [];
|
|
55
|
+
for (const child of stblChildren) if (child.type === 'stsd') { newParts.push(child.data); break; }
|
|
56
|
+
const samples = trackInfo?.samples || [];
|
|
57
|
+
const chunkOffsets = trackInfo?.chunkOffsets || [];
|
|
58
|
+
|
|
59
|
+
// stts
|
|
60
|
+
const sttsEntries = [];
|
|
61
|
+
let curDur = null, count = 0;
|
|
62
|
+
for (const s of samples) {
|
|
63
|
+
const d = s.duration || 0;
|
|
64
|
+
if (d === curDur) count++;
|
|
65
|
+
else { if (curDur !== null) sttsEntries.push({ count, duration: curDur }); curDur = d; count = 1; }
|
|
66
|
+
}
|
|
67
|
+
if (curDur !== null) sttsEntries.push({ count, duration: curDur });
|
|
68
|
+
const sttsData = new Uint8Array(8 + sttsEntries.length * 8);
|
|
69
|
+
const sttsView = new DataView(sttsData.buffer);
|
|
70
|
+
sttsView.setUint32(4, sttsEntries.length);
|
|
71
|
+
let off = 8;
|
|
72
|
+
for (const e of sttsEntries) { sttsView.setUint32(off, e.count); sttsView.setUint32(off + 4, e.duration); off += 8; }
|
|
73
|
+
newParts.push(createBox('stts', sttsData));
|
|
74
|
+
|
|
75
|
+
// stsc
|
|
76
|
+
const stscEntries = [];
|
|
77
|
+
if (chunkOffsets.length > 0) {
|
|
78
|
+
let currentSampleCount = chunkOffsets[0].sampleCount, firstChunk = 1;
|
|
79
|
+
for (let i = 1; i <= chunkOffsets.length; i++) {
|
|
80
|
+
const sampleCount = i < chunkOffsets.length ? chunkOffsets[i].sampleCount : -1;
|
|
81
|
+
if (sampleCount !== currentSampleCount) {
|
|
82
|
+
stscEntries.push({ firstChunk, samplesPerChunk: currentSampleCount, sampleDescriptionIndex: 1 });
|
|
83
|
+
firstChunk = i + 1; currentSampleCount = sampleCount;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} else stscEntries.push({ firstChunk: 1, samplesPerChunk: samples.length, sampleDescriptionIndex: 1 });
|
|
87
|
+
const stscData = new Uint8Array(8 + stscEntries.length * 12);
|
|
88
|
+
const stscView = new DataView(stscData.buffer);
|
|
89
|
+
stscView.setUint32(4, stscEntries.length);
|
|
90
|
+
off = 8;
|
|
91
|
+
for (const e of stscEntries) { stscView.setUint32(off, e.firstChunk); stscView.setUint32(off + 4, e.samplesPerChunk); stscView.setUint32(off + 8, e.sampleDescriptionIndex); off += 12; }
|
|
92
|
+
newParts.push(createBox('stsc', stscData));
|
|
93
|
+
|
|
94
|
+
// stsz
|
|
95
|
+
const stszData = new Uint8Array(12 + samples.length * 4);
|
|
96
|
+
const stszView = new DataView(stszData.buffer);
|
|
97
|
+
stszView.setUint32(8, samples.length);
|
|
98
|
+
off = 12;
|
|
99
|
+
for (const s of samples) { stszView.setUint32(off, s.size || 0); off += 4; }
|
|
100
|
+
newParts.push(createBox('stsz', stszData));
|
|
101
|
+
|
|
102
|
+
// stco
|
|
103
|
+
const numChunks = chunkOffsets.length || 1;
|
|
104
|
+
const stcoData = new Uint8Array(8 + numChunks * 4);
|
|
105
|
+
const stcoView = new DataView(stcoData.buffer);
|
|
106
|
+
stcoView.setUint32(4, numChunks);
|
|
107
|
+
for (let i = 0; i < numChunks; i++) stcoView.setUint32(8 + i * 4, chunkOffsets[i]?.offset || 0);
|
|
108
|
+
newParts.push(createBox('stco', stcoData));
|
|
109
|
+
|
|
110
|
+
// ctts
|
|
111
|
+
const hasCtts = samples.some(s => s.compositionTimeOffset);
|
|
112
|
+
if (hasCtts) {
|
|
113
|
+
const cttsEntries = [];
|
|
114
|
+
let curOff = null; count = 0;
|
|
115
|
+
for (const s of samples) {
|
|
116
|
+
const o = s.compositionTimeOffset || 0;
|
|
117
|
+
if (o === curOff) count++;
|
|
118
|
+
else { if (curOff !== null) cttsEntries.push({ count, offset: curOff }); curOff = o; count = 1; }
|
|
119
|
+
}
|
|
120
|
+
if (curOff !== null) cttsEntries.push({ count, offset: curOff });
|
|
121
|
+
const cttsData = new Uint8Array(8 + cttsEntries.length * 8);
|
|
122
|
+
const cttsView = new DataView(cttsData.buffer);
|
|
123
|
+
cttsView.setUint32(4, cttsEntries.length);
|
|
124
|
+
off = 8;
|
|
125
|
+
for (const e of cttsEntries) { cttsView.setUint32(off, e.count); cttsView.setInt32(off + 4, e.offset); off += 8; }
|
|
126
|
+
newParts.push(createBox('ctts', cttsData));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// stss
|
|
130
|
+
const syncSamples = [];
|
|
131
|
+
for (let i = 0; i < samples.length; i++) {
|
|
132
|
+
const flags = samples[i].flags;
|
|
133
|
+
if (flags !== undefined) { if (!((flags >> 16) & 0x1)) syncSamples.push(i + 1); }
|
|
134
|
+
}
|
|
135
|
+
if (syncSamples.length > 0 && syncSamples.length < samples.length) {
|
|
136
|
+
const stssData = new Uint8Array(8 + syncSamples.length * 4);
|
|
137
|
+
const stssView = new DataView(stssData.buffer);
|
|
138
|
+
stssView.setUint32(4, syncSamples.length);
|
|
139
|
+
off = 8;
|
|
140
|
+
for (const n of syncSamples) { stssView.setUint32(off, n); off += 4; }
|
|
141
|
+
newParts.push(createBox('stss', stssData));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return createBox('stbl', ...newParts);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function rebuildMinf(minfBox, trackInfo) {
|
|
148
|
+
const minfChildren = parseChildBoxes(minfBox);
|
|
149
|
+
const newParts = [];
|
|
150
|
+
for (const child of minfChildren) {
|
|
151
|
+
if (child.type === 'stbl') newParts.push(rebuildStbl(child, trackInfo));
|
|
152
|
+
else newParts.push(child.data);
|
|
153
|
+
}
|
|
154
|
+
return createBox('minf', ...newParts);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function rebuildMdia(mdiaBox, trackInfo, maxDuration) {
|
|
158
|
+
const mdiaChildren = parseChildBoxes(mdiaBox);
|
|
159
|
+
const newParts = [];
|
|
160
|
+
for (const child of mdiaChildren) {
|
|
161
|
+
if (child.type === 'minf') newParts.push(rebuildMinf(child, trackInfo));
|
|
162
|
+
else if (child.type === 'mdhd') newParts.push(rebuildMdhd(child, trackInfo, maxDuration));
|
|
163
|
+
else newParts.push(child.data);
|
|
164
|
+
}
|
|
165
|
+
return createBox('mdia', ...newParts);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function rebuildTrak(trakBox, trackIdMap, maxDuration) {
|
|
169
|
+
const trakChildren = parseChildBoxes(trakBox);
|
|
170
|
+
let trackId = 1;
|
|
171
|
+
for (const child of trakChildren) {
|
|
172
|
+
if (child.type === 'tkhd') {
|
|
173
|
+
const view = new DataView(child.data.buffer, child.data.byteOffset, child.data.byteLength);
|
|
174
|
+
trackId = child.data[8] === 0 ? view.getUint32(20) : view.getUint32(28);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const trackInfo = trackIdMap.get(trackId);
|
|
178
|
+
const newParts = [];
|
|
179
|
+
let hasEdts = false;
|
|
180
|
+
for (const child of trakChildren) {
|
|
181
|
+
if (child.type === 'edts') { hasEdts = true; newParts.push(child.data); }
|
|
182
|
+
else if (child.type === 'mdia') newParts.push(rebuildMdia(child, trackInfo, maxDuration));
|
|
183
|
+
else if (child.type === 'tkhd') newParts.push(rebuildTkhd(child, trackInfo, maxDuration));
|
|
184
|
+
else newParts.push(child.data);
|
|
185
|
+
}
|
|
186
|
+
if (!hasEdts && trackInfo) {
|
|
187
|
+
let trackDuration = 0;
|
|
188
|
+
for (const s of trackInfo.samples) trackDuration += s.duration || 0;
|
|
189
|
+
const elstData = new Uint8Array(20);
|
|
190
|
+
const elstView = new DataView(elstData.buffer);
|
|
191
|
+
elstView.setUint32(4, 1); elstView.setUint32(8, maxDuration); elstView.setInt32(12, 0); elstView.setInt16(16, 1);
|
|
192
|
+
const elst = createBox('elst', elstData);
|
|
193
|
+
const edts = createBox('edts', elst);
|
|
194
|
+
const tkhdIndex = newParts.findIndex(p => p.length >= 8 && String.fromCharCode(p[4], p[5], p[6], p[7]) === 'tkhd');
|
|
195
|
+
if (tkhdIndex >= 0) newParts.splice(tkhdIndex + 1, 0, edts);
|
|
196
|
+
}
|
|
197
|
+
return createBox('trak', ...newParts);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function updateStcoOffsets(output, ftypSize, moovSize) {
|
|
201
|
+
const mdatContentOffset = ftypSize + moovSize + 8;
|
|
202
|
+
const view = new DataView(output.buffer, output.byteOffset, output.byteLength);
|
|
203
|
+
function scan(start, end) {
|
|
204
|
+
let pos = start;
|
|
205
|
+
while (pos + 8 <= end) {
|
|
206
|
+
const size = view.getUint32(pos);
|
|
207
|
+
if (size < 8) break;
|
|
208
|
+
const type = String.fromCharCode(output[pos + 4], output[pos + 5], output[pos + 6], output[pos + 7]);
|
|
209
|
+
if (type === 'stco') {
|
|
210
|
+
const entryCount = view.getUint32(pos + 12);
|
|
211
|
+
for (let i = 0; i < entryCount; i++) {
|
|
212
|
+
const entryPos = pos + 16 + i * 4;
|
|
213
|
+
view.setUint32(entryPos, mdatContentOffset + view.getUint32(entryPos));
|
|
214
|
+
}
|
|
215
|
+
} else if (['moov', 'trak', 'mdia', 'minf', 'stbl'].includes(type)) scan(pos + 8, pos + size);
|
|
216
|
+
pos += size;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
scan(0, output.byteLength);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ============================================
|
|
223
|
+
// Main Converter Function
|
|
224
|
+
// ============================================
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Convert fragmented MP4 to standard MP4
|
|
228
|
+
* @param {Uint8Array} fmp4Data - fMP4 data
|
|
229
|
+
* @returns {Uint8Array} Standard MP4 data
|
|
230
|
+
*/
|
|
231
|
+
export function convertFmp4ToMp4(fmp4Data) {
|
|
232
|
+
const boxes = parseBoxes(fmp4Data);
|
|
233
|
+
const ftyp = findBox(boxes, 'ftyp');
|
|
234
|
+
const moov = findBox(boxes, 'moov');
|
|
235
|
+
if (!ftyp || !moov) throw new Error('Invalid fMP4: missing ftyp or moov');
|
|
236
|
+
|
|
237
|
+
const moovChildren = parseChildBoxes(moov);
|
|
238
|
+
const originalTrackIds = [];
|
|
239
|
+
for (const child of moovChildren) {
|
|
240
|
+
if (child.type === 'trak') {
|
|
241
|
+
const trakChildren = parseChildBoxes(child);
|
|
242
|
+
for (const tc of trakChildren) {
|
|
243
|
+
if (tc.type === 'tkhd') {
|
|
244
|
+
const view = new DataView(tc.data.buffer, tc.data.byteOffset, tc.data.byteLength);
|
|
245
|
+
originalTrackIds.push(tc.data[8] === 0 ? view.getUint32(20) : view.getUint32(28));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const tracks = new Map();
|
|
252
|
+
const mdatChunks = [];
|
|
253
|
+
let combinedMdatOffset = 0;
|
|
254
|
+
|
|
255
|
+
for (let i = 0; i < boxes.length; i++) {
|
|
256
|
+
const box = boxes[i];
|
|
257
|
+
if (box.type === 'moof') {
|
|
258
|
+
const moofChildren = parseChildBoxes(box);
|
|
259
|
+
const moofStart = box.offset;
|
|
260
|
+
let nextMdatOffset = 0;
|
|
261
|
+
for (let j = i + 1; j < boxes.length; j++) {
|
|
262
|
+
if (boxes[j].type === 'mdat') { nextMdatOffset = boxes[j].offset; break; }
|
|
263
|
+
if (boxes[j].type === 'moof') break;
|
|
264
|
+
}
|
|
265
|
+
for (const child of moofChildren) {
|
|
266
|
+
if (child.type === 'traf') {
|
|
267
|
+
const trafChildren = parseChildBoxes(child);
|
|
268
|
+
const tfhd = findBox(trafChildren, 'tfhd');
|
|
269
|
+
const trun = findBox(trafChildren, 'trun');
|
|
270
|
+
if (tfhd && trun) {
|
|
271
|
+
const tfhdInfo = parseTfhd(tfhd.data);
|
|
272
|
+
const { samples, dataOffset } = parseTrun(trun.data, tfhdInfo);
|
|
273
|
+
if (!tracks.has(tfhdInfo.trackId)) tracks.set(tfhdInfo.trackId, { samples: [], chunkOffsets: [] });
|
|
274
|
+
const track = tracks.get(tfhdInfo.trackId);
|
|
275
|
+
const chunkOffset = combinedMdatOffset + (moofStart + dataOffset) - (nextMdatOffset + 8);
|
|
276
|
+
track.chunkOffsets.push({ offset: chunkOffset, sampleCount: samples.length });
|
|
277
|
+
track.samples.push(...samples);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} else if (box.type === 'mdat') {
|
|
282
|
+
mdatChunks.push({ data: box.data.subarray(8), offset: combinedMdatOffset });
|
|
283
|
+
combinedMdatOffset += box.data.subarray(8).byteLength;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const totalMdatSize = mdatChunks.reduce((sum, c) => sum + c.data.byteLength, 0);
|
|
288
|
+
const combinedMdat = new Uint8Array(totalMdatSize);
|
|
289
|
+
for (const chunk of mdatChunks) combinedMdat.set(chunk.data, chunk.offset);
|
|
290
|
+
|
|
291
|
+
const trackIdMap = new Map();
|
|
292
|
+
const fmp4TrackIds = Array.from(tracks.keys()).sort((a, b) => a - b);
|
|
293
|
+
for (let i = 0; i < fmp4TrackIds.length && i < originalTrackIds.length; i++) {
|
|
294
|
+
trackIdMap.set(originalTrackIds[i], tracks.get(fmp4TrackIds[i]));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
let maxDuration = 0;
|
|
298
|
+
for (const [, track] of tracks) {
|
|
299
|
+
let dur = 0;
|
|
300
|
+
for (const s of track.samples) dur += s.duration || 0;
|
|
301
|
+
maxDuration = Math.max(maxDuration, dur);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const newMoovParts = [];
|
|
305
|
+
for (const child of moovChildren) {
|
|
306
|
+
if (child.type === 'mvex') continue;
|
|
307
|
+
if (child.type === 'trak') newMoovParts.push(rebuildTrak(child, trackIdMap, maxDuration));
|
|
308
|
+
else if (child.type === 'mvhd') newMoovParts.push(rebuildMvhd(child, maxDuration));
|
|
309
|
+
else newMoovParts.push(child.data);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const newMoov = createBox('moov', ...newMoovParts);
|
|
313
|
+
const newMdat = createBox('mdat', combinedMdat);
|
|
314
|
+
const output = new Uint8Array(ftyp.size + newMoov.byteLength + newMdat.byteLength);
|
|
315
|
+
output.set(ftyp.data, 0);
|
|
316
|
+
output.set(newMoov, ftyp.size);
|
|
317
|
+
output.set(newMdat, ftyp.size + newMoov.byteLength);
|
|
318
|
+
updateStcoOffsets(output, ftyp.size, newMoov.byteLength);
|
|
319
|
+
|
|
320
|
+
return output;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export default convertFmp4ToMp4;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fMP4 Module
|
|
3
|
+
*
|
|
4
|
+
* Handles fragmented MP4 (fMP4) processing:
|
|
5
|
+
* - Converting single fMP4 files to standard MP4
|
|
6
|
+
* - Stitching multiple fMP4 segments into a single MP4
|
|
7
|
+
*
|
|
8
|
+
* @module fmp4
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { convertFmp4ToMp4 } from './converter.js';
|
|
12
|
+
export { stitchFmp4 } from './stitcher.js';
|
|
13
|
+
|
|
14
|
+
// Re-export utilities for advanced use cases
|
|
15
|
+
export {
|
|
16
|
+
parseBoxes,
|
|
17
|
+
findBox,
|
|
18
|
+
parseChildBoxes,
|
|
19
|
+
createBox,
|
|
20
|
+
parseTfhd,
|
|
21
|
+
parseTfdt,
|
|
22
|
+
parseTrun,
|
|
23
|
+
extractTrackIds,
|
|
24
|
+
getMovieTimescale
|
|
25
|
+
} from './utils.js';
|