UNPKG

yuv-rx

Version:

Reading videos as Rx Observables of decoded frames

123 lines (122 loc) 5.15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.YuvParser = void 0; const reader_1 = require("./reader"); const rxjs_1 = require("rxjs"); const frame_1 = require("./frame"); const y4m_1 = require("./y4m"); function parseFrame(data, header, frameHeader, fn) { if (!frameHeader.startsWith('FRAME')) { throw 'Invalid frame header'; } const luma = header.lumaSize(); const chroma = header.chromaSize(); return { header: { fn: fn, width: header.width, height: header.height }, colorPlanes: { y: new frame_1.Gray(header.width, header.height, data.subarray(0, luma)), u: new frame_1.Gray(header.chromaWidth(), header.chromaHeight(), data.subarray(luma, luma + chroma)), v: new frame_1.Gray(header.chromaWidth(), header.chromaHeight(), data.subarray(luma + chroma)) } }; } class YuvParser { constructor(options) { this.options = options !== null && options !== void 0 ? options : {}; } parseHeader(chunk) { const headerString = chunk.toString('ascii'); if (this.options.verbose) { console.log(`Video header: ${headerString} (${chunk})`); } return y4m_1.Y4MHeader.fromString(headerString); } nextChunk(buffer, state, frameSize) { switch (state) { case 'reading header': case 'reading frame header': const idx = buffer.indexOf('\n', 0, 'ascii'); return idx != -1 ? buffer.subarray(0, idx + 1) : null; case 'reading frame': if (frameSize == null) { throw new Error('Illegal Y4M parser state: frame size should be known by now'); } else { return buffer.length >= frameSize ? buffer.subarray(0, frameSize) : null; } } } nextState(state) { switch (state) { case 'reading header': return 'reading frame header'; case 'reading frame header': return 'reading frame'; case 'reading frame': return 'reading frame header'; } } readCustom(ffmpegArgs, options) { return this.readFromStream(() => { var _a; return (0, reader_1.yuv4mpegStreamForCustomInput)((_a = this.options.ffmpeg) !== null && _a !== void 0 ? _a : 'ffmpeg', ffmpegArgs, Object.assign(Object.assign({}, this.options), (options !== null && options !== void 0 ? options : {}))); }); } read(path, options) { return this.readFromStream(() => { var _a; return (0, reader_1.yuv4mpegStreamForPath)((_a = this.options.ffmpeg) !== null && _a !== void 0 ? _a : 'ffmpeg', path, Object.assign(Object.assign({}, this.options), (options !== null && options !== void 0 ? options : {}))); }); } readFromStream(createStream) { return new rxjs_1.Observable(subscriber => { let state = 'reading header'; let buffer = Buffer.allocUnsafe(0); let header = null; let frameHeader = null; let fn = 0; createStream().subscribe({ next: data => { buffer = Buffer.concat([buffer, data]); let chunk = null; while ((chunk = this.nextChunk(buffer, state, header === null || header === void 0 ? void 0 : header.getFrameSize())) != null) { buffer = buffer.subarray(chunk.length); switch (state) { case 'reading header': header = this.parseHeader(chunk.subarray(0, -1)); break; case 'reading frame header': frameHeader = chunk.subarray(0, -1).toString('ascii'); if (this.options.verbose) { console.log(`Frame ${fn} header: ${frameHeader}`); } break; case 'reading frame': subscriber.next(parseFrame(chunk, header, frameHeader, fn)); fn += 1; break; } state = this.nextState(state); } }, error: subscriber.error, complete: () => { if (buffer.length != 0) { subscriber.error(new Error(`Unparsed ${buffer.length} bytes in the end of the stream`)); } else if (state == 'reading frame') { subscriber.error(new Error('Parser finished while expecting frame data')); } else { subscriber.complete(); } } }); }); } } exports.YuvParser = YuvParser;