@audio/decode-mp4 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/decode-mp4.js +24 -19
  2. package/package.json +1 -1
package/decode-mp4.js CHANGED
@@ -320,7 +320,7 @@ const DTS_TYPES = new Set(['dtsc', 'dtsh', 'dtsl', 'dtse']) // DTS-HD variants c
320
320
 
321
321
  async function createCodec({ entry, children, movie }) {
322
322
  let { type } = entry
323
- // AAC and ALAC trim the encoder's priming and padding by the track's edit list or iTunSMPB, MP3 by the edit list (gapless)
323
+ // AAC and ALAC trim the encoder's priming and padding by the track's edit list or iTunSMPB, MP3 and Opus by the edit list (gapless)
324
324
  let boxes = { elst: children.elst, mdhd: children.mdhd, stts: children.stts, mvhd: movie?.mvhd, ilst: movie?.ilst ?? children.ilst }
325
325
  if (type === 'mp4a') {
326
326
  let { oti, dsi } = children.esds ? parseEsds(children.esds) : {}
@@ -345,7 +345,7 @@ async function createCodec({ entry, children, movie }) {
345
345
  }
346
346
  if (type === 'Opus') {
347
347
  if (!children.dOps) throw Error('MP4 Opus track has no dOps box')
348
- return opus(children.dOps)
348
+ return opus(children.dOps, boxes)
349
349
  }
350
350
  if (type === 'samr' || type === 'sawb') return amr(type === 'sawb')
351
351
  if (type === 'ac-3') return frames(import('@audio/decode-ac3'))
@@ -403,20 +403,22 @@ async function frames(load) {
403
403
  // encoder and decoder delay (1105 for LAME, as ffmpeg writes it) and the padding. decode-aac reads the window.
404
404
  async function mp3(boxes) {
405
405
  let [codec, { gapless }] = await Promise.all([frames(import('@audio/decode-mp3')), import('@audio/decode-aac')])
406
- let g = gapless?.(boxes)
406
+ return windowed(codec, gapless?.(boxes))
407
+ }
408
+
409
+ // the edit list's window { start, duration } (seconds) over a codec that decodes from the media's first sample
410
+ function windowed(codec, g) {
407
411
  if (!g) return codec
408
412
  let skip = null, left = null
409
- return {
410
- feed(frames) {
411
- let r = codec.feed(frames), n = r.channelData?.[0]?.length
412
- if (!n) return r
413
- skip ??= Math.round(g.start * r.sampleRate); left ??= g.duration == null ? Infinity : Math.round(g.duration * r.sampleRate)
414
- let a = Math.min(n, skip), b = Math.min(n, a + left)
415
- skip -= a; left -= b - a
416
- return b <= a ? EMPTY : a || b < n ? { ...r, channelData: r.channelData.map(c => c.subarray(a, b)) } : r
417
- },
418
- flush: codec.flush, free: codec.free
413
+ let cut = r => {
414
+ let n = r.channelData?.[0]?.length
415
+ if (!n) return r
416
+ skip ??= Math.round(g.start * r.sampleRate); left ??= g.duration == null ? Infinity : Math.round(g.duration * r.sampleRate)
417
+ let a = Math.min(n, skip), b = Math.min(n, a + left)
418
+ skip -= a; left -= b - a
419
+ return b <= a ? EMPTY : a || b < n ? { ...r, channelData: r.channelData.map(c => c.subarray(a, b)) } : r
419
420
  }
421
+ return { feed: frames => cut(codec.feed(frames)), flush: () => cut(codec.flush()), free: codec.free }
420
422
  }
421
423
 
422
424
  async function flac(blocks) {
@@ -434,17 +436,20 @@ async function flac(blocks) {
434
436
  // as decode-opus reorders Ogg Opus: output channel j takes the coded channel of Vorbis position OPUS_ORDER[n][j]
435
437
  const OPUS_ORDER = { 3: [0, 2, 1], 5: [0, 2, 1, 3, 4], 6: [0, 2, 1, 5, 3, 4], 7: [0, 2, 1, 6, 5, 3, 4], 8: [0, 2, 1, 7, 5, 6, 3, 4] }
436
438
 
437
- async function opus(dOps) {
438
- let channels = dOps[1]
439
- let opts = { channels, sampleRate: 48000, preSkip: r16(dOps, 2), outputGain: (r16(dOps, 8) << 16) >> 16 }
439
+ // Opus in ISOBMFF: the edit list's media_time covers PreSkip and its duration is the real length, so with an edit
440
+ // list the window trims both ends from the media's first sample; without one, dOps' PreSkip trims the start
441
+ async function opus(dOps, boxes) {
442
+ let [{ createOpusDecoder }, { gapless }] = await Promise.all([import('@audio/decode-opus/core'), import('@audio/decode-aac')])
443
+ let g = gapless?.(boxes), channels = dOps[1]
444
+ let opts = { channels, sampleRate: 48000, preSkip: g ? 0 : r16(dOps, 2), outputGain: (r16(dOps, 8) << 16) >> 16 }
440
445
  if (dOps[10] > 0) {
441
446
  opts.streamCount = dOps[11]; opts.coupledStreamCount = dOps[12]
442
447
  let table = Array.from(dOps.subarray(13, 13 + channels))
443
448
  opts.channelMappingTable = dOps[10] === 1 && OPUS_ORDER[channels] ? OPUS_ORDER[channels].map(i => table[i]) : table
444
449
  }
445
- let dec = await (await import('@audio/decode-opus/core')).createOpusDecoder()
450
+ let dec = await createOpusDecoder()
446
451
  dec.configure(opts)
447
- return {
452
+ return windowed({
448
453
  feed: frames => {
449
454
  let r = dec.decodeFrames(frames)
450
455
  if (!r.samplesDecoded) return EMPTY
@@ -452,7 +457,7 @@ async function opus(dOps) {
452
457
  },
453
458
  flush: () => EMPTY,
454
459
  free: () => dec.free()
455
- }
460
+ }, g)
456
461
  }
457
462
 
458
463
  async function amr(wb) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audio/decode-mp4",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Decode the audio track of MP4, MOV, M4A, M4V and 3GP files (AAC, ALAC, MP3, FLAC, Opus, AMR, PCM) to PCM samples",
5
5
  "type": "module",
6
6
  "main": "decode-mp4.js",