diff --git a/lib/mp4/AtomToken.ts b/lib/mp4/AtomToken.ts index 2fe7d490a..43f206822 100644 --- a/lib/mp4/AtomToken.ts +++ b/lib/mp4/AtomToken.ts @@ -489,7 +489,7 @@ const stsdHeader: IGetToken = { export interface ISampleDescription { dataFormat: string; dataReferenceIndex: number; - description: Uint8Array; + description: Uint8Array | undefined; } export interface IAtomStsd { @@ -499,7 +499,7 @@ export interface IAtomStsd { /** * Atom: Sample Description Atom ('stsd') - * Ref: https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25691 + * Ref: https://developer.apple.com/documentation/quicktime-file-format/sample_description_atom */ class SampleDescriptionTable implements IGetToken { @@ -507,11 +507,11 @@ class SampleDescriptionTable implements IGetToken { } public get(buf: Uint8Array, off: number): ISampleDescription { - + const descrLen = this.len - 12; return { dataFormat: FourCcToken.get(buf, off), dataReferenceIndex: Token.UINT16_BE.get(buf, off + 10), - description: new Token.Uint8ArrayType(this.len - 12).get(buf, off + 12) + description: descrLen > 0 ? new Token.Uint8ArrayType(descrLen).get(buf, off + 12) : undefined }; } } @@ -535,7 +535,7 @@ export class StsdAtom implements IGetToken { for (let n = 0; n < header.numberOfEntries; ++n) { const size = Token.UINT32_BE.get(buf, off); // Sample description size off += Token.UINT32_BE.len; - table.push(new SampleDescriptionTable(size).get(buf, off)); + table.push(new SampleDescriptionTable(size - Token.UINT32_BE.len).get(buf, off)); off += size; } diff --git a/lib/mp4/MP4Parser.ts b/lib/mp4/MP4Parser.ts index 47fdf99de..ea3363c29 100644 --- a/lib/mp4/MP4Parser.ts +++ b/lib/mp4/MP4Parser.ts @@ -5,11 +5,11 @@ import { BasicParser } from '../common/BasicParser.js'; import { Genres } from '../id3v1/ID3v1Parser.js'; import { Atom } from './Atom.js'; import * as AtomToken from './AtomToken.js'; +import { Mp4ContentError } from './AtomToken.js'; import { type AnyTagValue, type IChapter, type ITrackInfo, TrackType } from '../type.js'; import type { IGetToken } from '@tokenizer/token'; import { uint8ArrayToHex, uint8ArrayToString } from 'uint8array-extras'; -import { Mp4ContentError } from './AtomToken.js'; const debug = initDebug('music-metadata:parser:MP4'); const tagFormat = 'iTunes'; @@ -545,14 +545,16 @@ export class MP4Parser extends BasicParser { }; let offset = 0; - const version = AtomToken.SoundSampleDescriptionVersion.get(sampleDescription.description, offset); - offset += AtomToken.SoundSampleDescriptionVersion.len; - - if (version.version === 0 || version.version === 1) { - // Sound Sample Description (Version 0) - ssd.description = AtomToken.SoundSampleDescriptionV0.get(sampleDescription.description, offset); - } else { - debug(`Warning: sound-sample-description ${version} not implemented`); + if (sampleDescription.description) { + const version = AtomToken.SoundSampleDescriptionVersion.get(sampleDescription.description, offset); + offset += AtomToken.SoundSampleDescriptionVersion.len; + + if (version.version === 0 || version.version === 1) { + // Sound Sample Description (Version 0) + ssd.description = AtomToken.SoundSampleDescriptionV0.get(sampleDescription.description, offset); + } else { + debug(`Warning: sound-sample-description ${version} not implemented`); + } } return ssd; } diff --git a/test/samples/mp4/frag_bunny.mp4 b/test/samples/mp4/frag_bunny.mp4 new file mode 100644 index 000000000..78bd6ad8b Binary files /dev/null and b/test/samples/mp4/frag_bunny.mp4 differ diff --git a/test/test-file-mp4.ts b/test/test-file-mp4.ts index 7816b99d2..a55221571 100644 --- a/test/test-file-mp4.ts +++ b/test/test-file-mp4.ts @@ -473,4 +473,14 @@ describe('Parse MPEG-4 files with iTunes metadata', () => { assert.equal(mm.ratingToStars(common.rating[0].rating), 4, 'Vorbis tag rating conversion'); }); + it('\'stsd\' atom: Handle empty sample entry description', async () => { + + const filePath = path.join(mp4Samples, 'frag_bunny.mp4'); + const {format, common} = await mm.parseFile(filePath); + + assert.strictEqual(format.container, 'mp42/avc1/iso5', 'format.container'); + assert.strictEqual(format.codec, 'MPEG-4/AAC', 'format.codec'); + assert.strictEqual(common.title, undefined, 'common.title'); + }); + });