UNPKG

bddvdsubreader

Version:

Parse VobSub and PGS subtitles with NodeJS

178 lines (144 loc) 4.59 kB
// Value Bits n=length, c=color // 1-3 4 n n c c // 4-15 8 0 0 n n n n c c // 16-63 12 0 0 0 0 n n n n | n n c c // 64-255 16 0 0 0 0 0 0 n n | n n n n n n c c const b16 = 0b11111100; const b12 = 0b11110000; const b8 = 0b11000000; const col = ['b', 'p', 'e1', 'e2']; class SPUImage { constructor(width, height) { this._width = width; this._height = height; } setPalette(palette, alpha) { this._palette = palette; this._alpha = alpha; } _parseRle(buffer, offset, half) { const data = buffer.readUIntBE(offset, 3); let group = (half ? data >> 4 : data >> 8) & 0xffff; const byte = group >> 8; let is16 = false; // dsize half +offset // 1 1 0 // 1 0 1 // 2 0 1 // 2 1 1 // 3 1 1 // 3 0 2 // 4 0 2 // 4 1 2 if ((byte & b16) === 0) { is16 = true; offset += 2; } else if ((byte & b12) === 0) { half = !half; group = group >> 4; offset += 1 + !half; } else if ((byte & b8) === 0) { group = group >> 8; offset += 1; } else { half = !half; group = group >> 12; offset += !half; } const color = group & 0b11; let size = group >> 2; if (size === 0) { if (is16) { size = -1; } else { throw new Error('Invalid RLE group size'); } } if (color > 3) { throw new Error('Invalid RLE color'); } return { size, color, half, offset }; } _scanLine(line_index, input, length) { let x = 0; let offset = 0; let half = false; while (x < length) { const data = this._parseRle(input, offset, half); // console.log(data); offset = data.offset; half = data.half; const count = data.size < 0 ? length - x : data.size; if (x + count > length) { throw new Error('Scan line is too long'); } const color = col[data.color]; if(this._alpha[color] != 0){ this._lines.push({ line_index, start: x, length: count, color }); } x += count; } if (x > length) { throw new Error('Decoded scan line is too long'); } return offset + half; } decompressRLEImage(PXDtf, PXDbf) { this._lines = []; const data = [PXDtf, PXDbf]; const offsets = [0, 0]; let curLine = 0; while(curLine < this._height){ const odd = curLine % 2; const consumed = this._scanLine( curLine, data[odd].subarray(offsets[odd]), this._width * this._height, ); offsets[odd] += consumed; curLine++; } } getPxData() { const pixelData = Buffer.alloc(this._width * this._height * 4); const color = Buffer.alloc(4); let row = 0; while(row < this._lines.length){ const row_data = this._lines[row]; const offset = ((row_data.line_index * this._width) + row_data.start); const length = row_data.length; const c = this._palette[row_data.color]; const a = this._alpha[row_data.color] * 0x11; color.set([c.r, c.g, c.b, a]); pixelData.fill(color, offset * 4, (offset + length) * 4) row++; } return pixelData; } } export default class RGBAImage { #width; #height; #palette; #alpha; #PXDtf; #PXDbf; constructor(w, h, p, a, PXDtf, PXDbf) { this.#width = w; this.#height = h; this.#palette = p; this.#alpha = a; this.#PXDtf = PXDtf; this.#PXDbf = PXDbf; } get(){ const pic = new SPUImage(this.#width, this.#height); pic.setPalette(this.#palette, this.#alpha); pic.decompressRLEImage(this.#PXDtf, this.#PXDbf); return pic.getPxData(); } }