ts-ebml-esm
Version:
ebml decoder and encoder
144 lines (125 loc) • 3.75 kB
text/typescript
import * as EBML from "./EBML.js";
import * as tools from "./tools.js";
import schema from "matroska-schema";
const { byEbmlID } = schema;
interface DataTree {
tagId: Buffer;
elm: EBML.EBMLElementBuffer;
children: DataTree[];
data: Buffer | null;
}
export default class EBMLEncoder {
private _buffers: Buffer[];
private _stack: DataTree[];
private _schema: { [key: number]: EBML.Schema };
constructor() {
this._schema = byEbmlID;
this._buffers = [];
this._stack = [];
}
encode(elms: EBML.EBMLElementBuffer[]): ArrayBuffer {
return tools.concat(
elms.reduce<Buffer[]>((lst, elm) => lst.concat(this.encodeChunk(elm)), [])
).buffer;
}
private encodeChunk(elm: EBML.EBMLElementBuffer): Buffer[] {
if (elm.type === "m") {
if (!elm.isEnd) {
this.startTag(elm);
} else {
this.endTag(elm);
}
} else {
// ensure that we are working with an internal `Buffer` instance
elm.data = Buffer.from(elm.data);
this.writeTag(elm);
}
return this.flush();
}
private flush(): Buffer[] {
const ret = this._buffers;
this._buffers = [];
return ret;
}
private getSchemaInfo(tagName: string): Buffer | null {
for (const [tagNum, tagVal] of Object.entries(this._schema)) {
if (tagVal.name === tagName) {
return Buffer.from(Number(tagNum).toString(16), "hex");
}
}
return null;
}
private writeTag(elm: EBML.ChildElementBuffer) {
const tagName = elm.name;
const tagId = this.getSchemaInfo(tagName);
const tagData = elm.data;
if (tagId == null) {
throw new Error("No schema entry found for " + tagName);
}
const data = tools.encodeTag(tagId, tagData);
// 親要素が閉じタグあり(isEnd)なら閉じタグが来るまで待つ(children queに入る)
if (this._stack.length > 0) {
const last = this._stack[this._stack.length - 1];
last.children.push({
tagId,
elm,
children: [] as DataTree[],
data
});
return;
}
this._buffers = this._buffers.concat(data);
return;
}
private startTag(elm: EBML.MasterElement) {
const tagName = elm.name;
const tagId = this.getSchemaInfo(tagName);
if (tagId == null) {
throw new Error("No schema entry found for " + tagName);
}
// 閉じタグ不定長の場合はスタックに積まずに即時バッファに書き込む
if (elm.unknownSize) {
const data = tools.encodeTag(tagId, Buffer.alloc(0), elm.unknownSize);
this._buffers = this._buffers.concat(data);
return;
}
const tag: DataTree = {
tagId,
elm,
children: [] as DataTree[],
data: null
};
if (this._stack.length > 0) {
this._stack[this._stack.length - 1].children.push(tag);
}
this._stack.push(tag);
}
private endTag(elm: EBML.MasterElement) {
const tag = this._stack.pop();
if (tag == null) {
throw new Error("EBML structure is broken");
}
if (tag.elm.name !== elm.name) {
throw new Error("EBML structure is broken");
}
const childTagDataBuffers = tag.children.reduce<Buffer[]>((lst, child) => {
if (child.data === null) {
throw new Error("EBML structure is broken");
}
return lst.concat(child.data);
}, []);
const childTagDataBuffer = tools.concat(childTagDataBuffers);
if (tag.elm.type === "m") {
tag.data = tools.encodeTag(
tag.tagId,
childTagDataBuffer,
tag.elm.unknownSize
);
} else {
tag.data = tools.encodeTag(tag.tagId, childTagDataBuffer);
}
if (this._stack.length < 1) {
this._buffers = this._buffers.concat(tag.data);
}
}
}