@h3l1os/mp4vault 2.0.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/dist/index.js ADDED
@@ -0,0 +1,1479 @@
1
+ // src/jspack.js
2
+ function JSPack() {
3
+ var el;
4
+ var bBE = false;
5
+ var m = this;
6
+ m._DeArray = function(a, p, l) {
7
+ return [a.slice(p, p + l)];
8
+ };
9
+ m._EnArray = function(a, p, l, v) {
10
+ for (var i = 0; i < l; a[p + i] = v[i] ? v[i] : 0, i++) {
11
+ }
12
+ };
13
+ m._DeChar = function(a, p) {
14
+ return String.fromCharCode(a[p]);
15
+ };
16
+ m._EnChar = function(a, p, v) {
17
+ a[p] = v.charCodeAt(0);
18
+ };
19
+ m._DeInt = function(a, p) {
20
+ var lsb = bBE ? el.len - 1 : 0;
21
+ var nsb = bBE ? -1 : 1;
22
+ var stop = lsb + nsb * el.len;
23
+ var rv, i, f;
24
+ for (rv = 0, i = lsb, f = 1; i != stop; rv += a[p + i] * f, i += nsb, f *= 256) {
25
+ }
26
+ if (el.bSigned && rv & Math.pow(2, el.len * 8 - 1)) {
27
+ rv -= Math.pow(2, el.len * 8);
28
+ }
29
+ return rv;
30
+ };
31
+ m._EnInt = function(a, p, v) {
32
+ var lsb = bBE ? el.len - 1 : 0;
33
+ var nsb = bBE ? -1 : 1;
34
+ var stop = lsb + nsb * el.len;
35
+ var i;
36
+ v = v < el.min ? el.min : v > el.max ? el.max : v;
37
+ for (i = lsb; i != stop; a[p + i] = v & 255, i += nsb, v >>= 8) {
38
+ }
39
+ };
40
+ m._DeString = function(a, p, l) {
41
+ for (var rv = new Array(l), i = 0; i < l; rv[i] = String.fromCharCode(a[p + i]), i++) {
42
+ }
43
+ return rv.join("");
44
+ };
45
+ m._EnString = function(a, p, l, v) {
46
+ for (var t, i = 0; i < l; a[p + i] = (t = v.charCodeAt(i)) ? t : 0, i++) {
47
+ }
48
+ };
49
+ m._De754 = function(a, p) {
50
+ var s, e, m2, i, d, nBits, mLen, eLen, eBias, eMax;
51
+ mLen = el.mLen, eLen = el.len * 8 - el.mLen - 1, eMax = (1 << eLen) - 1, eBias = eMax >> 1;
52
+ i = bBE ? 0 : el.len - 1;
53
+ d = bBE ? 1 : -1;
54
+ s = a[p + i];
55
+ i += d;
56
+ nBits = -7;
57
+ for (e = s & (1 << -nBits) - 1, s >>= -nBits, nBits += eLen; nBits > 0; e = e * 256 + a[p + i], i += d, nBits -= 8) {
58
+ }
59
+ for (m2 = e & (1 << -nBits) - 1, e >>= -nBits, nBits += mLen; nBits > 0; m2 = m2 * 256 + a[p + i], i += d, nBits -= 8) {
60
+ }
61
+ switch (e) {
62
+ case 0:
63
+ e = 1 - eBias;
64
+ break;
65
+ case eMax:
66
+ return m2 ? NaN : (s ? -1 : 1) * Infinity;
67
+ default:
68
+ m2 = m2 + Math.pow(2, mLen);
69
+ e = e - eBias;
70
+ break;
71
+ }
72
+ return (s ? -1 : 1) * m2 * Math.pow(2, e - mLen);
73
+ };
74
+ m._En754 = function(a, p, v) {
75
+ var s, e, m2, i, d, c, mLen, eLen, eBias, eMax;
76
+ mLen = el.mLen, eLen = el.len * 8 - el.mLen - 1, eMax = (1 << eLen) - 1, eBias = eMax >> 1;
77
+ s = v < 0 ? 1 : 0;
78
+ v = Math.abs(v);
79
+ if (isNaN(v) || v == Infinity) {
80
+ m2 = isNaN(v) ? 1 : 0;
81
+ e = eMax;
82
+ } else {
83
+ e = Math.floor(Math.log(v) / Math.LN2);
84
+ if (v * (c = Math.pow(2, -e)) < 1) {
85
+ e--;
86
+ c *= 2;
87
+ }
88
+ if (e + eBias >= 1) {
89
+ v += el.rt / c;
90
+ } else {
91
+ v += el.rt * Math.pow(2, 1 - eBias);
92
+ }
93
+ if (v * c >= 2) {
94
+ e++;
95
+ c /= 2;
96
+ }
97
+ if (e + eBias >= eMax) {
98
+ m2 = 0;
99
+ e = eMax;
100
+ } else if (e + eBias >= 1) {
101
+ m2 = (v * c - 1) * Math.pow(2, mLen);
102
+ e = e + eBias;
103
+ } else {
104
+ m2 = v * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
105
+ e = 0;
106
+ }
107
+ }
108
+ for (i = bBE ? el.len - 1 : 0, d = bBE ? -1 : 1; mLen >= 8; a[p + i] = m2 & 255, i += d, m2 /= 256, mLen -= 8) {
109
+ }
110
+ for (e = e << mLen | m2, eLen += mLen; eLen > 0; a[p + i] = e & 255, i += d, e /= 256, eLen -= 8) {
111
+ }
112
+ a[p + i - d] |= s * 128;
113
+ };
114
+ m._DeInt64 = function(a, p) {
115
+ var start = bBE ? 0 : 7;
116
+ var nsb = bBE ? 1 : -1;
117
+ var stop = start + nsb * 8;
118
+ var rv = [0, 0, !el.bSigned];
119
+ var i, f, rvi;
120
+ for (i = start, rvi = 1, f = 0; i != stop; rv[rvi] = (rv[rvi] << 8 >>> 0) + a[p + i], i += nsb, f++, rvi = f < 4 ? 1 : 0) {
121
+ }
122
+ return rv[0] + rv[1] * 4294967296;
123
+ };
124
+ m._EnInt64 = function(a, p, v) {
125
+ if (!Array.isArray(v)) {
126
+ v = [
127
+ v % 4294967296,
128
+ Math.floor(v / 4294967296),
129
+ 1
130
+ ];
131
+ }
132
+ var start = bBE ? 0 : 7;
133
+ var nsb = bBE ? 1 : -1;
134
+ var stop = start + nsb * 8;
135
+ var i, f, rvi, s;
136
+ for (i = start, rvi = 1, f = 0, s = 24; i != stop; a[p + i] = v[rvi] >> s & 255, i += nsb, f++, rvi = f < 4 ? 1 : 0, s = 24 - 8 * (f % 4)) {
137
+ }
138
+ };
139
+ m._sPattern = "(\\d+)?([AxcbBhHsfdiIlLqQ])";
140
+ m._lenLut = {
141
+ "A": 1,
142
+ "x": 1,
143
+ "c": 1,
144
+ "b": 1,
145
+ "B": 1,
146
+ "h": 2,
147
+ "H": 2,
148
+ "s": 1,
149
+ "f": 4,
150
+ "d": 8,
151
+ "i": 4,
152
+ "I": 4,
153
+ "l": 4,
154
+ "L": 4,
155
+ "q": 8,
156
+ "Q": 8
157
+ };
158
+ m._elLut = {
159
+ "A": { en: m._EnArray, de: m._DeArray },
160
+ "s": { en: m._EnString, de: m._DeString },
161
+ "c": { en: m._EnChar, de: m._DeChar },
162
+ "b": { en: m._EnInt, de: m._DeInt, len: 1, bSigned: true, min: -Math.pow(2, 7), max: Math.pow(2, 7) - 1 },
163
+ "B": { en: m._EnInt, de: m._DeInt, len: 1, bSigned: false, min: 0, max: Math.pow(2, 8) - 1 },
164
+ "h": { en: m._EnInt, de: m._DeInt, len: 2, bSigned: true, min: -Math.pow(2, 15), max: Math.pow(2, 15) - 1 },
165
+ "H": { en: m._EnInt, de: m._DeInt, len: 2, bSigned: false, min: 0, max: Math.pow(2, 16) - 1 },
166
+ "i": { en: m._EnInt, de: m._DeInt, len: 4, bSigned: true, min: -Math.pow(2, 31), max: Math.pow(2, 31) - 1 },
167
+ "I": { en: m._EnInt, de: m._DeInt, len: 4, bSigned: false, min: 0, max: Math.pow(2, 32) - 1 },
168
+ "l": { en: m._EnInt, de: m._DeInt, len: 4, bSigned: true, min: -Math.pow(2, 31), max: Math.pow(2, 31) - 1 },
169
+ "L": { en: m._EnInt, de: m._DeInt, len: 4, bSigned: false, min: 0, max: Math.pow(2, 32) - 1 },
170
+ "f": { en: m._En754, de: m._De754, len: 4, mLen: 23, rt: Math.pow(2, -24) - Math.pow(2, -77) },
171
+ "d": { en: m._En754, de: m._De754, len: 8, mLen: 52, rt: 0 },
172
+ "q": { en: m._EnInt64, de: m._DeInt64, bSigned: true },
173
+ "Q": { en: m._EnInt64, de: m._DeInt64, bSigned: false }
174
+ };
175
+ m._UnpackSeries = function(n, s, a, p) {
176
+ for (var fxn = el.de, rv = [], i = 0; i < n; rv.push(fxn(a, p + i * s)), i++) {
177
+ }
178
+ return rv;
179
+ };
180
+ m._PackSeries = function(n, s, a, p, v, i) {
181
+ for (var fxn = el.en, o = 0; o < n; fxn(a, p + o * s, v[i + o]), o++) {
182
+ }
183
+ };
184
+ m.UnpackTo = function(fmt, a, p, allowLessData = false) {
185
+ bBE = fmt.charAt(0) != "<";
186
+ p = p ? p : 0;
187
+ var re = new RegExp(this._sPattern, "g");
188
+ var m2, n, s;
189
+ var rv = [];
190
+ while (m2 = re.exec(fmt)) {
191
+ n = m2[1] == void 0 || m2[1] == "" ? 1 : parseInt(m2[1]);
192
+ s = this._lenLut[m2[2]];
193
+ if (p + n * s > a.length) {
194
+ if (allowLessData) {
195
+ break;
196
+ } else {
197
+ return void 0;
198
+ }
199
+ }
200
+ switch (m2[2]) {
201
+ case "A":
202
+ case "s":
203
+ rv.push(this._elLut[m2[2]].de(a, p, n));
204
+ break;
205
+ case "c":
206
+ case "b":
207
+ case "B":
208
+ case "h":
209
+ case "H":
210
+ case "i":
211
+ case "I":
212
+ case "l":
213
+ case "L":
214
+ case "f":
215
+ case "d":
216
+ case "q":
217
+ case "Q":
218
+ el = this._elLut[m2[2]];
219
+ if (n > 1) {
220
+ var arr = [];
221
+ arr.push(this._UnpackSeries(n, s, a, p));
222
+ rv.push(arr);
223
+ } else {
224
+ rv.push(this._UnpackSeries(n, s, a, p));
225
+ }
226
+ break;
227
+ }
228
+ p += n * s;
229
+ }
230
+ return Array.prototype.concat.apply([], rv);
231
+ };
232
+ m.PackTo = function(fmt, a, p, values, allowLessData) {
233
+ bBE = fmt.charAt(0) != "<";
234
+ var re = new RegExp(this._sPattern, "g");
235
+ var m2, n, s, j;
236
+ var i = 0;
237
+ while (m2 = re.exec(fmt)) {
238
+ n = m2[1] == void 0 || m2[1] == "" ? 1 : parseInt(m2[1]);
239
+ s = this._lenLut[m2[2]];
240
+ if (p + n * s > a.length) {
241
+ return false;
242
+ }
243
+ switch (m2[2]) {
244
+ case "A":
245
+ case "s":
246
+ if (i + 1 > values.length) {
247
+ return allowLessData ? a.slice(0, p) : false;
248
+ }
249
+ this._elLut[m2[2]].en(a, p, n, values[i]);
250
+ i += 1;
251
+ break;
252
+ case "c":
253
+ case "b":
254
+ case "B":
255
+ case "h":
256
+ case "H":
257
+ case "i":
258
+ case "I":
259
+ case "l":
260
+ case "L":
261
+ case "f":
262
+ case "d":
263
+ case "q":
264
+ case "Q":
265
+ el = this._elLut[m2[2]];
266
+ if (n > 1 && Array.isArray(values[i])) {
267
+ if (i + 1 > values.length) {
268
+ return allowLessData ? a.slice(0, p) : false;
269
+ }
270
+ this._PackSeries(n, s, a, p, values[i], 0);
271
+ i += 1;
272
+ } else {
273
+ if (i + n > values.length) {
274
+ return allowLessData ? a.slice(0, p) : false;
275
+ }
276
+ this._PackSeries(n, s, a, p, values, i);
277
+ i += n;
278
+ }
279
+ break;
280
+ case "x":
281
+ for (j = 0; j < n; j++) {
282
+ a[p + j] = 0;
283
+ }
284
+ break;
285
+ }
286
+ p += n * s;
287
+ }
288
+ return a;
289
+ };
290
+ m.Pack = function(fmt, values, allowLessData = false) {
291
+ return this.PackTo(fmt, new Array(this.CalcLength(fmt)), 0, values, allowLessData);
292
+ };
293
+ m.Unpack = function(fmt, values, allowLessData = false) {
294
+ return this.UnpackTo(fmt, values, 0, allowLessData);
295
+ };
296
+ m.CalcLength = function(fmt) {
297
+ var re = new RegExp(this._sPattern, "g");
298
+ var sum = 0;
299
+ var m2;
300
+ while (m2 = re.exec(fmt)) {
301
+ sum += (m2[1] == void 0 || m2[1] == "" ? 1 : parseInt(m2[1])) * this._lenLut[m2[2]];
302
+ }
303
+ return sum;
304
+ };
305
+ }
306
+ var jspack_default = new JSPack();
307
+
308
+ // src/Pack.ts
309
+ var Pack = class {
310
+ static pack(format, values) {
311
+ return jspack_default.Pack(format, values);
312
+ }
313
+ static unpack(format, buffer) {
314
+ return jspack_default.Unpack(format, buffer, 0);
315
+ }
316
+ };
317
+
318
+ // src/constants.ts
319
+ var BUFFER_SIZE = 1e5;
320
+ var MAX_HEADER_SIZE = 10485760;
321
+ var MAX_INT32 = 4294967295;
322
+
323
+ // src/Atom.ts
324
+ var Atom = class {
325
+ constructor(params) {
326
+ this.readable = params.readable || null;
327
+ this.name = params.name;
328
+ this.start = params.start;
329
+ this.size = params.size;
330
+ this.header_size = params.header_size;
331
+ this.mother = params.mother || null;
332
+ this.children = [];
333
+ this.contents = null;
334
+ }
335
+ findAtoms(atoms, name) {
336
+ atoms = atoms || this.children;
337
+ let ret = [];
338
+ for (const a of atoms) {
339
+ if (a.name === name) {
340
+ ret.push(a);
341
+ }
342
+ if (a.children.length) {
343
+ ret = ret.concat(this.findAtoms(a.children, name));
344
+ }
345
+ }
346
+ return ret;
347
+ }
348
+ async unpackFromOffset(offset, length, fmt) {
349
+ try {
350
+ const data = await this.readable.getSlice(this.start + offset, length);
351
+ return Pack.unpack(fmt, data);
352
+ } catch {
353
+ return [];
354
+ }
355
+ }
356
+ isVideo() {
357
+ const vmhdAtoms = this.findAtoms(null, "vmhd");
358
+ return !!(vmhdAtoms && vmhdAtoms.length);
359
+ }
360
+ isAudio() {
361
+ const smhdAtoms = this.findAtoms(null, "smhd");
362
+ return !!(smhdAtoms && smhdAtoms.length);
363
+ }
364
+ async getChunkOffsets() {
365
+ let sampleOffsets = [];
366
+ if (this.name !== "stco" && this.name !== "co64") {
367
+ const sampleAtoms = this.findAtoms(null, "stco").concat(this.findAtoms(null, "co64"));
368
+ for (const atom of sampleAtoms) {
369
+ sampleOffsets = sampleOffsets.concat(await atom.getChunkOffsets());
370
+ }
371
+ return sampleOffsets;
372
+ }
373
+ const data = await this.readable.getSlice(this.start + this.header_size, 8);
374
+ const unpacked = Pack.unpack(">II", data);
375
+ const count = unpacked[1];
376
+ if (this.name === "stco") {
377
+ for (let i = 0; i < count; i += 1024) {
378
+ const cToRead = Math.min(1024, count - i);
379
+ const readOffsets = Pack.unpack(
380
+ ">" + "I".repeat(cToRead),
381
+ await this.readable.getSlice(this.start + this.header_size + 8 + i * 4, cToRead * 4)
382
+ );
383
+ sampleOffsets.push(...readOffsets);
384
+ }
385
+ } else if (this.name === "co64") {
386
+ for (let i = 0; i < count; i += 1024) {
387
+ const cToRead = Math.min(1024, count - i);
388
+ const readOffsets = Pack.unpack(
389
+ ">" + "Q".repeat(cToRead),
390
+ await this.readable.getSlice(this.start + this.header_size + 8 + i * 8, cToRead * 8)
391
+ );
392
+ sampleOffsets.push(...readOffsets);
393
+ }
394
+ }
395
+ return sampleOffsets;
396
+ }
397
+ async writeHeader(writable) {
398
+ if (this.size > MAX_INT32 && this.header_size === 8) {
399
+ throw new Error("Size too large for compact header");
400
+ }
401
+ if (this.size < MAX_INT32) {
402
+ await writable.write(Pack.pack(">I4s", [this.size, this.name]));
403
+ } else {
404
+ await writable.write(Pack.pack(">I4sQ", [1, this.name, this.size]));
405
+ }
406
+ }
407
+ async writePayload(writable) {
408
+ if (this.children.length) {
409
+ for (const a of this.children) {
410
+ await a.write(writable);
411
+ }
412
+ } else {
413
+ const bodySize = this.size - this.header_size;
414
+ if (this.readable) {
415
+ for (let i = 0; i < bodySize; i += BUFFER_SIZE) {
416
+ const copySize = Math.min(BUFFER_SIZE, bodySize - i);
417
+ const chunk = await this.readable.getSlice(this.start + this.header_size + i, copySize);
418
+ await writable.write(chunk);
419
+ }
420
+ } else if (this.contents) {
421
+ if (this.contents.length === bodySize) {
422
+ await writable.write(this.contents);
423
+ } else {
424
+ throw new Error("Invalid bodySize for contents chunk");
425
+ }
426
+ } else {
427
+ if (bodySize > 0) {
428
+ await writable.write(new Uint8Array([0]));
429
+ }
430
+ }
431
+ }
432
+ }
433
+ async write(writable) {
434
+ await this.writeHeader(writable);
435
+ await this.writePayload(writable);
436
+ }
437
+ };
438
+
439
+ // src/AES.ts
440
+ import crypto from "crypto";
441
+ var IV_BYTE_LENGTH = 12;
442
+ var SALT_BYTE_LENGTH = 16;
443
+ var AUTH_TAG_BYTE_LENGTH = 16;
444
+ var KEY_BYTE_LENGTH = 32;
445
+ var PBKDF2_ITERATIONS = 6e5;
446
+ var PBKDF2_DIGEST = "sha512";
447
+ var AES = class _AES {
448
+ constructor(params) {
449
+ this._cipher = null;
450
+ this._decipher = null;
451
+ this._iv = params.iv || crypto.randomBytes(IV_BYTE_LENGTH);
452
+ this._salt = params.salt || null;
453
+ this._authTag = params.authTag || null;
454
+ if (params.password) {
455
+ if (!this._salt) {
456
+ this._salt = crypto.randomBytes(SALT_BYTE_LENGTH);
457
+ }
458
+ this._key = _AES.deriveKey(params.password, this._salt);
459
+ } else if (params.key) {
460
+ this._key = _AES._normalizeKey(params.key);
461
+ } else {
462
+ throw new Error("Either key or password is required");
463
+ }
464
+ }
465
+ static {
466
+ this.ivByteLength = IV_BYTE_LENGTH;
467
+ }
468
+ static {
469
+ this.saltByteLength = SALT_BYTE_LENGTH;
470
+ }
471
+ static {
472
+ this.authTagByteLength = AUTH_TAG_BYTE_LENGTH;
473
+ }
474
+ static _normalizeKey(key) {
475
+ if (Buffer.isBuffer(key)) {
476
+ if (key.length !== 16 && key.length !== 24 && key.length !== 32) {
477
+ throw new Error("Key must be 16, 24, or 32 bytes. Got " + key.length);
478
+ }
479
+ return key;
480
+ }
481
+ if (key instanceof Uint8Array) {
482
+ if (key.length !== 16 && key.length !== 24 && key.length !== 32) {
483
+ throw new Error("Key must be 16, 24, or 32 bytes. Got " + key.length);
484
+ }
485
+ return Buffer.from(key);
486
+ }
487
+ throw new Error("Key must be a Buffer or Uint8Array");
488
+ }
489
+ static _algorithmForKey(key) {
490
+ switch (key.length) {
491
+ case 16:
492
+ return "aes-128-gcm";
493
+ case 24:
494
+ return "aes-192-gcm";
495
+ case 32:
496
+ return "aes-256-gcm";
497
+ default:
498
+ throw new Error("Key must be 16, 24, or 32 bytes. Got " + key.length);
499
+ }
500
+ }
501
+ static deriveKey(password, salt) {
502
+ return crypto.pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, KEY_BYTE_LENGTH, PBKDF2_DIGEST);
503
+ }
504
+ encrypt(chunk, finalize = false) {
505
+ if (!this._cipher) {
506
+ const algo = _AES._algorithmForKey(this._key);
507
+ this._cipher = crypto.createCipheriv(algo, this._key, this._iv);
508
+ }
509
+ if (finalize) {
510
+ if (chunk && chunk.length > 0) {
511
+ const processed = this._cipher.update(chunk);
512
+ const final = this._cipher.final();
513
+ this._authTag = this._cipher.getAuthTag();
514
+ return Buffer.concat([processed, final]);
515
+ } else {
516
+ const final = this._cipher.final();
517
+ this._authTag = this._cipher.getAuthTag();
518
+ return final;
519
+ }
520
+ } else {
521
+ return this._cipher.update(chunk);
522
+ }
523
+ }
524
+ decrypt(chunk, finalize = false) {
525
+ if (!this._decipher) {
526
+ const algo = _AES._algorithmForKey(this._key);
527
+ this._decipher = crypto.createDecipheriv(algo, this._key, this._iv);
528
+ if (this._authTag) {
529
+ this._decipher.setAuthTag(this._authTag);
530
+ }
531
+ }
532
+ if (finalize) {
533
+ if (chunk && chunk.length > 0) {
534
+ const processed = this._decipher.update(chunk);
535
+ const final = this._decipher.final();
536
+ return Buffer.concat([processed, final]);
537
+ } else {
538
+ return this._decipher.final();
539
+ }
540
+ } else {
541
+ return this._decipher.update(chunk);
542
+ }
543
+ }
544
+ getAuthTag() {
545
+ if (!this._authTag) {
546
+ throw new Error("Auth tag not available. Call encrypt with finalize=true first");
547
+ }
548
+ return this._authTag;
549
+ }
550
+ getIV() {
551
+ return this._iv;
552
+ }
553
+ getSalt() {
554
+ return this._salt;
555
+ }
556
+ };
557
+
558
+ // src/Convert.ts
559
+ import crypto2 from "crypto";
560
+ var Convert = class {
561
+ static randomByteIn(maxOptions, option) {
562
+ const maxMultiple = Math.floor(256 / maxOptions);
563
+ const randomValue = crypto2.randomInt(maxMultiple);
564
+ return randomValue * maxOptions + option;
565
+ }
566
+ static isByteIn(byte, maxOptions, option) {
567
+ return byte % maxOptions === option;
568
+ }
569
+ static objectToBuffer(object) {
570
+ return Buffer.from(JSON.stringify(object), "utf-8");
571
+ }
572
+ static bufferToObject(buffer) {
573
+ return JSON.parse(Buffer.from(buffer).toString("utf-8"));
574
+ }
575
+ static hexStringToBuffer(str) {
576
+ if (typeof str !== "string" || !/^[0-9a-fA-F]*$/.test(str) || str.length % 2 !== 0) {
577
+ throw new Error("Invalid hex string");
578
+ }
579
+ return Buffer.from(str, "hex");
580
+ }
581
+ };
582
+
583
+ // src/EmbedObject.ts
584
+ var EmbedObject = class _EmbedObject {
585
+ constructor(params = {}) {
586
+ this._encryptor = null;
587
+ this._object = params.object || null;
588
+ this._binary = params.object ? Convert.objectToBuffer(params.object) : null;
589
+ this._key = params.key || null;
590
+ this._password = params.password || null;
591
+ this._iv = params.iv || null;
592
+ this._salt = params.salt || null;
593
+ this._readBytes = params.readBytes || 0;
594
+ }
595
+ get readBytes() {
596
+ return this._readBytes;
597
+ }
598
+ async getExpectedSize() {
599
+ if (this._key || this._password) {
600
+ return 2 + AES.saltByteLength + AES.ivByteLength + 4 + this._binary.length + AES.authTagByteLength;
601
+ } else {
602
+ return 2 + 4 + this._binary.length;
603
+ }
604
+ }
605
+ get object() {
606
+ return this._object;
607
+ }
608
+ getEncryptor() {
609
+ if (this._encryptor) {
610
+ return this._encryptor;
611
+ }
612
+ this._encryptor = new AES({
613
+ key: this._key || void 0,
614
+ password: this._password || void 0,
615
+ iv: this._iv || void 0,
616
+ salt: this._salt || void 0
617
+ });
618
+ if (!this._iv) {
619
+ this._iv = this._encryptor.getIV();
620
+ }
621
+ if (!this._salt) {
622
+ this._salt = this._encryptor.getSalt();
623
+ }
624
+ return this._encryptor;
625
+ }
626
+ getIV() {
627
+ if (!this._iv) {
628
+ throw new Error("IV is not yet ready. Run getEncryptor() first, or specify one yourself");
629
+ }
630
+ return this._iv;
631
+ }
632
+ static async restoreFromReadable(readable, params = {}, offset = 0) {
633
+ const firstByte = (await readable.getSlice(offset + 0, 1))[0];
634
+ const typeByte = (await readable.getSlice(offset + 1, 1))[0];
635
+ let size = null;
636
+ let readBytes = 2;
637
+ if (Convert.isByteIn(firstByte, 2, 1)) {
638
+ const salt = await readable.getSlice(offset + 2, AES.saltByteLength);
639
+ readBytes += AES.saltByteLength;
640
+ const iv = await readable.getSlice(offset + 2 + AES.saltByteLength, AES.ivByteLength);
641
+ readBytes += AES.ivByteLength;
642
+ const headerDataOffset = offset + 2 + AES.saltByteLength + AES.ivByteLength;
643
+ const sizeChunk = await readable.getSlice(headerDataOffset, 4);
644
+ readBytes += 4;
645
+ const decryptParams = {
646
+ iv: Buffer.from(iv),
647
+ salt: Buffer.from(salt)
648
+ };
649
+ if (params.key) decryptParams.key = params.key;
650
+ if (params.password) decryptParams.password = params.password;
651
+ const peekDecryptor = new AES(decryptParams);
652
+ const sizeDecrypted = peekDecryptor.decrypt(Buffer.from(sizeChunk));
653
+ size = Pack.unpack(">I", sizeDecrypted)[0];
654
+ if (!size) {
655
+ throw new Error("Can not get size of EmbedObject to restore");
656
+ }
657
+ if (size > MAX_HEADER_SIZE) {
658
+ throw new Error("Header is too large to extract");
659
+ }
660
+ const fullEncryptedSize = 4 + size;
661
+ const authTagOffset = headerDataOffset + fullEncryptedSize;
662
+ const authTag = await readable.getSlice(authTagOffset, AES.authTagByteLength);
663
+ readBytes += size + AES.authTagByteLength;
664
+ decryptParams.authTag = Buffer.from(authTag);
665
+ const fullDecryptor = new AES(decryptParams);
666
+ const fullChunk = await readable.getSlice(headerDataOffset, fullEncryptedSize);
667
+ const decrypted = fullDecryptor.decrypt(Buffer.from(fullChunk), true);
668
+ const jsonPayload = decrypted.subarray(4);
669
+ params.object = Convert.bufferToObject(jsonPayload);
670
+ } else {
671
+ const sizeBytes = await readable.getSlice(offset + 2, 4);
672
+ readBytes += 4;
673
+ size = Pack.unpack(">I", sizeBytes)[0];
674
+ if (!size) {
675
+ throw new Error("Can not get size of EmbedObject to restore");
676
+ }
677
+ if (size > MAX_HEADER_SIZE) {
678
+ throw new Error("Header is too large to extract");
679
+ }
680
+ const chunk = await readable.getSlice(offset + 6, size);
681
+ readBytes += size;
682
+ params.object = Convert.bufferToObject(Buffer.from(chunk));
683
+ }
684
+ delete params.iv;
685
+ delete params.salt;
686
+ await readable.close();
687
+ params.readBytes = readBytes;
688
+ return new _EmbedObject(params);
689
+ }
690
+ async writeTo(writable) {
691
+ const binary = this.getBinary();
692
+ await writable.write(binary);
693
+ }
694
+ getBinary() {
695
+ if (this._key || this._password) {
696
+ return this.getEncrypted();
697
+ } else {
698
+ return this.getRaw();
699
+ }
700
+ }
701
+ getRaw() {
702
+ const payload = this._binary;
703
+ const ret = new Uint8Array(payload.length + 2 + 4);
704
+ const firstByte = Convert.randomByteIn(2, 0);
705
+ const secondByte = Convert.randomByteIn(11, 0);
706
+ ret.set([firstByte], 0);
707
+ ret.set([secondByte], 1);
708
+ ret.set(Pack.pack(">I", [payload.length]), 2);
709
+ ret.set(payload, 6);
710
+ return ret;
711
+ }
712
+ getEncrypted() {
713
+ const encryptor = this.getEncryptor();
714
+ const packedSize = Pack.pack(">I", [this._binary.length]);
715
+ const plaintext = Buffer.concat([Buffer.from(packedSize), this._binary]);
716
+ const ciphertext = encryptor.encrypt(plaintext, true);
717
+ const iv = this.getIV();
718
+ const salt = encryptor.getSalt() || Buffer.alloc(AES.saltByteLength);
719
+ const authTag = encryptor.getAuthTag();
720
+ const ret = new Uint8Array(2 + salt.length + iv.length + ciphertext.length + authTag.length);
721
+ const firstByte = Convert.randomByteIn(2, 1);
722
+ const secondByte = Convert.randomByteIn(11, 0);
723
+ ret.set([firstByte], 0);
724
+ ret.set([secondByte], 1);
725
+ let pos = 2;
726
+ ret.set(salt, pos);
727
+ pos += salt.length;
728
+ ret.set(iv, pos);
729
+ pos += iv.length;
730
+ ret.set(ciphertext, pos);
731
+ pos += ciphertext.length;
732
+ ret.set(authTag, pos);
733
+ return ret;
734
+ }
735
+ };
736
+
737
+ // src/node/Readable.ts
738
+ import fs from "fs/promises";
739
+ var Readable = class {
740
+ constructor(params = {}) {
741
+ this._prepared = false;
742
+ this._size = 0;
743
+ this._fp = null;
744
+ this._filename = params.filename;
745
+ }
746
+ isPrepared() {
747
+ return this._prepared;
748
+ }
749
+ async prepare() {
750
+ if (this._prepared) {
751
+ return;
752
+ }
753
+ if (this._filename) {
754
+ this._fp = await fs.open(this._filename, "r");
755
+ const stats = await this._fp.stat();
756
+ this._size = stats.size;
757
+ }
758
+ this._prepared = true;
759
+ }
760
+ async close() {
761
+ if (this._fp) {
762
+ try {
763
+ await this._fp.close();
764
+ } catch {
765
+ }
766
+ }
767
+ this._fp = null;
768
+ this._prepared = false;
769
+ }
770
+ async getSlice(offset, length) {
771
+ if (!this._prepared) {
772
+ await this.prepare();
773
+ }
774
+ const ret = new Uint8Array(length);
775
+ await this._fp.read(ret, 0, length, offset);
776
+ return ret;
777
+ }
778
+ async size() {
779
+ if (!this._prepared) {
780
+ await this.prepare();
781
+ }
782
+ return this._size;
783
+ }
784
+ };
785
+
786
+ // src/node/Writable.ts
787
+ import fs2 from "fs/promises";
788
+
789
+ // src/utils.ts
790
+ import tmp from "tmp";
791
+ function tmpFileSync() {
792
+ const tmpobj = tmp.fileSync();
793
+ return tmpobj.name;
794
+ }
795
+
796
+ // src/node/Writable.ts
797
+ var Writable = class {
798
+ constructor(params = {}) {
799
+ this._uint8Array = new Uint8Array([]);
800
+ this._prepared = false;
801
+ this._bytesWrote = 0;
802
+ this._fp = null;
803
+ if (params.filename) {
804
+ this._filename = params.filename;
805
+ }
806
+ }
807
+ size() {
808
+ if (this._filename) {
809
+ return this._bytesWrote;
810
+ }
811
+ return this._uint8Array.length;
812
+ }
813
+ async prepare() {
814
+ if (this._prepared) {
815
+ return;
816
+ }
817
+ if (this._filename) {
818
+ this._fp = await fs2.open(this._filename, "w");
819
+ }
820
+ this._prepared = true;
821
+ }
822
+ async close() {
823
+ if (this._fp) {
824
+ try {
825
+ await this._fp.close();
826
+ } catch {
827
+ }
828
+ this._fp = null;
829
+ this._prepared = false;
830
+ }
831
+ }
832
+ async saveToFile(filename) {
833
+ await fs2.writeFile(filename, this._uint8Array);
834
+ }
835
+ async write(append) {
836
+ if (!this._prepared) {
837
+ await this.prepare();
838
+ }
839
+ const data = append instanceof Uint8Array ? append : Uint8Array.from(append);
840
+ if (this._fp) {
841
+ await this._fp.write(data, 0, data.length);
842
+ this._bytesWrote += data.length;
843
+ } else {
844
+ const ret = new Uint8Array(this._uint8Array.length + data.length);
845
+ ret.set(this._uint8Array, 0);
846
+ ret.set(data, this._uint8Array.length);
847
+ this._bytesWrote += data.length;
848
+ this._uint8Array = ret;
849
+ }
850
+ }
851
+ async toReadable() {
852
+ if (this._filename) {
853
+ await this.close();
854
+ return new Readable({ filename: this._filename });
855
+ } else {
856
+ const tmpName = tmpFileSync();
857
+ await this.saveToFile(tmpName);
858
+ await this.close();
859
+ return new Readable({ filename: tmpName });
860
+ }
861
+ }
862
+ };
863
+
864
+ // src/EmbedBinary.ts
865
+ var EmbedBinary = class {
866
+ constructor(params = {}) {
867
+ this._encryptor = null;
868
+ this._readable = null;
869
+ if (params.readable) {
870
+ this._readable = params.readable;
871
+ } else if (params.filename && !params.file) {
872
+ this._readable = new Readable({ filename: params.filename });
873
+ } else if (params.file) {
874
+ this._readable = new Readable({ filename: params.file.name });
875
+ }
876
+ this._key = params.key || null;
877
+ this._password = params.password || null;
878
+ this._iv = params.iv || null;
879
+ this._salt = params.salt || null;
880
+ }
881
+ async getExpectedSize() {
882
+ if (!this._readable) {
883
+ throw new Error("No readable source. Provide a filename, file, or readable");
884
+ }
885
+ const readableSize = await this._readable.size();
886
+ if (this._key || this._password) {
887
+ return 2 + AES.saltByteLength + AES.ivByteLength + readableSize + AES.authTagByteLength;
888
+ } else {
889
+ return 2 + readableSize;
890
+ }
891
+ }
892
+ getEncryptor() {
893
+ if (this._encryptor) {
894
+ return this._encryptor;
895
+ }
896
+ this._encryptor = new AES({
897
+ key: this._key || void 0,
898
+ password: this._password || void 0,
899
+ iv: this._iv || void 0,
900
+ salt: this._salt || void 0
901
+ });
902
+ if (!this._iv) {
903
+ this._iv = this._encryptor.getIV();
904
+ }
905
+ if (!this._salt) {
906
+ this._salt = this._encryptor.getSalt();
907
+ }
908
+ return this._encryptor;
909
+ }
910
+ getIV() {
911
+ if (!this._iv) {
912
+ throw new Error("IV is not yet ready. Run getEncryptor() first, or specify one yourself");
913
+ }
914
+ return this._iv;
915
+ }
916
+ static async restoreFromReadable(readable, params = {}, offset = 0, size = null, writable = null) {
917
+ const firstByte = (await readable.getSlice(offset + 0, 1))[0];
918
+ const typeByte = (await readable.getSlice(offset + 1, 1))[0];
919
+ if (!size) {
920
+ size = await readable.size() - offset;
921
+ }
922
+ let decryptor = null;
923
+ if (Convert.isByteIn(firstByte, 2, 1)) {
924
+ let readOffset = offset + 2;
925
+ const salt = await readable.getSlice(readOffset, AES.saltByteLength);
926
+ readOffset += AES.saltByteLength;
927
+ const iv = await readable.getSlice(readOffset, AES.ivByteLength);
928
+ const authTagOffset = offset + size - AES.authTagByteLength;
929
+ const authTag = await readable.getSlice(authTagOffset, AES.authTagByteLength);
930
+ const decryptParams = {
931
+ iv: Buffer.from(iv),
932
+ salt: Buffer.from(salt),
933
+ authTag: Buffer.from(authTag)
934
+ };
935
+ if (params.key) decryptParams.key = params.key;
936
+ if (params.password) decryptParams.password = params.password;
937
+ decryptor = new AES(decryptParams);
938
+ }
939
+ if (Convert.isByteIn(typeByte, 11, 1)) {
940
+ if (!writable) {
941
+ writable = new Writable();
942
+ }
943
+ let bodySize = size - 2;
944
+ let bodyOffset = offset + 2;
945
+ if (decryptor) {
946
+ bodySize = bodySize - AES.saltByteLength - AES.ivByteLength - AES.authTagByteLength;
947
+ bodyOffset = offset + 2 + AES.saltByteLength + AES.ivByteLength;
948
+ }
949
+ for (let i = 0; i < bodySize; i += BUFFER_SIZE) {
950
+ const copySize = Math.min(BUFFER_SIZE, bodySize - i);
951
+ const chunk = await readable.getSlice(bodyOffset + i, copySize);
952
+ if (decryptor) {
953
+ const isLast = i + copySize >= bodySize;
954
+ const decrypted = decryptor.decrypt(Buffer.from(chunk), isLast);
955
+ await writable.write(decrypted);
956
+ } else {
957
+ await writable.write(chunk);
958
+ }
959
+ }
960
+ if (decryptor && bodySize === 0) {
961
+ const final = decryptor.decrypt(null, true);
962
+ if (final.length > 0) {
963
+ await writable.write(final);
964
+ }
965
+ }
966
+ await readable.close();
967
+ return writable;
968
+ }
969
+ throw new Error("Unknown embed type");
970
+ }
971
+ async writeTo(writable) {
972
+ if (!this._readable) return;
973
+ let encryptor = null;
974
+ let firstByte = Convert.randomByteIn(2, 0);
975
+ if (this._key || this._password) {
976
+ encryptor = this.getEncryptor();
977
+ firstByte = Convert.randomByteIn(2, 1);
978
+ }
979
+ await writable.write([firstByte]);
980
+ await writable.write([Convert.randomByteIn(11, 1)]);
981
+ if (encryptor) {
982
+ await writable.write(this._encryptor.getSalt() || Buffer.alloc(AES.saltByteLength));
983
+ await writable.write(this.getIV());
984
+ }
985
+ const bodySize = await this._readable.size();
986
+ for (let i = 0; i < bodySize; i += BUFFER_SIZE) {
987
+ const copySize = Math.min(BUFFER_SIZE, bodySize - i);
988
+ const chunk = await this._readable.getSlice(i, copySize);
989
+ if (encryptor) {
990
+ const isLast = i + copySize >= bodySize;
991
+ const encrypted = encryptor.encrypt(Buffer.from(chunk), isLast);
992
+ await writable.write(encrypted);
993
+ } else {
994
+ await writable.write(chunk);
995
+ }
996
+ }
997
+ if (encryptor) {
998
+ await writable.write(encryptor.getAuthTag());
999
+ }
1000
+ await this._readable.close();
1001
+ }
1002
+ };
1003
+
1004
+ // src/Embed.ts
1005
+ var Embed = class {
1006
+ constructor(params = {}) {
1007
+ this._files = [];
1008
+ this._headerEmbed = null;
1009
+ this._publicHeaderEmbed = null;
1010
+ this.hasEncryptedFiles = false;
1011
+ this.hasPublicFiles = false;
1012
+ this._key = params.key || null;
1013
+ this._password = params.password || null;
1014
+ }
1015
+ basename(path) {
1016
+ return ("" + path).split(/[\\/]/).pop() || "";
1017
+ }
1018
+ async addFile(params) {
1019
+ const file = params.file || null;
1020
+ let filename = params.filename || null;
1021
+ const meta = params.meta || null;
1022
+ let isEncrypted = false;
1023
+ if ((this._key || this._password) && params.key !== null && params.password !== null) {
1024
+ isEncrypted = true;
1025
+ }
1026
+ let embedBinary;
1027
+ if (isEncrypted) {
1028
+ embedBinary = new EmbedBinary({
1029
+ filename: filename || void 0,
1030
+ file: file || void 0,
1031
+ key: this._key,
1032
+ password: this._password
1033
+ });
1034
+ } else {
1035
+ embedBinary = new EmbedBinary({
1036
+ filename: filename || void 0,
1037
+ file: file || void 0
1038
+ });
1039
+ }
1040
+ if (!filename && file) {
1041
+ filename = file.name || null;
1042
+ }
1043
+ const fileEntity = {
1044
+ filename,
1045
+ embedBinary,
1046
+ isEncrypted
1047
+ };
1048
+ if (meta) {
1049
+ fileEntity.meta = meta;
1050
+ }
1051
+ this._files.push(fileEntity);
1052
+ }
1053
+ async composeHeader() {
1054
+ const headerObject = { files: [] };
1055
+ const publicHeaderObject = { files: [] };
1056
+ for (const fileEntity of this._files) {
1057
+ const size = await fileEntity.embedBinary.getExpectedSize();
1058
+ const fileRecord = {
1059
+ filename: this.basename(fileEntity.filename || ""),
1060
+ size
1061
+ };
1062
+ if (fileEntity.meta) {
1063
+ fileRecord.meta = fileEntity.meta;
1064
+ }
1065
+ if (fileEntity.isEncrypted) {
1066
+ headerObject.files.push(fileRecord);
1067
+ this.hasEncryptedFiles = true;
1068
+ } else {
1069
+ publicHeaderObject.files.push(fileRecord);
1070
+ this.hasPublicFiles = true;
1071
+ }
1072
+ }
1073
+ this._publicHeaderEmbed = new EmbedObject({ object: publicHeaderObject });
1074
+ this._headerEmbed = new EmbedObject({
1075
+ object: headerObject,
1076
+ key: this._key,
1077
+ password: this._password
1078
+ });
1079
+ return true;
1080
+ }
1081
+ async getExpectedSize() {
1082
+ await this.composeHeader();
1083
+ let size = 0;
1084
+ if (this.hasEncryptedFiles) {
1085
+ size += await this._headerEmbed.getExpectedSize();
1086
+ }
1087
+ if (this.hasPublicFiles) {
1088
+ size += await this._publicHeaderEmbed.getExpectedSize();
1089
+ }
1090
+ const encFiles = this._headerEmbed.object.files;
1091
+ for (const file of encFiles) {
1092
+ size += file.size;
1093
+ }
1094
+ const pubFiles = this._publicHeaderEmbed.object.files;
1095
+ for (const file of pubFiles) {
1096
+ size += file.size;
1097
+ }
1098
+ return size;
1099
+ }
1100
+ async writeTo(writable) {
1101
+ await this.composeHeader();
1102
+ if (this.hasPublicFiles) {
1103
+ await this._publicHeaderEmbed.writeTo(writable);
1104
+ }
1105
+ if (this.hasEncryptedFiles) {
1106
+ await this._headerEmbed.writeTo(writable);
1107
+ }
1108
+ for (const file of this._files) {
1109
+ await file.embedBinary.writeTo(writable);
1110
+ }
1111
+ }
1112
+ async restoreFromReadable(readable, params = {}, offset = 0) {
1113
+ const publicParams = {};
1114
+ Object.assign(publicParams, params);
1115
+ Object.assign(publicParams, { password: null, key: void 0 });
1116
+ let encryptedHeaderOffset = 0;
1117
+ try {
1118
+ this._publicHeaderEmbed = await EmbedObject.restoreFromReadable(
1119
+ readable,
1120
+ publicParams,
1121
+ offset
1122
+ );
1123
+ encryptedHeaderOffset = this._publicHeaderEmbed.readBytes;
1124
+ } catch {
1125
+ this._publicHeaderEmbed = new EmbedObject({ object: { files: [] } });
1126
+ }
1127
+ const pubObj = this._publicHeaderEmbed._object;
1128
+ this.hasPublicFiles = !!(pubObj && pubObj.files && pubObj.files.length);
1129
+ try {
1130
+ this._headerEmbed = await EmbedObject.restoreFromReadable(
1131
+ readable,
1132
+ params,
1133
+ offset + encryptedHeaderOffset
1134
+ );
1135
+ } catch {
1136
+ this._headerEmbed = new EmbedObject({
1137
+ object: { files: [] },
1138
+ key: this._key,
1139
+ password: this._password
1140
+ });
1141
+ }
1142
+ const encObj = this._headerEmbed._object;
1143
+ this.hasEncryptedFiles = !!(encObj && encObj.files && encObj.files.length);
1144
+ }
1145
+ getFilesToExtract() {
1146
+ const filesToExtract = [];
1147
+ let offset = 0;
1148
+ offset += this._publicHeaderEmbed.readBytes;
1149
+ offset += this._headerEmbed.readBytes;
1150
+ const pubFiles = this._publicHeaderEmbed._object.files;
1151
+ for (const fileRecord of pubFiles) {
1152
+ filesToExtract.push({ ...fileRecord, isEncrypted: false, offset });
1153
+ offset += fileRecord.size;
1154
+ }
1155
+ const encFiles = this._headerEmbed._object.files;
1156
+ for (const fileRecord of encFiles) {
1157
+ filesToExtract.push({ ...fileRecord, isEncrypted: true, offset });
1158
+ offset += fileRecord.size;
1159
+ }
1160
+ return filesToExtract;
1161
+ }
1162
+ async restoreBinary(readable, params, n, offset, writable = null) {
1163
+ if (!this._headerEmbed && !this._publicHeaderEmbed) {
1164
+ await this.restoreFromReadable(readable, params, offset);
1165
+ }
1166
+ const filesToExtract = this.getFilesToExtract();
1167
+ if (!filesToExtract[n]) {
1168
+ throw new Error("There is no file " + n + " found in this container");
1169
+ }
1170
+ const fileSize = filesToExtract[n].size;
1171
+ const fileOffset = offset + filesToExtract[n].offset;
1172
+ if (filesToExtract[n].isEncrypted) {
1173
+ return await EmbedBinary.restoreFromReadable(readable, params, fileOffset, fileSize, writable);
1174
+ } else {
1175
+ const publicParams = { ...params, password: void 0, key: void 0 };
1176
+ return await EmbedBinary.restoreFromReadable(readable, publicParams, fileOffset, fileSize, writable);
1177
+ }
1178
+ }
1179
+ };
1180
+
1181
+ // src/MP4.ts
1182
+ import debug from "debug";
1183
+ var log = debug("mp4vault");
1184
+ var MP4 = class {
1185
+ constructor() {
1186
+ this._analyzed = false;
1187
+ this._readable = null;
1188
+ this._embed = null;
1189
+ this._key = null;
1190
+ this._password = null;
1191
+ this._atoms = [];
1192
+ this._initialMdatStart = null;
1193
+ this._initialEmbed = null;
1194
+ }
1195
+ getEmbedFiles() {
1196
+ if (this._initialEmbed) {
1197
+ return this._initialEmbed.getFilesToExtract();
1198
+ }
1199
+ return [];
1200
+ }
1201
+ setKey(key) {
1202
+ this._key = key;
1203
+ this._password = null;
1204
+ }
1205
+ setPassword(password) {
1206
+ this._password = password;
1207
+ this._key = null;
1208
+ }
1209
+ async loadFile(params) {
1210
+ if (!params || !params.filename) {
1211
+ throw new Error("filename is required");
1212
+ }
1213
+ this._readable = new Readable(params);
1214
+ await this.analyzeFile();
1215
+ await this._readable.close();
1216
+ }
1217
+ async embedFile(params) {
1218
+ if (!this._embed) {
1219
+ this._embed = new Embed({ mp4: this, key: this._key, password: this._password });
1220
+ }
1221
+ await this._embed.addFile(params);
1222
+ }
1223
+ async getExpectedSize() {
1224
+ let expectedSize = 0;
1225
+ const ftyp = this.findAtom("ftyp");
1226
+ const mdat = this.findAtom("mdat");
1227
+ const moov = this.findAtom("moov");
1228
+ if (!ftyp || !mdat || !moov) {
1229
+ throw new Error("ftyp, mdat and moov atoms required");
1230
+ }
1231
+ const extendOffset = this._embed ? await this._embed.getExpectedSize() : 0;
1232
+ let mdatOffset = 0;
1233
+ const mdatNewSize = mdat.size - mdat.header_size;
1234
+ mdatOffset += ftyp.size;
1235
+ expectedSize += ftyp.size;
1236
+ const tempH = mdat.header_size;
1237
+ const tempL = mdat.size;
1238
+ if (mdatNewSize <= MAX_INT32) {
1239
+ const freeAtom = new Atom({
1240
+ name: "free",
1241
+ start: 0,
1242
+ size: 8,
1243
+ header_size: 8
1244
+ });
1245
+ expectedSize += freeAtom.size;
1246
+ mdat.size += extendOffset;
1247
+ expectedSize += mdat.header_size;
1248
+ mdatOffset += 8;
1249
+ mdatOffset += 8;
1250
+ } else {
1251
+ mdat.size += extendOffset;
1252
+ mdat.header_size = 16;
1253
+ expectedSize += mdat.header_size;
1254
+ mdatOffset += 16;
1255
+ }
1256
+ mdat.header_size = tempH;
1257
+ mdat.size = tempL;
1258
+ if (this._embed) {
1259
+ expectedSize += await this._embed.getExpectedSize();
1260
+ }
1261
+ expectedSize += mdat.size - mdat.header_size;
1262
+ const shiftOffsets = extendOffset + (mdatOffset - this._initialMdatStart);
1263
+ await this.adjustSampleOffsets(shiftOffsets);
1264
+ expectedSize += moov.size;
1265
+ return expectedSize;
1266
+ }
1267
+ async adjustSampleOffsets(offset) {
1268
+ const sampleAtoms = this.findAtoms(null, "stco").concat(this.findAtoms(null, "co64"));
1269
+ log("adjusting sample offsets by", offset, "stco co64 atoms count:", sampleAtoms.length);
1270
+ for (const atom of sampleAtoms) {
1271
+ const data = await this._readable.getSlice(atom.start + atom.header_size, 8);
1272
+ const unpacked = Pack.unpack(">II", data);
1273
+ const verFlags = unpacked[0];
1274
+ const count = unpacked[1];
1275
+ const sampleOffsets = [];
1276
+ if (atom.name === "stco") {
1277
+ for (let i = 0; i < count; i += 1024) {
1278
+ const cToRead = Math.min(1024, count - i);
1279
+ const readOffsets = Pack.unpack(
1280
+ ">" + "I".repeat(cToRead),
1281
+ await this._readable.getSlice(atom.start + atom.header_size + 8 + i * 4, cToRead * 4)
1282
+ );
1283
+ sampleOffsets.push(...readOffsets);
1284
+ }
1285
+ } else if (atom.name === "co64") {
1286
+ for (let i = 0; i < count; i += 1024) {
1287
+ const cToRead = Math.min(1024, count - i);
1288
+ const readOffsets = Pack.unpack(
1289
+ ">" + "Q".repeat(cToRead),
1290
+ await this._readable.getSlice(atom.start + atom.header_size + 8 + i * 8, cToRead * 8)
1291
+ );
1292
+ sampleOffsets.push(...readOffsets);
1293
+ }
1294
+ }
1295
+ for (let i = 0; i < sampleOffsets.length; i++) {
1296
+ sampleOffsets[i] = sampleOffsets[i] + offset;
1297
+ if (atom.name === "stco" && sampleOffsets[i] >= MAX_INT32) {
1298
+ atom.name = "co64";
1299
+ }
1300
+ }
1301
+ if (atom.name === "stco") {
1302
+ atom.contents = Pack.pack(">II", [verFlags, count]).concat(
1303
+ Pack.pack(">" + "I".repeat(count), sampleOffsets)
1304
+ );
1305
+ atom.size = atom.contents.length + 8;
1306
+ } else {
1307
+ atom.contents = Pack.pack(">II", [verFlags, count]).concat(
1308
+ Pack.pack(">" + "Q".repeat(count), sampleOffsets)
1309
+ );
1310
+ atom.size = atom.contents.length + 8;
1311
+ }
1312
+ atom.readable = null;
1313
+ }
1314
+ }
1315
+ async extractEmbedHeader() {
1316
+ const mdat = this.findAtom("mdat");
1317
+ const offset = mdat.start + mdat.header_size;
1318
+ this._initialEmbed = new Embed({ mp4: this });
1319
+ await this._initialEmbed.restoreFromReadable(
1320
+ this._readable,
1321
+ { key: this._key || void 0, password: this._password },
1322
+ offset
1323
+ );
1324
+ }
1325
+ async extractFile(n, writable = null) {
1326
+ const mdat = this.findAtom("mdat");
1327
+ const offset = mdat.start + mdat.header_size;
1328
+ return await this._initialEmbed.restoreBinary(
1329
+ this._readable,
1330
+ { key: this._key || void 0, password: this._password || void 0 },
1331
+ n,
1332
+ offset,
1333
+ writable
1334
+ );
1335
+ }
1336
+ async embed(writable) {
1337
+ const ftyp = this.findAtom("ftyp");
1338
+ const mdat = this.findAtom("mdat");
1339
+ const moov = this.findAtom("moov");
1340
+ if (!ftyp || !mdat || !moov) {
1341
+ throw new Error("ftyp, mdat and moov atoms required");
1342
+ }
1343
+ if (!writable) {
1344
+ writable = new Writable();
1345
+ }
1346
+ const extendOffset = this._embed ? await this._embed.getExpectedSize() : 0;
1347
+ let mdatOffset = 0;
1348
+ const mdatNewSize = mdat.size - mdat.header_size;
1349
+ await ftyp.write(writable);
1350
+ mdatOffset += ftyp.size;
1351
+ const tempH = mdat.header_size;
1352
+ const tempL = mdat.size;
1353
+ if (mdatNewSize <= MAX_INT32) {
1354
+ const freeAtom = new Atom({
1355
+ name: "free",
1356
+ start: 0,
1357
+ size: 8,
1358
+ header_size: 8
1359
+ });
1360
+ await freeAtom.write(writable);
1361
+ mdat.size += extendOffset;
1362
+ await mdat.writeHeader(writable);
1363
+ mdatOffset += 8;
1364
+ mdatOffset += 8;
1365
+ } else {
1366
+ mdat.size += extendOffset;
1367
+ mdat.header_size = 16;
1368
+ await mdat.writeHeader(writable);
1369
+ mdatOffset += 16;
1370
+ }
1371
+ log("writing mdat atom start", mdatOffset);
1372
+ mdat.header_size = tempH;
1373
+ mdat.size = tempL;
1374
+ if (this._embed) {
1375
+ await this._embed.writeTo(writable);
1376
+ }
1377
+ await mdat.writePayload(writable);
1378
+ const shiftOffsets = extendOffset + (mdatOffset - this._initialMdatStart);
1379
+ await this.adjustSampleOffsets(shiftOffsets);
1380
+ await moov.write(writable);
1381
+ await this._readable.close();
1382
+ await writable.close();
1383
+ return writable;
1384
+ }
1385
+ async analyzeFile() {
1386
+ this._atoms = [];
1387
+ const size = await this._readable.size();
1388
+ await this.parseAtoms(0, size, null);
1389
+ this._analyzed = true;
1390
+ const mdat = this.findAtom("mdat");
1391
+ this._initialMdatStart = mdat.start + mdat.header_size;
1392
+ log("initial mdat atom start", this._initialMdatStart);
1393
+ try {
1394
+ await this.extractEmbedHeader();
1395
+ } catch {
1396
+ }
1397
+ return this._atoms;
1398
+ }
1399
+ printAtoms(atoms, level = 0) {
1400
+ atoms = atoms || this._atoms;
1401
+ for (const a of atoms) {
1402
+ console.log(a.start, "".padStart(level, "-"), a.name, a.size, a.header_size);
1403
+ if (a.children.length) {
1404
+ this.printAtoms(a.children, level + 1);
1405
+ }
1406
+ }
1407
+ }
1408
+ findAtom(name) {
1409
+ if (!this._analyzed) {
1410
+ throw new Error("Run await analyzeFile() first");
1411
+ }
1412
+ const atoms = this.findAtoms(null, name);
1413
+ return atoms.length ? atoms[0] : null;
1414
+ }
1415
+ findAtoms(atoms, name) {
1416
+ if (!this._analyzed) {
1417
+ throw new Error("Run await analyzeFile() first");
1418
+ }
1419
+ atoms = atoms || this._atoms;
1420
+ let ret = [];
1421
+ for (const a of atoms) {
1422
+ if (a.name === name) {
1423
+ ret.push(a);
1424
+ }
1425
+ if (a.children.length) {
1426
+ ret = ret.concat(this.findAtoms(a.children, name));
1427
+ }
1428
+ }
1429
+ return ret;
1430
+ }
1431
+ async parseAtoms(start, end, mother) {
1432
+ let offset = start;
1433
+ while (offset < end) {
1434
+ let atomSize = Pack.unpack(">I", await this._readable.getSlice(offset, 4))[0];
1435
+ const atomType = Pack.unpack(">4s", await this._readable.getSlice(offset + 4, 4))[0];
1436
+ let atomHeaderSize;
1437
+ if (atomSize === 1) {
1438
+ atomSize = Pack.unpack(">Q", await this._readable.getSlice(offset + 8, 8))[0];
1439
+ atomHeaderSize = 16;
1440
+ } else {
1441
+ atomHeaderSize = 8;
1442
+ if (atomSize === 0) {
1443
+ atomSize = end - offset;
1444
+ }
1445
+ }
1446
+ const atom = new Atom({
1447
+ readable: this._readable,
1448
+ name: String(atomType),
1449
+ start: offset,
1450
+ size: atomSize,
1451
+ header_size: atomHeaderSize,
1452
+ mother
1453
+ });
1454
+ if (mother) {
1455
+ mother.children.push(atom);
1456
+ } else {
1457
+ this._atoms.push(atom);
1458
+ }
1459
+ if (["moov", "trak", "mdia", "minf", "stbl", "edts", "udta"].includes(String(atomType))) {
1460
+ await this.parseAtoms(offset + atomHeaderSize, offset + atomSize, atom);
1461
+ }
1462
+ offset = offset + atomSize;
1463
+ }
1464
+ return this._atoms;
1465
+ }
1466
+ };
1467
+ export {
1468
+ AES,
1469
+ Atom,
1470
+ Convert,
1471
+ Embed,
1472
+ EmbedBinary,
1473
+ EmbedObject,
1474
+ MP4,
1475
+ Pack,
1476
+ Readable,
1477
+ Writable
1478
+ };
1479
+ //# sourceMappingURL=index.js.map