Initial working version
This commit is contained in:
+8
@@ -0,0 +1,8 @@
|
||||
import { IContainerType } from './types';
|
||||
/**
|
||||
* Elements of document type description
|
||||
* Derived from https://github.com/tungol/EBML/blob/master/doctypes/matroska.dtd
|
||||
* Extended with:
|
||||
* https://www.matroska.org/technical/specs/index.html
|
||||
*/
|
||||
export declare const elements: IContainerType;
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.elements = void 0;
|
||||
const types_1 = require("./types");
|
||||
/**
|
||||
* Elements of document type description
|
||||
* Derived from https://github.com/tungol/EBML/blob/master/doctypes/matroska.dtd
|
||||
* Extended with:
|
||||
* https://www.matroska.org/technical/specs/index.html
|
||||
*/
|
||||
exports.elements = {
|
||||
0x1a45dfa3: {
|
||||
name: 'ebml',
|
||||
container: {
|
||||
0x4286: { name: 'ebmlVersion', value: types_1.DataType.uint },
|
||||
0x42f7: { name: 'ebmlReadVersion', value: types_1.DataType.uint },
|
||||
0x42f2: { name: 'ebmlMaxIDWidth', value: types_1.DataType.uint },
|
||||
0x42f3: { name: 'ebmlMaxSizeWidth', value: types_1.DataType.uint },
|
||||
0x4282: { name: 'docType', value: types_1.DataType.string },
|
||||
0x4287: { name: 'docTypeVersion', value: types_1.DataType.uint },
|
||||
0x4285: { name: 'docTypeReadVersion', value: types_1.DataType.uint } // 5.1.7
|
||||
}
|
||||
},
|
||||
// Matroska segments
|
||||
0x18538067: {
|
||||
name: 'segment',
|
||||
container: {
|
||||
// Meta Seek Information
|
||||
0x114d9b74: {
|
||||
name: 'seekHead',
|
||||
container: {
|
||||
0x4dbb: {
|
||||
name: 'seek',
|
||||
container: {
|
||||
0x53ab: { name: 'seekId', value: types_1.DataType.binary },
|
||||
0x53ac: { name: 'seekPosition', value: types_1.DataType.uint }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Segment Information
|
||||
0x1549a966: {
|
||||
name: 'info',
|
||||
container: {
|
||||
0x73a4: { name: 'uid', value: types_1.DataType.uid },
|
||||
0x7384: { name: 'filename', value: types_1.DataType.string },
|
||||
0x3cb923: { name: 'prevUID', value: types_1.DataType.uid },
|
||||
0x3c83ab: { name: 'prevFilename', value: types_1.DataType.string },
|
||||
0x3eb923: { name: 'nextUID', value: types_1.DataType.uid },
|
||||
0x3e83bb: { name: 'nextFilename', value: types_1.DataType.string },
|
||||
0x2ad7b1: { name: 'timecodeScale', value: types_1.DataType.uint },
|
||||
0x4489: { name: 'duration', value: types_1.DataType.float },
|
||||
0x4461: { name: 'dateUTC', value: types_1.DataType.uint },
|
||||
0x7ba9: { name: 'title', value: types_1.DataType.string },
|
||||
0x4d80: { name: 'muxingApp', value: types_1.DataType.string },
|
||||
0x5741: { name: 'writingApp', value: types_1.DataType.string }
|
||||
}
|
||||
},
|
||||
// Cluster
|
||||
0x1f43b675: {
|
||||
name: 'cluster',
|
||||
multiple: true,
|
||||
container: {
|
||||
0xe7: { name: 'timecode', value: types_1.DataType.uid },
|
||||
0xa3: { name: 'unknown', value: types_1.DataType.binary },
|
||||
0xa7: { name: 'position', value: types_1.DataType.uid },
|
||||
0xab: { name: 'prevSize', value: types_1.DataType.uid }
|
||||
}
|
||||
},
|
||||
// Track
|
||||
0x1654ae6b: {
|
||||
name: 'tracks',
|
||||
container: {
|
||||
0xae: {
|
||||
name: 'entries',
|
||||
multiple: true,
|
||||
container: {
|
||||
0xd7: { name: 'trackNumber', value: types_1.DataType.uint },
|
||||
0x73c5: { name: 'uid', value: types_1.DataType.uid },
|
||||
0x83: { name: 'trackType', value: types_1.DataType.uint },
|
||||
0xb9: { name: 'flagEnabled', value: types_1.DataType.bool },
|
||||
0x88: { name: 'flagDefault', value: types_1.DataType.bool },
|
||||
0x55aa: { name: 'flagForced', value: types_1.DataType.bool },
|
||||
0x9c: { name: 'flagLacing', value: types_1.DataType.bool },
|
||||
0x6de7: { name: 'minCache', value: types_1.DataType.uint },
|
||||
0x6de8: { name: 'maxCache', value: types_1.DataType.uint },
|
||||
0x23e383: { name: 'defaultDuration', value: types_1.DataType.uint },
|
||||
0x23314f: { name: 'timecodeScale', value: types_1.DataType.float },
|
||||
0x536e: { name: 'name', value: types_1.DataType.string },
|
||||
0x22b59c: { name: 'language', value: types_1.DataType.string },
|
||||
0x86: { name: 'codecID', value: types_1.DataType.string },
|
||||
0x63a2: { name: 'codecPrivate', value: types_1.DataType.binary },
|
||||
0x258688: { name: 'codecName', value: types_1.DataType.string },
|
||||
0x3a9697: { name: 'codecSettings', value: types_1.DataType.string },
|
||||
0x3b4040: { name: 'codecInfoUrl', value: types_1.DataType.string },
|
||||
0x26b240: { name: 'codecDownloadUrl', value: types_1.DataType.string },
|
||||
0xaa: { name: 'codecDecodeAll', value: types_1.DataType.bool },
|
||||
0x6fab: { name: 'trackOverlay', value: types_1.DataType.uint },
|
||||
// Video
|
||||
0xe0: {
|
||||
name: 'video',
|
||||
container: {
|
||||
0x9a: { name: 'flagInterlaced', value: types_1.DataType.bool },
|
||||
0x53b8: { name: 'stereoMode', value: types_1.DataType.uint },
|
||||
0xb0: { name: 'pixelWidth', value: types_1.DataType.uint },
|
||||
0xba: { name: 'pixelHeight', value: types_1.DataType.uint },
|
||||
0x54b0: { name: 'displayWidth', value: types_1.DataType.uint },
|
||||
0x54ba: { name: 'displayHeight', value: types_1.DataType.uint },
|
||||
0x54b3: { name: 'aspectRatioType', value: types_1.DataType.uint },
|
||||
0x2eb524: { name: 'colourSpace', value: types_1.DataType.uint },
|
||||
0x2fb523: { name: 'gammaValue', value: types_1.DataType.float }
|
||||
}
|
||||
},
|
||||
// Audio
|
||||
0xe1: {
|
||||
name: 'audio',
|
||||
container: {
|
||||
0xb5: { name: 'samplingFrequency', value: types_1.DataType.float },
|
||||
0x78b5: { name: 'outputSamplingFrequency', value: types_1.DataType.float },
|
||||
0x9f: { name: 'channels', value: types_1.DataType.uint },
|
||||
0x94: { name: 'channels', value: types_1.DataType.uint },
|
||||
0x7d7b: { name: 'channelPositions', value: types_1.DataType.binary },
|
||||
0x6264: { name: 'bitDepth', value: types_1.DataType.uint }
|
||||
}
|
||||
},
|
||||
// Content Encoding
|
||||
0x6d80: {
|
||||
name: 'contentEncodings',
|
||||
container: {
|
||||
0x6240: {
|
||||
name: 'contentEncoding',
|
||||
container: {
|
||||
0x5031: { name: 'order', value: types_1.DataType.uint },
|
||||
0x5032: { name: 'scope', value: types_1.DataType.bool },
|
||||
0x5033: { name: 'type', value: types_1.DataType.uint },
|
||||
0x5034: {
|
||||
name: 'contentEncoding',
|
||||
container: {
|
||||
0x4254: { name: 'contentCompAlgo', value: types_1.DataType.uint },
|
||||
0x4255: { name: 'contentCompSettings', value: types_1.DataType.binary }
|
||||
}
|
||||
},
|
||||
0x5035: {
|
||||
name: 'contentEncoding',
|
||||
container: {
|
||||
0x47e1: { name: 'contentEncAlgo', value: types_1.DataType.uint },
|
||||
0x47e2: { name: 'contentEncKeyID', value: types_1.DataType.binary },
|
||||
0x47e3: { name: 'contentSignature ', value: types_1.DataType.binary },
|
||||
0x47e4: { name: 'ContentSigKeyID ', value: types_1.DataType.binary },
|
||||
0x47e5: { name: 'contentSigAlgo ', value: types_1.DataType.uint },
|
||||
0x47e6: { name: 'contentSigHashAlgo ', value: types_1.DataType.uint }
|
||||
}
|
||||
},
|
||||
0x6264: { name: 'bitDepth', value: types_1.DataType.uint }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Cueing Data
|
||||
0x1c53bb6b: {
|
||||
name: 'cues',
|
||||
container: {
|
||||
0xbb: {
|
||||
name: 'cuePoint',
|
||||
container: {
|
||||
0xb3: { name: 'cueTime', value: types_1.DataType.uid },
|
||||
0xb7: {
|
||||
name: 'positions',
|
||||
container: {
|
||||
0xf7: { name: 'track', value: types_1.DataType.uint },
|
||||
0xf1: { name: 'clusterPosition', value: types_1.DataType.uint },
|
||||
0x5378: { name: 'blockNumber', value: types_1.DataType.uint },
|
||||
0xea: { name: 'codecState', value: types_1.DataType.uint },
|
||||
0xdb: {
|
||||
name: 'reference', container: {
|
||||
0x96: { name: 'time', value: types_1.DataType.uint },
|
||||
0x97: { name: 'cluster', value: types_1.DataType.uint },
|
||||
0x535f: { name: 'number', value: types_1.DataType.uint },
|
||||
0xeb: { name: 'codecState', value: types_1.DataType.uint }
|
||||
}
|
||||
},
|
||||
0xf0: { name: 'relativePosition', value: types_1.DataType.uint } // extended
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Attachment
|
||||
0x1941a469: {
|
||||
name: 'attachments',
|
||||
container: {
|
||||
0x61a7: {
|
||||
name: 'attachedFiles',
|
||||
multiple: true,
|
||||
container: {
|
||||
0x467e: { name: 'description', value: types_1.DataType.string },
|
||||
0x466e: { name: 'name', value: types_1.DataType.string },
|
||||
0x4660: { name: 'mimeType', value: types_1.DataType.string },
|
||||
0x465c: { name: 'data', value: types_1.DataType.binary },
|
||||
0x46ae: { name: 'uid', value: types_1.DataType.uid }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Chapters
|
||||
0x1043a770: {
|
||||
name: 'chapters',
|
||||
container: {
|
||||
0x45b9: {
|
||||
name: 'editionEntry',
|
||||
container: {
|
||||
0xb6: {
|
||||
name: 'chapterAtom',
|
||||
container: {
|
||||
0x73c4: { name: 'uid', value: types_1.DataType.uid },
|
||||
0x91: { name: 'timeStart', value: types_1.DataType.uint },
|
||||
0x92: { name: 'timeEnd', value: types_1.DataType.uid },
|
||||
0x98: { name: 'hidden', value: types_1.DataType.bool },
|
||||
0x4598: { name: 'enabled', value: types_1.DataType.uid },
|
||||
0x8f: { name: 'track', container: {
|
||||
0x89: { name: 'trackNumber', value: types_1.DataType.uid },
|
||||
0x80: {
|
||||
name: 'display', container: {
|
||||
0x85: { name: 'string', value: types_1.DataType.string },
|
||||
0x437c: { name: 'language ', value: types_1.DataType.string },
|
||||
0x437e: { name: 'country ', value: types_1.DataType.string }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Tagging
|
||||
0x1254c367: {
|
||||
name: 'tags',
|
||||
container: {
|
||||
0x7373: {
|
||||
name: 'tag',
|
||||
multiple: true,
|
||||
container: {
|
||||
0x63c0: {
|
||||
name: 'target',
|
||||
container: {
|
||||
0x63c5: { name: 'tagTrackUID', value: types_1.DataType.uid },
|
||||
0x63c4: { name: 'tagChapterUID', value: types_1.DataType.uint },
|
||||
0x63c6: { name: 'tagAttachmentUID', value: types_1.DataType.uid },
|
||||
0x63ca: { name: 'targetType', value: types_1.DataType.string },
|
||||
0x68ca: { name: 'targetTypeValue', value: types_1.DataType.uint },
|
||||
0x63c9: { name: 'tagEditionUID', value: types_1.DataType.uid } // extended
|
||||
}
|
||||
},
|
||||
0x67c8: {
|
||||
name: 'simpleTags',
|
||||
multiple: true,
|
||||
container: {
|
||||
0x45a3: { name: 'name', value: types_1.DataType.string },
|
||||
0x4487: { name: 'string', value: types_1.DataType.string },
|
||||
0x4485: { name: 'binary', value: types_1.DataType.binary },
|
||||
0x447a: { name: 'language', value: types_1.DataType.string },
|
||||
0x447b: { name: 'languageIETF', value: types_1.DataType.string },
|
||||
0x4484: { name: 'default', value: types_1.DataType.bool } // extended
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { INativeMetadataCollector } from '../common/MetadataCollector';
|
||||
import { ITokenizer } from 'strtok3/lib/core';
|
||||
import { IOptions } from '../type';
|
||||
import { ITokenParser } from '../ParserFactory';
|
||||
import { BasicParser } from '../common/BasicParser';
|
||||
/**
|
||||
* Extensible Binary Meta Language (EBML) parser
|
||||
* https://en.wikipedia.org/wiki/Extensible_Binary_Meta_Language
|
||||
* http://matroska.sourceforge.net/technical/specs/rfc/index.html
|
||||
*
|
||||
* WEBM VP8 AUDIO FILE
|
||||
*/
|
||||
export declare class MatroskaParser extends BasicParser {
|
||||
private padding;
|
||||
private parserMap;
|
||||
private ebmlMaxIDLength;
|
||||
private ebmlMaxSizeLength;
|
||||
constructor();
|
||||
/**
|
||||
* Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
|
||||
* @param {INativeMetadataCollector} metadata Output
|
||||
* @param {ITokenizer} tokenizer Input
|
||||
* @param {IOptions} options Parsing options
|
||||
*/
|
||||
init(metadata: INativeMetadataCollector, tokenizer: ITokenizer, options: IOptions): ITokenParser;
|
||||
parse(): Promise<void>;
|
||||
private parseContainer;
|
||||
private readVintData;
|
||||
private readElement;
|
||||
private isMaxValue;
|
||||
private readFloat;
|
||||
private readFlag;
|
||||
private readUint;
|
||||
private readString;
|
||||
private readBuffer;
|
||||
private addTag;
|
||||
}
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.MatroskaParser = void 0;
|
||||
const Token = require("token-types");
|
||||
const _debug = require("debug");
|
||||
const BasicParser_1 = require("../common/BasicParser");
|
||||
const types_1 = require("./types");
|
||||
const matroskaDtd = require("./MatroskaDtd");
|
||||
const debug = _debug('music-metadata:parser:matroska');
|
||||
/**
|
||||
* Extensible Binary Meta Language (EBML) parser
|
||||
* https://en.wikipedia.org/wiki/Extensible_Binary_Meta_Language
|
||||
* http://matroska.sourceforge.net/technical/specs/rfc/index.html
|
||||
*
|
||||
* WEBM VP8 AUDIO FILE
|
||||
*/
|
||||
class MatroskaParser extends BasicParser_1.BasicParser {
|
||||
constructor() {
|
||||
super();
|
||||
this.padding = 0;
|
||||
this.parserMap = new Map();
|
||||
this.ebmlMaxIDLength = 4;
|
||||
this.ebmlMaxSizeLength = 8;
|
||||
this.parserMap.set(types_1.DataType.uint, e => this.readUint(e));
|
||||
this.parserMap.set(types_1.DataType.string, e => this.readString(e));
|
||||
this.parserMap.set(types_1.DataType.binary, e => this.readBuffer(e));
|
||||
this.parserMap.set(types_1.DataType.uid, async (e) => await this.readUint(e) === 1);
|
||||
this.parserMap.set(types_1.DataType.bool, e => this.readFlag(e));
|
||||
this.parserMap.set(types_1.DataType.float, e => this.readFloat(e));
|
||||
}
|
||||
/**
|
||||
* Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
|
||||
* @param {INativeMetadataCollector} metadata Output
|
||||
* @param {ITokenizer} tokenizer Input
|
||||
* @param {IOptions} options Parsing options
|
||||
*/
|
||||
init(metadata, tokenizer, options) {
|
||||
super.init(metadata, tokenizer, options);
|
||||
return this;
|
||||
}
|
||||
async parse() {
|
||||
const matroska = await this.parseContainer(matroskaDtd.elements, this.tokenizer.fileInfo.size, []);
|
||||
this.metadata.setFormat('container', `EBML/${matroska.ebml.docType}`);
|
||||
if (matroska.segment) {
|
||||
const info = matroska.segment.info;
|
||||
if (info) {
|
||||
const timecodeScale = info.timecodeScale ? info.timecodeScale : 1000000;
|
||||
const duration = info.duration * timecodeScale / 1000000000;
|
||||
this.addTag('segment:title', info.title);
|
||||
this.metadata.setFormat('duration', duration);
|
||||
}
|
||||
const audioTracks = matroska.segment.tracks;
|
||||
if (audioTracks && audioTracks.entries) {
|
||||
audioTracks.entries.forEach(entry => {
|
||||
const stream = {
|
||||
codecName: entry.codecID.replace('A_', '').replace('V_', ''),
|
||||
codecSettings: entry.codecSettings,
|
||||
flagDefault: entry.flagDefault,
|
||||
flagLacing: entry.flagLacing,
|
||||
flagEnabled: entry.flagEnabled,
|
||||
language: entry.language,
|
||||
name: entry.name,
|
||||
type: entry.trackType,
|
||||
audio: entry.audio,
|
||||
video: entry.video
|
||||
};
|
||||
this.metadata.addStreamInfo(stream);
|
||||
});
|
||||
const audioTrack = audioTracks.entries
|
||||
.filter(entry => {
|
||||
return entry.trackType === types_1.TrackType.audio.valueOf();
|
||||
})
|
||||
.reduce((acc, cur) => {
|
||||
if (!acc) {
|
||||
return cur;
|
||||
}
|
||||
if (!acc.flagDefault && cur.flagDefault) {
|
||||
return cur;
|
||||
}
|
||||
if (cur.trackNumber && cur.trackNumber < acc.trackNumber) {
|
||||
return cur;
|
||||
}
|
||||
return acc;
|
||||
}, null);
|
||||
if (audioTrack) {
|
||||
this.metadata.setFormat('codec', audioTrack.codecID.replace('A_', ''));
|
||||
this.metadata.setFormat('sampleRate', audioTrack.audio.samplingFrequency);
|
||||
this.metadata.setFormat('numberOfChannels', audioTrack.audio.channels);
|
||||
}
|
||||
if (matroska.segment.tags) {
|
||||
matroska.segment.tags.tag.forEach(tag => {
|
||||
const target = tag.target;
|
||||
const targetType = target.targetTypeValue ? types_1.TargetType[target.targetTypeValue] : (target.targetType ? target.targetType : 'track');
|
||||
tag.simpleTags.forEach(simpleTag => {
|
||||
const value = simpleTag.string ? simpleTag.string : simpleTag.binary;
|
||||
this.addTag(`${targetType}:${simpleTag.name}`, value);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (matroska.segment.attachments) {
|
||||
matroska.segment.attachments.attachedFiles
|
||||
.filter(file => file.mimeType.startsWith('image/'))
|
||||
.map(file => {
|
||||
return {
|
||||
data: file.data,
|
||||
format: file.mimeType,
|
||||
description: file.description,
|
||||
name: file.name
|
||||
};
|
||||
}).forEach(picture => {
|
||||
this.addTag('picture', picture);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async parseContainer(container, posDone, path) {
|
||||
const tree = {};
|
||||
while (this.tokenizer.position < posDone) {
|
||||
let element;
|
||||
try {
|
||||
element = await this.readElement();
|
||||
}
|
||||
catch (error) {
|
||||
if (error.message === 'End-Of-Stream') {
|
||||
break;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
const type = container[element.id];
|
||||
if (type) {
|
||||
debug(`Element: name=${type.name}, container=${!!type.container}`);
|
||||
if (type.container) {
|
||||
const res = await this.parseContainer(type.container, element.len >= 0 ? this.tokenizer.position + element.len : -1, path.concat([type.name]));
|
||||
if (type.multiple) {
|
||||
if (!tree[type.name]) {
|
||||
tree[type.name] = [];
|
||||
}
|
||||
tree[type.name].push(res);
|
||||
}
|
||||
else {
|
||||
tree[type.name] = res;
|
||||
}
|
||||
}
|
||||
else {
|
||||
tree[type.name] = await this.parserMap.get(type.value)(element);
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (element.id) {
|
||||
case 0xec: // void
|
||||
this.padding += element.len;
|
||||
await this.tokenizer.ignore(element.len);
|
||||
break;
|
||||
default:
|
||||
debug(`parseEbml: path=${path.join('/')}, unknown element: id=${element.id.toString(16)}`);
|
||||
this.padding += element.len;
|
||||
await this.tokenizer.ignore(element.len);
|
||||
}
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
async readVintData(maxLength) {
|
||||
const msb = await this.tokenizer.peekNumber(Token.UINT8);
|
||||
let mask = 0x80;
|
||||
let oc = 1;
|
||||
// Calculate VINT_WIDTH
|
||||
while ((msb & mask) === 0) {
|
||||
if (oc > maxLength) {
|
||||
throw new Error('VINT value exceeding maximum size');
|
||||
}
|
||||
++oc;
|
||||
mask >>= 1;
|
||||
}
|
||||
const id = Buffer.alloc(oc);
|
||||
await this.tokenizer.readBuffer(id);
|
||||
return id;
|
||||
}
|
||||
async readElement() {
|
||||
const id = await this.readVintData(this.ebmlMaxIDLength);
|
||||
const lenField = await this.readVintData(this.ebmlMaxSizeLength);
|
||||
lenField[0] ^= 0x80 >> (lenField.length - 1);
|
||||
const nrLen = Math.min(6, lenField.length); // JavaScript can max read 6 bytes integer
|
||||
return {
|
||||
id: id.readUIntBE(0, id.length),
|
||||
len: lenField.readUIntBE(lenField.length - nrLen, nrLen)
|
||||
};
|
||||
}
|
||||
isMaxValue(vintData) {
|
||||
if (vintData.length === this.ebmlMaxSizeLength) {
|
||||
for (let n = 1; n < this.ebmlMaxSizeLength; ++n) {
|
||||
if (vintData[n] !== 0xff)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
async readFloat(e) {
|
||||
switch (e.len) {
|
||||
case 0:
|
||||
return 0.0;
|
||||
case 4:
|
||||
return this.tokenizer.readNumber(Token.Float32_BE);
|
||||
case 8:
|
||||
return this.tokenizer.readNumber(Token.Float64_BE);
|
||||
case 10:
|
||||
return this.tokenizer.readNumber(Token.Float64_BE);
|
||||
default:
|
||||
throw new Error(`Invalid IEEE-754 float length: ${e.len}`);
|
||||
}
|
||||
}
|
||||
async readFlag(e) {
|
||||
return (await this.readUint(e)) === 1;
|
||||
}
|
||||
async readUint(e) {
|
||||
const buf = await this.readBuffer(e);
|
||||
const nrLen = Math.min(6, e.len); // JavaScript can max read 6 bytes integer
|
||||
return buf.readUIntBE(e.len - nrLen, nrLen);
|
||||
}
|
||||
async readString(e) {
|
||||
const rawString = await this.tokenizer.readToken(new Token.StringType(e.len, 'utf-8'));
|
||||
return rawString.replace(/\00.*$/g, '');
|
||||
}
|
||||
async readBuffer(e) {
|
||||
const buf = Buffer.alloc(e.len);
|
||||
await this.tokenizer.readBuffer(buf);
|
||||
return buf;
|
||||
}
|
||||
addTag(tagId, value) {
|
||||
this.metadata.addTag('matroska', tagId, value);
|
||||
}
|
||||
}
|
||||
exports.MatroskaParser = MatroskaParser;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { CaseInsensitiveTagMap } from '../common/CaseInsensitiveTagMap';
|
||||
export declare class MatroskaTagMapper extends CaseInsensitiveTagMap {
|
||||
constructor();
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.MatroskaTagMapper = void 0;
|
||||
const CaseInsensitiveTagMap_1 = require("../common/CaseInsensitiveTagMap");
|
||||
/**
|
||||
* EBML Tag map
|
||||
*/
|
||||
const ebmlTagMap = {
|
||||
'segment:title': 'title',
|
||||
'album:ARTIST': 'albumartist',
|
||||
'album:ARTISTSORT': 'albumartistsort',
|
||||
'album:TITLE': 'album',
|
||||
'album:DATE_RECORDED': 'originaldate',
|
||||
'album:PART_NUMBER': 'disk',
|
||||
'album:TOTAL_PARTS': 'totaltracks',
|
||||
'track:ARTIST': 'artist',
|
||||
'track:ARTISTSORT': 'artistsort',
|
||||
'track:TITLE': 'title',
|
||||
'track:PART_NUMBER': 'track',
|
||||
'track:MUSICBRAINZ_TRACKID': 'musicbrainz_recordingid',
|
||||
'track:MUSICBRAINZ_ALBUMID': 'musicbrainz_albumid',
|
||||
'track:MUSICBRAINZ_ARTISTID': 'musicbrainz_artistid',
|
||||
'track:PUBLISHER': 'label',
|
||||
'track:GENRE': 'genre',
|
||||
'track:ENCODER': 'encodedby',
|
||||
'track:ENCODER_OPTIONS': 'encodersettings',
|
||||
'edition:TOTAL_PARTS': 'totaldiscs',
|
||||
picture: 'picture'
|
||||
};
|
||||
class MatroskaTagMapper extends CaseInsensitiveTagMap_1.CaseInsensitiveTagMap {
|
||||
constructor() {
|
||||
super(['matroska'], ebmlTagMap);
|
||||
}
|
||||
}
|
||||
exports.MatroskaTagMapper = MatroskaTagMapper;
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
/// <reference types="node" />
|
||||
export interface IHeader {
|
||||
id: number;
|
||||
len: number;
|
||||
}
|
||||
export declare enum DataType {
|
||||
string = 0,
|
||||
uint = 1,
|
||||
uid = 2,
|
||||
bool = 3,
|
||||
binary = 4,
|
||||
float = 5
|
||||
}
|
||||
export interface IElementType<T> {
|
||||
readonly name: string;
|
||||
readonly value?: DataType;
|
||||
readonly container?: IContainerType;
|
||||
readonly multiple?: boolean;
|
||||
}
|
||||
export interface IContainerType {
|
||||
[id: number]: IElementType<string | number | boolean | Buffer>;
|
||||
}
|
||||
export interface ITree {
|
||||
[name: string]: string | number | boolean | Buffer | ITree | ITree[];
|
||||
}
|
||||
export interface ISeekHead {
|
||||
id?: Buffer;
|
||||
position?: number;
|
||||
}
|
||||
export interface IMetaSeekInformation {
|
||||
seekHeads: ISeekHead[];
|
||||
}
|
||||
export interface ISegmentInformation {
|
||||
uid?: Buffer;
|
||||
timecodeScale?: number;
|
||||
duration?: number;
|
||||
dateUTC?: number;
|
||||
title?: string;
|
||||
muxingApp?: string;
|
||||
writingApp?: string;
|
||||
}
|
||||
export interface ITrackEntry {
|
||||
uid?: Buffer;
|
||||
trackNumber?: number;
|
||||
trackType?: TrackType;
|
||||
audio?: ITrackAudio;
|
||||
video?: ITrackVideo;
|
||||
flagEnabled?: boolean;
|
||||
flagDefault?: boolean;
|
||||
flagLacing?: boolean;
|
||||
defaultDuration?: number;
|
||||
trackTimecodeScale?: number;
|
||||
name?: string;
|
||||
language?: string;
|
||||
codecID?: string;
|
||||
codecPrivate?: Buffer;
|
||||
codecName?: string;
|
||||
codecSettings?: string;
|
||||
codecInfoUrl?: string;
|
||||
codecDownloadUrl?: string;
|
||||
codecDecodeAll?: string;
|
||||
trackOverlay?: string;
|
||||
}
|
||||
export interface ITrackVideo {
|
||||
flagInterlaced?: boolean;
|
||||
stereoMode?: number;
|
||||
pixelWidth?: number;
|
||||
pixelHeight?: number;
|
||||
displayWidth?: number;
|
||||
displayHeight?: number;
|
||||
displayUnit?: number;
|
||||
aspectRatioType?: number;
|
||||
colourSpace?: Buffer;
|
||||
gammaValue?: number;
|
||||
}
|
||||
export interface ITrackAudio {
|
||||
samplingFrequency?: number;
|
||||
outputSamplingFrequency?: number;
|
||||
channels?: number;
|
||||
channelPositions?: Buffer;
|
||||
bitDepth?: number;
|
||||
}
|
||||
export interface ICuePoint {
|
||||
cueTime?: number;
|
||||
cueTrackPositions: ICueTrackPosition[];
|
||||
}
|
||||
export interface ICueTrackPosition {
|
||||
cueTrack?: number;
|
||||
cueClusterPosition?: number;
|
||||
cueBlockNumber?: number;
|
||||
cueCodecState?: number;
|
||||
cueReference?: ICueReference;
|
||||
}
|
||||
export interface ICueReference {
|
||||
cueRefTime?: number;
|
||||
cueRefCluster?: number;
|
||||
cueRefNumber?: number;
|
||||
cueRefCodecState?: number;
|
||||
}
|
||||
export interface ISimpleTag {
|
||||
name?: string;
|
||||
string?: string;
|
||||
binary?: Buffer;
|
||||
language?: string;
|
||||
default?: boolean;
|
||||
}
|
||||
export declare enum TargetType {
|
||||
shot = 10,
|
||||
scene = 20,
|
||||
track = 30,
|
||||
part = 40,
|
||||
album = 50,
|
||||
edition = 60,
|
||||
collection = 70
|
||||
}
|
||||
export declare enum TrackType {
|
||||
video = 1,
|
||||
audio = 2,
|
||||
complex = 3,
|
||||
logo = 4,
|
||||
subtitle = 17,
|
||||
button = 18,
|
||||
control = 32
|
||||
}
|
||||
export interface ITarget {
|
||||
trackUID?: Buffer;
|
||||
chapterUID?: Buffer;
|
||||
attachmentUID?: Buffer;
|
||||
targetTypeValue?: TargetType;
|
||||
targetType?: string;
|
||||
}
|
||||
export interface ITag {
|
||||
target: ITarget;
|
||||
simpleTags: ISimpleTag[];
|
||||
}
|
||||
export interface ITags {
|
||||
tag: ITag[];
|
||||
}
|
||||
export interface ITrackElement {
|
||||
entries?: ITrackEntry[];
|
||||
}
|
||||
export interface IAttachmedFile {
|
||||
description?: string;
|
||||
name: string;
|
||||
mimeType: string;
|
||||
data: Buffer;
|
||||
uid: string;
|
||||
}
|
||||
export interface IAttachments {
|
||||
attachedFiles?: IAttachmedFile[];
|
||||
}
|
||||
export interface IMatroskaSegment {
|
||||
metaSeekInfo?: IMetaSeekInformation;
|
||||
seekHeads?: ISeekHead[];
|
||||
info?: ISegmentInformation;
|
||||
tracks?: ITrackElement;
|
||||
tags?: ITags;
|
||||
cues?: ICuePoint[];
|
||||
attachments?: IAttachments;
|
||||
}
|
||||
export interface IEbmlElements {
|
||||
version?: number;
|
||||
readVersion?: number;
|
||||
maxIDWidth?: number;
|
||||
maxSizeWidth?: number;
|
||||
docType?: string;
|
||||
docTypeVersion?: number;
|
||||
docTypeReadVersion?: number;
|
||||
}
|
||||
export interface IEbmlDoc {
|
||||
ebml: IEbmlElements;
|
||||
}
|
||||
export interface IMatroskaDoc extends IEbmlDoc {
|
||||
segment: IMatroskaSegment;
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.TrackType = exports.TargetType = exports.DataType = void 0;
|
||||
var DataType;
|
||||
(function (DataType) {
|
||||
DataType[DataType["string"] = 0] = "string";
|
||||
DataType[DataType["uint"] = 1] = "uint";
|
||||
DataType[DataType["uid"] = 2] = "uid";
|
||||
DataType[DataType["bool"] = 3] = "bool";
|
||||
DataType[DataType["binary"] = 4] = "binary";
|
||||
DataType[DataType["float"] = 5] = "float";
|
||||
})(DataType = exports.DataType || (exports.DataType = {}));
|
||||
var TargetType;
|
||||
(function (TargetType) {
|
||||
TargetType[TargetType["shot"] = 10] = "shot";
|
||||
TargetType[TargetType["scene"] = 20] = "scene";
|
||||
TargetType[TargetType["track"] = 30] = "track";
|
||||
TargetType[TargetType["part"] = 40] = "part";
|
||||
TargetType[TargetType["album"] = 50] = "album";
|
||||
TargetType[TargetType["edition"] = 60] = "edition";
|
||||
TargetType[TargetType["collection"] = 70] = "collection";
|
||||
})(TargetType = exports.TargetType || (exports.TargetType = {}));
|
||||
var TrackType;
|
||||
(function (TrackType) {
|
||||
TrackType[TrackType["video"] = 1] = "video";
|
||||
TrackType[TrackType["audio"] = 2] = "audio";
|
||||
TrackType[TrackType["complex"] = 3] = "complex";
|
||||
TrackType[TrackType["logo"] = 4] = "logo";
|
||||
TrackType[TrackType["subtitle"] = 17] = "subtitle";
|
||||
TrackType[TrackType["button"] = 18] = "button";
|
||||
TrackType[TrackType["control"] = 32] = "control";
|
||||
})(TrackType = exports.TrackType || (exports.TrackType = {}));
|
||||
Reference in New Issue
Block a user