UNPKG

yuv-rx

Version:

Reading videos as Rx Observables of decoded frames

82 lines (81 loc) 4.02 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.YuvWriter = void 0; const log_handler_1 = require("./log-handler"); const child_process_1 = require("child_process"); const DEFAULT_TIMESCALE = 24; const DEFAULT_FRAME_DURATION = 1; function ffmpegArgs(outputFile, opts) { var _a, _b; const args = [ '-r', `${(_a = opts === null || opts === void 0 ? void 0 : opts.timescale) !== null && _a !== void 0 ? _a : DEFAULT_TIMESCALE}/${(_b = opts === null || opts === void 0 ? void 0 : opts.frameDuration) !== null && _b !== void 0 ? _b : 1}`, '-f', 'yuv4mpegpipe', '-i', 'pipe:0', '-loglevel', 'error', ]; if (opts === null || opts === void 0 ? void 0 : opts.overwrite) { args.push('-y'); } args.push(outputFile); return args; } function y4mHeader(frameHeader, timescale, frameDuration) { return Buffer.from(`YUV4MPEG2 W${frameHeader.width} H${frameHeader.height} F${timescale}:${frameDuration} Ip A1:1 C420mpeg2 XYSCSS=420MPEG2\n`, 'ascii'); } function frameToBuffer(frame) { return Buffer.concat([Buffer.from('FRAME\n', 'ascii'), frame.colorPlanes.y.data, frame.colorPlanes.u.data, frame.colorPlanes.v.data]); } class YuvWriter { constructor(options = {}) { this.options = options; } write(frames, outputFile, options) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { const opts = Object.assign(Object.assign({ timescale: DEFAULT_TIMESCALE, frameDuration: DEFAULT_FRAME_DURATION }, this.options), (options !== null && options !== void 0 ? options : {})); const ffmpeg = (_a = opts.ffmpeg) !== null && _a !== void 0 ? _a : 'ffmpeg'; const handleFfmpegLog = (0, log_handler_1.logHandlerFn)((_b = opts === null || opts === void 0 ? void 0 : opts.ffmpegLogHandler) !== null && _b !== void 0 ? _b : 'none'); return new Promise((resolve, reject) => { let framesWritten = 0; let headerWritten = false; const child = (0, child_process_1.spawn)(ffmpeg, ffmpegArgs(outputFile, options), { stdio: 'pipe' }); child.on('exit', (code) => { if (code !== 0) { reject(new Error(`FFMpeg exited with code ${code}`)); } else { resolve(framesWritten); } }); child.stdin.on('error', err => reject(err)); child.stdout.on('data', handleFfmpegLog); child.stderr.on('data', handleFfmpegLog); frames.subscribe({ next: frame => { if (!headerWritten) { child.stdin.write(y4mHeader(frame.header, opts.timescale, opts.frameDuration)); headerWritten = true; } child.stdin.write(frameToBuffer(frame)); framesWritten++; }, error: err => { reject(err); child.kill(); }, complete: () => child.stdin.end(), }); }); }); } } exports.YuvWriter = YuvWriter;