import * as Token from 'token-types' ; import * as _debug from 'debug' ; import { IOptions } from '../../type' ; import { INativeMetadataCollector } from '../../common/MetadataCollector' ; import * as Ogg from '../Ogg' ; import { VorbisDecoder } from './VorbisDecoder' ; import { CommonHeader , IdentificationHeader , IVorbisPicture , VorbisPictureToken } from './Vorbis' ; const debug = _debug ( 'music-metadata:parser:ogg:vorbis1' ) ; /**
 * Vorbis 1 Parser.
 * Used by OggParser
 */ export class VorbisParser implements Ogg . IPageConsumer { private pageSegments : Buffer [ ] = [ ] ; constructor ( protected metadata , protected options ) { } param header param pageData public parsePage ( header , pageData ) { if ( header . headerType . firstPage ) { this . parseFirstPage ( header , pageData ) ; } else { if ( header . headerType . continued ) { if ( this . pageSegments . length === 0 ) { throw new Error ( "Cannot continue on previous page" ) ; } this . pageSegments . push ( pageData ) ; } if ( header . headerType . lastPage || ! header . headerType . continued ) { if ( this . pageSegments . length > 0 ) { const fullPage = Buffer . concat ( this . pageSegments ) ; this . parseFullPage ( fullPage ) ; } this . pageSegments = header . headerType . lastPage ? [ ] : [ pageData ] ; } } if ( header . headerType . lastPage ) { this . calculateDuration ( header ) ; } } public flush ( ) { this . parseFullPage ( Buffer . concat ( this . pageSegments ) ) ; } public parseUserComment ( pageData , offset ) { const decoder = new VorbisDecoder ( pageData , offset ) ; const tag = decoder . parseUserComment ( ) ; this . addTag ( tag . key , tag . value ) ; return tag . len ; } public addTag ( id , value ) { if ( id === 'METADATA_BLOCK_PICTURE' && ( typeof value === 'string' ) ) { if ( this . options . skipCovers ) { debug ( `Ignore picture` ) ; return ; } value = VorbisPictureToken . fromBase64 ( value ) ; debug ( `Push picture: id=${ id }, format=${ value . format }` ) ; } else { debug ( `Push tag: id=${ id }, value=${ value }` ) ; } this . metadata . addTag ( 'vorbis' , id , value ) ; } public calculateDuration ( header ) { if ( this . metadata . format . sampleRate && header . absoluteGranulePosition >= 0 ) { this . metadata . setFormat ( 'numberOfSamples' , header . absoluteGranulePosition ) ; this . metadata . setFormat ( 'duration' , this . metadata . format . numberOfSamples / this . metadata . format . sampleRate ) ; } } param { IPageHeader } header param { Buffer } pageData protected parseFirstPage ( header , pageData ) { this . metadata . setFormat ( 'codec' , 'Vorbis I' ) ; debug ( "Parse first page" ) ; const commonHeader = CommonHeader . get ( pageData , 0 ) ; if ( commonHeader . vorbis !== 'vorbis' ) throw new Error ( 'Metadata does not look like Vorbis' ) ; if ( commonHeader . packetType === 1 ) { const idHeader = IdentificationHeader . get ( pageData , CommonHeader . len ) ; this . metadata . setFormat ( 'sampleRate' , idHeader . sampleRate ) ; this . metadata . setFormat ( 'bitrate' , idHeader . bitrateNominal ) ; this . metadata . setFormat ( 'numberOfChannels' , idHeader . channelMode ) ; debug ( "sample-rate=%s[hz], bitrate=%s[b/s], channel-mode=%s" , idHeader . sampleRate , idHeader . bitrateNominal , idHeader . channelMode ) ; } else throw new Error ( 'First Ogg page should be type 1: the identification header' ) ; } protected parseFullPage ( pageData ) { const commonHeader = CommonHeader . get ( pageData , 0 ) ; debug ( "Parse full page: type=%s, byteLength=%s" , commonHeader . packetType , pageData . byteLength ) ; switch ( commonHeader . packetType ) { case 3 : return this . parseUserCommentList ( pageData , CommonHeader . len ) ; case 1 : case 5 : break ; } } /**
     * Ref: https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-840005.2
     */ protected parseUserCommentList ( pageData , offset ) { const strLen = Token . UINT32_LE . get ( pageData , offset ) ; offset += 4 ; offset += strLen ; let userCommentListLength = Token . UINT32_LE . get ( pageData , offset ) ; offset += 4 ; while ( userCommentListLength -- > 0 ) { offset += this . parseUserComment ( pageData , offset ) ; } } }