136 lines
7.0 KiB
JavaScript
136 lines
7.0 KiB
JavaScript
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.AsfParser = void 0;
|
||
|
const debug_1 = require("debug");
|
||
|
const type_1 = require("../type");
|
||
|
const GUID_1 = require("./GUID");
|
||
|
const AsfObject = require("./AsfObject");
|
||
|
const BasicParser_1 = require("../common/BasicParser");
|
||
|
const debug = (0, debug_1.default)('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
|
||
|
*/
|
||
|
class AsfParser extends BasicParser_1.BasicParser {
|
||
|
async parse() {
|
||
|
const header = await this.tokenizer.readToken(AsfObject.TopLevelHeaderObjectToken);
|
||
|
if (!header.objectId.equals(GUID_1.default.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);
|
||
|
}
|
||
|
}
|
||
|
async parseObjectHeader(numberOfObjectHeaders) {
|
||
|
let tags;
|
||
|
do {
|
||
|
// Parse common header of the ASF Object (3.1)
|
||
|
const header = await this.tokenizer.readToken(AsfObject.HeaderObjectToken);
|
||
|
// Parse data part of the ASF Object
|
||
|
debug('header GUID=%s', header.objectId.str);
|
||
|
switch (header.objectId.str) {
|
||
|
case AsfObject.FilePropertiesObject.guid.str: // 3.2
|
||
|
const fpo = await this.tokenizer.readToken(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: // 3.3
|
||
|
const spo = await this.tokenizer.readToken(new AsfObject.StreamPropertiesObject(header));
|
||
|
this.metadata.setFormat('container', 'ASF/' + spo.streamType);
|
||
|
break;
|
||
|
case AsfObject.HeaderExtensionObject.guid.str: // 3.4
|
||
|
const extHeader = await this.tokenizer.readToken(new AsfObject.HeaderExtensionObject());
|
||
|
await this.parseExtensionObject(extHeader.extensionDataSize);
|
||
|
break;
|
||
|
case AsfObject.ContentDescriptionObjectState.guid.str: // 3.10
|
||
|
tags = await this.tokenizer.readToken(new AsfObject.ContentDescriptionObjectState(header));
|
||
|
this.addTags(tags);
|
||
|
break;
|
||
|
case AsfObject.ExtendedContentDescriptionObjectState.guid.str: // 3.11
|
||
|
tags = await this.tokenizer.readToken(new AsfObject.ExtendedContentDescriptionObjectState(header));
|
||
|
this.addTags(tags);
|
||
|
break;
|
||
|
case GUID_1.default.CodecListObject.str:
|
||
|
const codecs = await AsfObject.readCodecEntries(this.tokenizer);
|
||
|
codecs.forEach(codec => {
|
||
|
this.metadata.addStreamInfo({
|
||
|
type: codec.type.videoCodec ? type_1.TrackType.video : type_1.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_1.default.StreamBitratePropertiesObject.str:
|
||
|
// ToDo?
|
||
|
await this.tokenizer.ignore(header.objectSize - AsfObject.HeaderObjectToken.len);
|
||
|
break;
|
||
|
case GUID_1.default.PaddingObject.str:
|
||
|
// ToDo: register bytes pad
|
||
|
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(new AsfObject.IgnoreObjectState(header));
|
||
|
}
|
||
|
} while (--numberOfObjectHeaders);
|
||
|
// done
|
||
|
}
|
||
|
addTags(tags) {
|
||
|
tags.forEach(tag => {
|
||
|
this.metadata.addTag(headerType, tag.id, tag.value);
|
||
|
});
|
||
|
}
|
||
|
async parseExtensionObject(extensionSize) {
|
||
|
do {
|
||
|
// Parse common header of the ASF Object (3.1)
|
||
|
const header = await this.tokenizer.readToken(AsfObject.HeaderObjectToken);
|
||
|
const remaining = header.objectSize - AsfObject.HeaderObjectToken.len;
|
||
|
// Parse data part of the ASF Object
|
||
|
switch (header.objectId.str) {
|
||
|
case AsfObject.ExtendedStreamPropertiesObjectState.guid.str: // 4.1
|
||
|
// ToDo: extended stream header properties are ignored
|
||
|
await this.tokenizer.readToken(new AsfObject.ExtendedStreamPropertiesObjectState(header));
|
||
|
break;
|
||
|
case AsfObject.MetadataObjectState.guid.str: // 4.7
|
||
|
const moTags = await this.tokenizer.readToken(new AsfObject.MetadataObjectState(header));
|
||
|
this.addTags(moTags);
|
||
|
break;
|
||
|
case AsfObject.MetadataLibraryObjectState.guid.str: // 4.8
|
||
|
const mlTags = await this.tokenizer.readToken(new AsfObject.MetadataLibraryObjectState(header));
|
||
|
this.addTags(mlTags);
|
||
|
break;
|
||
|
case GUID_1.default.PaddingObject.str:
|
||
|
// ToDo: register bytes pad
|
||
|
await this.tokenizer.ignore(remaining);
|
||
|
break;
|
||
|
case GUID_1.default.CompatibilityObject.str:
|
||
|
this.tokenizer.ignore(remaining);
|
||
|
break;
|
||
|
case GUID_1.default.ASF_Index_Placeholder_Object.str:
|
||
|
await this.tokenizer.ignore(remaining);
|
||
|
break;
|
||
|
default:
|
||
|
this.metadata.addWarning('Ignore ASF-Object-GUID: ' + header.objectId.str);
|
||
|
// console.log("Ignore ASF-Object-GUID: %s", header.objectId.str);
|
||
|
await this.tokenizer.readToken(new AsfObject.IgnoreObjectState(header));
|
||
|
break;
|
||
|
}
|
||
|
extensionSize -= header.objectSize;
|
||
|
} while (extensionSize > 0);
|
||
|
}
|
||
|
}
|
||
|
exports.AsfParser = AsfParser;
|