import { ITokenizer } from 'strtok3/lib/core' ; import * as initDebug from 'debug' ; import * as AtomToken from './AtomToken' ; export type AtomDataHandler = ( atom : Atom , remaining : number ) => Promise < void > ; const debug = initDebug ( 'music-metadata:parser:MP4:Atom' ) ; export class Atom { public static async readAtom ( tokenizer , dataHandler , parent , remaining ) { const offset = tokenizer . position ; const header = await tokenizer . readToken < AtomToken . IAtomHeader > ( AtomToken . Header ) ; const extended = header . length === BigInt ( 1 ) ; if ( extended ) { header . length = await tokenizer . readToken < bigint > ( AtomToken . ExtendedSize ) ; } const atomBean = new Atom ( header , header . length === BigInt ( 1 ) , parent ) ; const payloadLength = atomBean . getPayloadLength ( remaining ) ; debug ( `parse atom name=${ atomBean . atomPath }, extended=${ atomBean . extended }, offset=${ offset }, len=${ atomBean . header . length }` ) ; await atomBean . readData ( tokenizer , dataHandler , payloadLength ) ; return atomBean ; } public readonly children : Atom [ ] ; public readonly atomPath : string ; public constructor ( public readonly header , public extended , public readonly parent ) { this . children = [ ] ; this . atomPath = ( this . parent ? this . parent . atomPath + '.' : '' ) + this . header . name ; } public getHeaderLength ( ) { return this . extended ? 16 : 8 ; } public getPayloadLength ( remaining ) { return ( this . header . length === BigInt ( 0 ) ? remaining : Number ( this . header . length ) ) - this . getHeaderLength ( ) ; } public async readAtoms ( tokenizer , dataHandler , size ) { while ( size > 0 ) { const atomBean = await Atom . readAtom ( tokenizer , dataHandler , this , size ) ; this . children . push ( atomBean ) ; size -= atomBean . header . length === BigInt ( 0 ) ? size : Number ( atomBean . header . length ) ; } } private async readData ( tokenizer , dataHandler , remaining ) { switch ( this . header . name ) { case 'moov' : case 'udta' : case 'trak' : case 'mdia' : case 'minf' : case 'stbl' : case '<id>' : case 'ilst' : case 'tref' : return this . readAtoms ( tokenizer , dataHandler , this . getPayloadLength ( remaining ) ) ; case 'meta' : await tokenizer . ignore ( 4 ) ; return this . readAtoms ( tokenizer , dataHandler , this . getPayloadLength ( remaining ) - 4 ) ; case 'mdhd' : case 'mvhd' : case 'tkhd' : case 'stsz' : case 'mdat' : default : return dataHandler ( this , remaining ) ; } } }