import * as strtok3 from 'strtok3/lib/core' ; import * as Token from 'token-types' ; import * as initDebug from 'debug' ; import * as riff from '../riff/RiffChunk' ; import * as WaveChunk from './../wav/WaveChunk' ; import { ID3v2Parser } from '../id3v2/ID3v2Parser' ; import * as util from '../common/Util' ; import { FourCcToken } from '../common/FourCC' ; import { BasicParser } from '../common/BasicParser' ; const debug = initDebug ( 'music-metadata:parser:RIFF' ) ; /**
 * Resource Interchange File Format (RIFF) Parser
 *
 * WAVE PCM soundfile format
 *
 * Ref:
 *  http://www.johnloomis.org/cpe102/asgn/asgn1/riff.html
 *  http://soundfile.sapp.org/doc/WaveFormat
 *
 *  ToDo: Split WAVE part from RIFF parser
 */ export class WaveParser extends BasicParser { private fact : WaveChunk . IFactChunk ; private blockAlign : number ; private header : riff . IChunkHeader ; public async parse ( ) { const riffHeader = await this . tokenizer . readToken < riff . IChunkHeader > ( riff . Header ) ; debug ( `pos=${ this . tokenizer . position }, parse: chunkID=${ riffHeader . chunkID }` ) ; if ( riffHeader . chunkID !== 'RIFF' ) return ; return this . parseRiffChunk ( riffHeader . chunkSize ) . catch ( err => { if ( ! ( err instanceof strtok3 . EndOfStreamError ) ) { throw err ; } } ) ; } public async parseRiffChunk ( chunkSize ) { const type = await this . tokenizer . readToken < string > ( FourCcToken ) ; this . metadata . setFormat ( 'container' , type ) ; switch ( type ) { case 'WAVE' : return this . readWaveChunk ( chunkSize - FourCcToken . len ) ; default : throw new Error ( `Unsupported RIFF format: RIFF/${ type }` ) ; } } public async readWaveChunk ( remaining ) { while ( remaining >= riff . Header . len ) { const header = await this . tokenizer . readToken < riff . IChunkHeader > ( riff . Header ) ; remaining -= riff . Header . len + header . chunkSize ; if ( header . chunkSize > remaining ) { this . metadata . addWarning ( 'Data chunk size exceeds file size' ) ; } this . header = header ; debug ( `pos=${ this . tokenizer . position }, readChunk: chunkID=RIFF/WAVE/${ header . chunkID }` ) ; switch ( header . chunkID ) { case 'LIST' : await this . parseListTag ( header ) ; break ; case 'fact' : this . metadata . setFormat ( 'lossless' , false ) ; this . fact = await this . tokenizer . readToken ( new WaveChunk . FactChunk ( header ) ) ; break ; case 'fmt ' : const fmt = await this . tokenizer . readToken < WaveChunk . IWaveFormat > ( new WaveChunk . Format ( header ) ) ; let subFormat = WaveChunk . WaveFormat [ fmt . wFormatTag ] ; if ( ! subFormat ) { debug ( 'WAVE/non-PCM format=' + fmt . wFormatTag ) ; subFormat = 'non-PCM (' + fmt . wFormatTag + ')' ; } this . metadata . setFormat ( 'codec' , subFormat ) ; this . metadata . setFormat ( 'bitsPerSample' , fmt . wBitsPerSample ) ; this . metadata . setFormat ( 'sampleRate' , fmt . nSamplesPerSec ) ; this . metadata . setFormat ( 'numberOfChannels' , fmt . nChannels ) ; this . metadata . setFormat ( 'bitrate' , fmt . nBlockAlign * fmt . nSamplesPerSec * 8 ) ; this . blockAlign = fmt . nBlockAlign ; break ; case 'id3 ' : case 'ID3 ' : const id3_data = await this . tokenizer . readToken < Uint8Array > ( new Token . Uint8ArrayType ( header . chunkSize ) ) ; const rst = strtok3 . fromBuffer ( id3_data ) ; await new ID3v2Parser ( ) . parse ( this . metadata , rst , this . options ) ; break ; case 'data' : if ( this . metadata . format . lossless !== false ) { this . metadata . setFormat ( 'lossless' , true ) ; } let chunkSize = header . chunkSize ; if ( this . tokenizer . fileInfo . size ) { const calcRemaining = this . tokenizer . fileInfo . size - this . tokenizer . position ; if ( calcRemaining < chunkSize ) { this . metadata . addWarning ( 'data chunk length exceeding file length' ) ; chunkSize = calcRemaining ; } } const numberOfSamples = this . fact ? this . fact . dwSampleLength : ( chunkSize === 0xffffffff ? undefined : chunkSize / this . blockAlign ) ; if ( numberOfSamples ) { this . metadata . setFormat ( 'numberOfSamples' , numberOfSamples ) ; this . metadata . setFormat ( 'duration' , numberOfSamples / this . metadata . format . sampleRate ) ; } this . metadata . setFormat ( 'bitrate' , this . metadata . format . numberOfChannels * this . blockAlign * this . metadata . format . sampleRate ) ; await this . tokenizer . ignore ( header . chunkSize ) ; break ; default : debug ( `Ignore chunk: RIFF/${ header . chunkID } of ${ header . chunkSize } bytes` ) ; this . metadata . addWarning ( 'Ignore chunk: RIFF/' + header . chunkID ) ; await this . tokenizer . ignore ( header . chunkSize ) ; } if ( this . header . chunkSize % 2 === 1 ) { debug ( 'Read odd padding byte' ) ; await this . tokenizer . ignore ( 1 ) ; } } } public async parseListTag ( listHeader ) { const listType = await this . tokenizer . readToken ( new Token . StringType ( 4 , 'binary' ) ) ; debug ( 'pos=%s, parseListTag: chunkID=RIFF/WAVE/LIST/%s' , this . tokenizer . position , listType ) ; switch ( listType ) { case 'INFO' : return this . parseRiffInfoTags ( listHeader . chunkSize - 4 ) ; case 'adtl' : default : this . metadata . addWarning ( 'Ignore chunk: RIFF/WAVE/LIST/' + listType ) ; debug ( 'Ignoring chunkID=RIFF/WAVE/LIST/' + listType ) ; return this . tokenizer . ignore ( listHeader . chunkSize - 4 ) . then ( ) ; } } private async parseRiffInfoTags ( chunkSize ) { while ( chunkSize >= 8 ) { const header = await this . tokenizer . readToken < riff . IChunkHeader > ( riff . Header ) ; const valueToken = new riff . ListInfoTagValue ( header ) ; const value = await this . tokenizer . readToken ( valueToken ) ; this . addTag ( header . chunkID , util . stripNulls ( value ) ) ; chunkSize -= ( 8 + valueToken . len ) ; } if ( chunkSize !== 0 ) { throw Error ( 'Illegal remaining size: ' + chunkSize ) ; } } private addTag ( id , value ) { this . metadata . addTag ( 'exif' , id , value ) ; } }