gts-x / src /lib /gts /formats /binary.ts
JAMES HAN
Initial commit — GTS Modern subtitle converter
dff41a8
Raw
History Blame Contribute Delete
4.24 kB
/** 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);
}
}