import * as _debug from 'debug' ; import { ITag , TrackType } from '../type' ; import GUID from './GUID' ; import * as AsfObject from './AsfObject' ; import { BasicParser } from '../common/BasicParser' ; const debug = _debug ( 'music-metadata:parser:ASF' ) ; const headerType = 'asf' ; /**
 * Windows Media Metadata Usage Guidelines
 *   Ref: https://msdn.microsoft.com/en-us/library/ms867702.aspx
 *
 * Ref:
 *   https://tools.ietf.org/html/draft-fleischman-asf-01
 *   https://hwiegman.home.xs4all.nl/fileformats/asf/ASF_Specification.pdf
 *   http://drang.s4.xrea.com/program/tips/id3tag/wmp/index.html
 *   https://msdn.microsoft.com/en-us/library/windows/desktop/ee663575(v=vs.85).aspx
 */ export class AsfParser extends BasicParser { public async parse ( ) { const header = await this . tokenizer . readToken < AsfObject . IAsfTopLevelObjectHeader > ( AsfObject . TopLevelHeaderObjectToken ) ; if ( ! header . objectId . equals ( GUID . HeaderObject ) ) { throw new Error ( 'expected asf header; but was not found; got: ' + header . objectId . str ) ; } try { await this . parseObjectHeader ( header . numberOfHeaderObjects ) ; } catch ( err ) { debug ( 'Error while parsing ASF: %s' , err ) ; } } private async parseObjectHeader ( numberOfObjectHeaders ) { let tags ; do { const header = await this . tokenizer . readToken < AsfObject . IAsfObjectHeader > ( AsfObject . HeaderObjectToken ) ; debug ( 'header GUID=%s' , header . objectId . str ) ; switch ( header . objectId . str ) { case AsfObject . FilePropertiesObject . guid . str : const fpo = await this . tokenizer . readToken < AsfObject . IFilePropertiesObject > ( new AsfObject . FilePropertiesObject ( header ) ) ; this . metadata . setFormat ( 'duration' , Number ( fpo . playDuration / BigInt ( 1000 ) ) / 10000 - Number ( fpo . preroll ) / 1000 ) ; this . metadata . setFormat ( 'bitrate' , fpo . maximumBitrate ) ; break ; case AsfObject . StreamPropertiesObject . guid . str : const spo = await this . tokenizer . readToken < AsfObject . IStreamPropertiesObject > ( new AsfObject . StreamPropertiesObject ( header ) ) ; this . metadata . setFormat ( 'container' , 'ASF/' + spo . streamType ) ; break ; case AsfObject . HeaderExtensionObject . guid . str : const extHeader = await this . tokenizer . readToken < AsfObject . IHeaderExtensionObject > ( new AsfObject . HeaderExtensionObject ( ) ) ; await this . parseExtensionObject ( extHeader . extensionDataSize ) ; break ; case AsfObject . ContentDescriptionObjectState . guid . str : tags = await this . tokenizer . readToken < ITag [ ] > ( new AsfObject . ContentDescriptionObjectState ( header ) ) ; this . addTags ( tags ) ; break ; case AsfObject . ExtendedContentDescriptionObjectState . guid . str : tags = await this . tokenizer . readToken < ITag [ ] > ( new AsfObject . ExtendedContentDescriptionObjectState ( header ) ) ; this . addTags ( tags ) ; break ; case GUID . CodecListObject . str : const codecs = await AsfObject . readCodecEntries ( this . tokenizer ) ; codecs . forEach ( codec => { this . metadata . addStreamInfo ( { type : codec . type . videoCodec ? TrackType . video : TrackType . audio , codecName : codec . codecName } ) ; } ) ; const audioCodecs = codecs . filter ( codec => codec . type . audioCodec ) . map ( codec => codec . codecName ) . join ( '/' ) ; this . metadata . setFormat ( 'codec' , audioCodecs ) ; break ; case GUID . StreamBitratePropertiesObject . str : await this . tokenizer . ignore ( header . objectSize - AsfObject . HeaderObjectToken . len ) ; break ; case GUID . PaddingObject . str : debug ( 'Padding: %s bytes' , header . objectSize - AsfObject . HeaderObjectToken . len ) ; await this . tokenizer . ignore ( header . objectSize - AsfObject . HeaderObjectToken . len ) ; break ; default : this . metadata . addWarning ( 'Ignore ASF-Object-GUID: ' + header . objectId . str ) ; debug ( 'Ignore ASF-Object-GUID: %s' , header . objectId . str ) ; await this . tokenizer . readToken < void > ( new AsfObject . IgnoreObjectState ( header ) ) ; } } while ( -- numberOfObjectHeaders ) ; } private addTags ( tags ) { tags . forEach ( tag => { this . metadata . addTag ( headerType , tag . id , tag . value ) ; } ) ; } private async parseExtensionObject ( extensionSize ) { do { const header = await this . tokenizer . readToken < AsfObject . IAsfObjectHeader > ( AsfObject . HeaderObjectToken ) ; const remaining = header . objectSize - AsfObject . HeaderObjectToken . len ; switch ( header . objectId . str ) { case AsfObject . ExtendedStreamPropertiesObjectState . guid . str : await this . tokenizer . readToken < AsfObject . IExtendedStreamPropertiesObject > ( new AsfObject . ExtendedStreamPropertiesObjectState ( header ) ) ; break ; case AsfObject . MetadataObjectState . guid . str : const moTags = await this . tokenizer . readToken < ITag [ ] > ( new AsfObject . MetadataObjectState ( header ) ) ; this . addTags ( moTags ) ; break ; case AsfObject . MetadataLibraryObjectState . guid . str : const mlTags = await this . tokenizer . readToken < ITag [ ] > ( new AsfObject . MetadataLibraryObjectState ( header ) ) ; this . addTags ( mlTags ) ; break ; case GUID . PaddingObject . str : await this . tokenizer . ignore ( remaining ) ; break ; case GUID . CompatibilityObject . str : this . tokenizer . ignore ( remaining ) ; break ; case GUID . ASF_Index_Placeholder_Object . str : await this . tokenizer . ignore ( remaining ) ; break ; default : this . metadata . addWarning ( 'Ignore ASF-Object-GUID: ' + header . objectId . str ) ; await this . tokenizer . readToken < void > ( new AsfObject . IgnoreObjectState ( header ) ) ; break ; } extensionSize -= header . objectSize ; } while ( extensionSize > 0 ) ; } }