import * as Token from 'token-types' ; import * as _debug from 'debug' ; import { ITokenizer , IGetToken } from 'strtok3/lib/core' ; import * as util from '../common/Util' ; import { IVorbisPicture , VorbisPictureToken } from '../ogg/vorbis/Vorbis' ; import { AbstractID3Parser } from '../id3v2/AbstractID3Parser' ; import { FourCcToken } from '../common/FourCC' ; import { VorbisParser } from '../ogg/vorbis/VorbisParser' ; import { INativeMetadataCollector } from '../common/MetadataCollector' ; import { IOptions } from '../type' ; import { ITokenParser } from '../ParserFactory' ; import { VorbisDecoder } from '../ogg/vorbis/VorbisDecoder' ; const debug = _debug ( 'music-metadata:parser:FLAC' ) ; /**
 * FLAC supports up to 128 kinds of metadata blocks; currently the following are defined:
 * ref: https://xiph.org/flac/format.html#metadata_block
 */ enum BlockType { STREAMINFO = 0 , PADDING = 1 , APPLICATION = 2 , SEEKTABLE = 3 , VORBIS_COMMENT = 4 , CUESHEET = 5 , PICTURE = 6 } export class FlacParser extends AbstractID3Parser { private vorbisParser : VorbisParser ; private padding : number = 0 ; param { INativeMetadataCollector } metadata param { ITokenizer } tokenizer param { IOptions } options public init ( metadata , tokenizer , options ) { super . init ( metadata , tokenizer , options ) ; this . vorbisParser = new VorbisParser ( metadata , options ) ; return this ; } public async _parse ( ) { const fourCC = await this . tokenizer . readToken < string > ( FourCcToken ) ; if ( fourCC . toString ( ) !== 'fLaC' ) { throw new Error ( 'Invalid FLAC preamble' ) ; } let blockHeader ; do { blockHeader = await this . tokenizer . readToken < IBlockHeader > ( Metadata . BlockHeader ) ; await this . parseDataBlock ( blockHeader ) ; } while ( ! blockHeader . lastBlock ) ; if ( this . tokenizer . fileInfo . size && this . metadata . format . duration ) { const dataSize = this . tokenizer . fileInfo . size - this . tokenizer . position ; this . metadata . setFormat ( 'bitrate' , 8 * dataSize / this . metadata . format . duration ) ; } } private parseDataBlock ( blockHeader ) { debug ( `blockHeader type=${ blockHeader . type }, length=${ blockHeader . length }` ) ; switch ( blockHeader . type ) { case BlockType . STREAMINFO : return this . parseBlockStreamInfo ( blockHeader . length ) ; case BlockType . PADDING : this . padding += blockHeader . length ; break ; case BlockType . APPLICATION : break ; case BlockType . SEEKTABLE : break ; case BlockType . VORBIS_COMMENT : return this . parseComment ( blockHeader . length ) ; case BlockType . CUESHEET : break ; case BlockType . PICTURE : return this . parsePicture ( blockHeader . length ) . then ( ) ; default : this . metadata . addWarning ( 'Unknown block type: ' + blockHeader . type ) ; } return this . tokenizer . ignore ( blockHeader . length ) . then ( ) ; } /**
     * Parse STREAMINFO
     */ private async parseBlockStreamInfo ( dataLen ) { if ( dataLen !== Metadata . BlockStreamInfo . len ) throw new Error ( 'Unexpected block-stream-info length' ) ; const streamInfo = await this . tokenizer . readToken < IBlockStreamInfo > ( Metadata . BlockStreamInfo ) ; this . metadata . setFormat ( 'container' , 'FLAC' ) ; this . metadata . setFormat ( 'codec' , 'FLAC' ) ; this . metadata . setFormat ( 'lossless' , true ) ; this . metadata . setFormat ( 'numberOfChannels' , streamInfo . channels ) ; this . metadata . setFormat ( 'bitsPerSample' , streamInfo . bitsPerSample ) ; this . metadata . setFormat ( 'sampleRate' , streamInfo . sampleRate ) ; if ( streamInfo . totalSamples > 0 ) { this . metadata . setFormat ( 'duration' , streamInfo . totalSamples / streamInfo . sampleRate ) ; } } /**
     * Parse VORBIS_COMMENT
     * Ref: https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-640004.2.3
     */ private async parseComment ( dataLen ) { const data = await this . tokenizer . readToken < Uint8Array > ( new Token . Uint8ArrayType ( dataLen ) ) ; const decoder = new VorbisDecoder ( data , 0 ) ; decoder . readStringUtf8 ( ) ; const commentListLength = decoder . readInt32 ( ) ; for ( let i = 0 ; i < commentListLength ; i ++ ) { const tag = decoder . parseUserComment ( ) ; this . vorbisParser . addTag ( tag . key , tag . value ) ; } } private async parsePicture ( dataLen ) { if ( this . options . skipCovers ) { return this . tokenizer . ignore ( dataLen ) ; } else { const picture = await this . tokenizer . readToken < IVorbisPicture > ( new VorbisPictureToken ( dataLen ) ) ; this . vorbisParser . addTag ( 'METADATA_BLOCK_PICTURE' , picture ) ; } } } /**
 * METADATA_BLOCK_DATA
 * Ref: https://xiph.org/flac/format.html#metadata_block_streaminfo
 */ interface IBlockHeader { lastBlock : boolean ; type : BlockType ; length : number ; } /**
 * METADATA_BLOCK_DATA
 * Ref: https://xiph.org/flac/format.html#metadata_block_streaminfo
 */ interface IBlockStreamInfo { minimumBlockSize : number ; maximumBlockSize : number ; minimumFrameSize : number ; maximumFrameSize : number ; sampleRate : number ; channels : number ; bitsPerSample : number ; totalSamples : number ; fileMD5 : Uint8Array ; } class Metadata { public static BlockHeader : IGetToken < IBlockHeader > = { len : 4 , get : ( buf , off ) : IBlockHeader => { return { lastBlock : util . getBit ( buf , off , 7 ) , type : util . getBitAllignedNumber ( buf , off , 1 , 7 ) , length : Token . UINT24_BE . get ( buf , off + 1 ) } ; } } ; /**
     * METADATA_BLOCK_DATA
     * Ref: https://xiph.org/flac/format.html#metadata_block_streaminfo
     */ public static BlockStreamInfo : IGetToken < IBlockStreamInfo > = { len : 34 , get : ( buf , off ) : IBlockStreamInfo => { return { minimumBlockSize : Token . UINT16_BE . get ( buf , off ) , maximumBlockSize : Token . UINT16_BE . get ( buf , off + 2 ) / 1000 , minimumFrameSize : Token . UINT24_BE . get ( buf , off + 4 ) , maximumFrameSize : Token . UINT24_BE . get ( buf , off + 7 ) , sampleRate : Token . UINT24_BE . get ( buf , off + 10 ) >> 4 , channels : util . getBitAllignedNumber ( buf , off + 12 , 4 , 3 ) + 1 , bitsPerSample : util . getBitAllignedNumber ( buf , off + 12 , 7 , 5 ) + 1 , totalSamples : util . getBitAllignedNumber ( buf , off + 13 , 4 , 36 ) , fileMD5 : new Token . Uint8ArrayType ( 16 ) . get ( buf , off + 18 ) } ; } } ; }