Spaces:
Sleeping
Sleeping
| /** Binary reader/writer utilities for subtitle format parsing */ | |
| export class BinaryReader { | |
| private buf: Buffer; | |
| private _offset = 0; | |
| constructor(buf: Buffer) { | |
| this.buf = buf; | |
| } | |
| get offset(): number { | |
| return this._offset; | |
| } | |
| get remaining(): number { | |
| return this.buf.length - this._offset; | |
| } | |
| get eof(): boolean { | |
| return this._offset >= this.buf.length; | |
| } | |
| seek(offset: number): void { | |
| this._offset = offset; | |
| } | |
| skip(bytes: number): void { | |
| this._offset += bytes; | |
| } | |
| readUInt8(): number { | |
| const v = this.buf[this._offset]; | |
| this._offset += 1; | |
| return v; | |
| } | |
| readInt8(): number { | |
| const v = this.buf.readInt8(this._offset); | |
| this._offset += 1; | |
| return v; | |
| } | |
| readUInt16LE(): number { | |
| const v = this.buf.readUInt16LE(this._offset); | |
| this._offset += 2; | |
| return v; | |
| } | |
| readUInt32LE(): number { | |
| const v = this.buf.readUInt32LE(this._offset); | |
| this._offset += 4; | |
| return v; | |
| } | |
| readInt32LE(): number { | |
| const v = this.buf.readInt32LE(this._offset); | |
| this._offset += 4; | |
| return v; | |
| } | |
| readBytes(count: number): Buffer { | |
| const data = this.buf.subarray(this._offset, this._offset + count); | |
| this._offset += count; | |
| return data; | |
| } | |
| /** Read a PMW tag: [TAG:4][SIZE:4][DATA:N] */ | |
| readTag(): { tag: number; size: number; data: Buffer } { | |
| const tag = this.readUInt32LE(); | |
| const size = this.readUInt32LE(); | |
| const data = this.readBytes(size); | |
| return { tag, size, data }; | |
| } | |
| /** Read a PMW multi-string (BOM-aware: UTF-16LE or Latin1) */ | |
| readMultiString(data: Buffer): string { | |
| if (data.length === 0) return ""; | |
| if (data.length >= 2) { | |
| const bom = data.readUInt16LE(0); | |
| if (bom === 0xFEFF || bom === 0xFFFE) { | |
| return data.subarray(2).toString("utf16le"); | |
| } | |
| } | |
| return data.toString("latin1"); | |
| } | |
| /** Validate a PMW tag prefix */ | |
| static isValidPMWTag(tag: number): boolean { | |
| return (tag & 0xFFFF0000) === 0xFA500000; | |
| } | |
| /** Scan forward to find the next valid PMW tag */ | |
| recoverToNextTag(): { tag: number; size: number; data: Buffer } | null { | |
| while (this._offset + 8 <= this.buf.length) { | |
| const candidate = this.buf.readUInt32LE(this._offset); | |
| if (BinaryReader.isValidPMWTag(candidate)) { | |
| return this.readTag(); | |
| } | |
| this._offset += 1; | |
| } | |
| return null; | |
| } | |
| readString(length: number, encoding: BufferEncoding = "latin1"): string { | |
| const data = this.readBytes(length); | |
| return data.toString(encoding).replace(/\0+$/, ""); | |
| } | |
| } | |
| export class BinaryWriter { | |
| private chunks: Buffer[] = []; | |
| writeUInt8(v: number): void { | |
| const buf = Buffer.alloc(1); | |
| buf[0] = v; | |
| this.chunks.push(buf); | |
| } | |
| writeUInt16LE(v: number): void { | |
| const buf = Buffer.alloc(2); | |
| buf.writeUInt16LE(v); | |
| this.chunks.push(buf); | |
| } | |
| writeUInt32LE(v: number): void { | |
| const buf = Buffer.alloc(4); | |
| buf.writeUInt32LE(v); | |
| this.chunks.push(buf); | |
| } | |
| writeInt32LE(v: number): void { | |
| const buf = Buffer.alloc(4); | |
| buf.writeInt32LE(v); | |
| this.chunks.push(buf); | |
| } | |
| writeBytes(data: Buffer): void { | |
| this.chunks.push(data); | |
| } | |
| /** Write a PMW tag: [TAG:4][SIZE:4][DATA:N] */ | |
| writeTag(tag: number, data: Buffer): void { | |
| this.writeUInt32LE(tag); | |
| this.writeUInt32LE(data.length); | |
| this.writeBytes(data); | |
| } | |
| /** Write a PMW DWORD tag */ | |
| writeTagDWORD(tag: number, value: number): void { | |
| const buf = Buffer.alloc(4); | |
| buf.writeUInt32LE(value); | |
| this.writeTag(tag, buf); | |
| } | |
| /** Write a PMW multi-string (with UTF-16LE BOM) */ | |
| writeTagMultiString(tag: number, str: string): void { | |
| if (!str) { | |
| this.writeTag(tag, Buffer.alloc(0)); | |
| return; | |
| } | |
| // Always write as UTF-16LE with BOM | |
| const bom = Buffer.from([0xFF, 0xFE]); | |
| const strBuf = Buffer.from(str, "utf16le"); | |
| const data = Buffer.concat([bom, strBuf]); | |
| this.writeTag(tag, data); | |
| } | |
| writeString(str: string, length: number, encoding: BufferEncoding = "latin1"): void { | |
| const buf = Buffer.alloc(length, 0); | |
| buf.write(str, 0, length, encoding); | |
| this.chunks.push(buf); | |
| } | |
| toBuffer(): Buffer { | |
| return Buffer.concat(this.chunks); | |
| } | |
| } | |