yuv-rx
Version:
Reading videos as Rx Observables of decoded frames
82 lines (81 loc) • 4.02 kB
JavaScript
;
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;