UNPKG

node-webplay

Version:

A nodejs streaming server implementation

497 lines (354 loc) 17.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _events = require("events"); var _path = require("path"); var _path2 = _interopRequireDefault(_path); var _child_process = require("child_process"); var _child_process2 = _interopRequireDefault(_child_process); var _fs = require("fs"); var _fs2 = _interopRequireDefault(_fs); var _parseSpawnArgs = require("parse-spawn-args"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function pad(num, size) { var s = "000000000000" + num; return s.substr(s.length - size); } var Processor = function (_EventEmitter) { _inherits(Processor, _EventEmitter); function Processor(name, opt) { _classCallCheck(this, Processor); var _this = _possibleConstructorReturn(this, (Processor.__proto__ || Object.getPrototypeOf(Processor)).call(this)); var defop = { destination: "./", cmd_img: "ffmpeg -t 100 -i \"$(file)\" -vf fps=1/10 \"$(dir)/img%03d.jpg\"", duration_rx: "Duration: (\\d\\d):(\\d\\d):(\\d\\d).(\\d\\d), start: ([\\d\\.]+), bitrate: (\\d+) kb/s", stream_rx: "Stream\\s#0:(\\d+)(?:[\\(\\[](\\w+)[\\)\\]]){0,1}:\\s(Audio|Video):.*?(?:(?:,\\s(\\d+)x(\\d+))|(?:(\\d+) Hz)).*?, (?:(?:(\\d+) kb/s)|(?:stereo)|(?:.*? fps))", cmd_encode: "-i \"$(file)\" -vf \"scale=w=$(width):h=$(height)\" -codec:v libx264 -profile:v high -level 31 -b:v $(vb)k -r 25 -g 50 -sc_threshold 0 -x264opts ratetol=0.1 -minrate $(vb)k -maxrate $(vb)k -bufsize $(vb)k -b:a $(ab)k -codec:a aac -profile:a aac_low -ar 44100 -ac 2 -y \"$(outputfile)\"", quality: [{ videobitrate: 120, height: 144 }, { videobitrate: 320, height: 288 }, { videobitrate: 750, height: 576 }, { videobitrate: 1200, height: 720 }, { videobitrate: 2000, height: 720 }, { videobitrate: 3500, height: 720 }], outputfile: "$(name)_$(width)_$(height)_$(vb).mp4", audiobitrate: 96, max_encoders: 2 }; if (null != opt) _this.options = Object.assign(defop, opt);else _this.options = defop; _this._anchor = new Date(2100, 0, 0).getTime(); _this._create = Date.now(); _this.name = name; if (null == _this.options.id) { var seconds = (_this._anchor - _this._create) / 1000; var n = pad(seconds.toFixed(0), 12); _this.id = n + "_" + _this.name; } else { _this.id = _this.options.id; } _this.finished = false; return _this; } _createClass(Processor, [{ key: "get_full_name", value: function get_full_name() { return this.id; } }, { key: "get_target_dir", value: function get_target_dir() { var target = _path2.default.join(this.options.destination, this.get_full_name()); return _path2.default.resolve(target); } }, { key: "get_streams", value: function get_streams(output) { var regexpd = new RegExp(this.options.duration_rx, "g"); var m = null; var kb = 0; if ((m = regexpd.exec(output)) !== null) { kb = m[6]; } var regexp = new RegExp(this.options.stream_rx, "g"); var streams = []; while ((m = regexp.exec(output)) !== null) { var s = { index: m[1], lang: m[2], kind: m[3], width: m[4], height: m[5], kz: m[6], bps: m[7] }; if (s.kind == "Video" && s.bps == null) s.bps = kb; if (s.lang == null) s.lang = "und"; streams.push(s); } //return { raw : output, match : m }; // // streams.splice(-1, 1); return { raw: output, streams: streams }; } }, { key: "mkdirr", value: function mkdirr(cpath, callback) { var dirs = []; var ld = _path2.default.basename(cpath); var dir = _path2.default.dirname(cpath); var last = ""; //console.log(cpath, ld, dir); while (ld != last) { if (ld != "") dirs.push(ld); last = ld; ld = _path2.default.basename(dir); dir = _path2.default.dirname(dir); //console.log("--", dir, ld, last); } //console.log("--->", dirs); dirs.reverse(); //console.log("+++>", dirs); function mk(dirc, dd, idx, rback) { //console.log("mk", dirc, dd, idx); _fs2.default.mkdir(dirc, function (e) { if (!e || e && e.code === "EEXIST") { if (idx < dd.length - 1) { var m = _path2.default.join(dirc, dd[idx + 1]); mk(m, dd, idx + 1, rback); } else { rback(null); } } else { rback(e); } }); } mk(_path2.default.join(dir, dirs[0]), dirs, 0, callback); } }, { key: "read_stream_info", value: function read_stream_info(filepath) { var _this2 = this; return new Promise(function (resolve, reject) { var dir = _this2.get_target_dir(); var cmdline = _this2.options.cmd_img; cmdline = cmdline.replace("$(file)", filepath); cmdline = cmdline.replace("$(dir)", dir); _this2.mkdirr(dir, function (e) { if (null != e) { reject(e); } else { _this2.emit("processing", new Number(0.05)); _child_process2.default.exec(cmdline /*, {env: process.env}*/, function (err, stdout, stderr) { if (null != err) { reject(err); } else { resolve(_this2.get_streams(stdout + "\n" + stderr)); } }); } }); }); } }, { key: "encode_file", value: function encode_file(resolve, reject, qua, i) { var _this3 = this; var quality = qua; var idx = i; var outs = _fs2.default.openSync(_path2.default.join(this.get_target_dir(), quality[idx].filename + "_out.log"), "a"); var errs = _fs2.default.openSync(_path2.default.join(this.get_target_dir(), quality[idx].filename + "_err.log"), "a"); quality[idx].status = "running"; console.log("FFSPAWN", idx); //, quality[idx].args); var child = _child_process2.default.spawn("ffmpeg", quality[idx].args, { stdio: ["ignore", outs, errs], cwd: process.cwd() //, env: process.env }); child.on("close", function (code, signal) { console.log("FFCLOSE", idx, code, signal, /*quality,*/_this3.finished); if (_this3.finished) return; if (0 != code) { var msg = "Invalid Return Code [" + code + " " + signal + "]"; if (null == code) msg += " The process is being terminated. Check you have enough resources to run it."; reject(new Error(msg)); return; } quality[idx].done = true; var completed = true; var running = 0; var spawn_id = -1; var done = 0; for (var k = 0; k < quality.length; k++) { if (!quality[k].done) { completed = false; if (quality[k].status == "running") running++; if (quality[k].status == "ready" && spawn_id === -1) spawn_id = k; } else { done++; } } if (completed) { _this3.finished = true; resolve(quality); } else { if (running < _this3.options.max_encoders && spawn_id >= 0) { //console.log(">>PP", quality.length, done); _this3.emit("processing", new Number(done / quality.length * 0.7 + 0.1)); _this3.encode_file(resolve, reject, quality, spawn_id); } } }); child.on("error", function (err) { if (err) { _this3.finished = true; //console.log(stdout + stderr); reject(err); } }); } }, { key: "encode", value: function encode(filepath, streams) { var _this4 = this; return new Promise(function (resolve, reject) { if (!streams) { reject(new Error("invalid streams")); return; } if (streams.length != 2) { console.log("INVALID STREAMS", streams, streams.length); var err = new Error("at the moment only audio / video supported [" + streams.length.toString() + "]"); reject(err); return; } var video = null; var max = 10000; for (var i = 0; i < streams.length; i++) { if (streams[i].kind === "Video") { if (video != null) { reject(new Error("more than one video stream unsupported")); return; } video = streams[i]; max = new Number(video.bps); max += 100; } } //console.log(video); //console.log(max); // var quality = _this4.options.quality.slice(); var ratio = video.width / video.height; //console.log(this.options.quality); //console.log(quality); for (var _i = 0; _i < quality.length; _i++) { if (quality[_i].videobitrate > max) quality[_i].videobitrate = 0; quality[_i].width = (quality[_i].height * ratio).toFixed(0); quality[_i].done = false; if (quality[_i].width % 2) quality[_i].width = new Number(quality[_i].width) + 1; if (quality[_i].height % 2) quality[_i].height = new Number(quality[_i].height) + 1; filepath = filepath.replace(/\\/g, "/"); quality[_i].status = "none"; if (quality[_i].videobitrate > 0) { var outputf = _this4.options.outputfile.replace("$(name)", _this4.name); var cmdline = _this4.options.cmd_encode.replace("$(file)", filepath); cmdline = cmdline.replace("$(width)", quality[_i].width); outputf = outputf.replace("$(width)", quality[_i].width); cmdline = cmdline.replace("$(height)", quality[_i].height); outputf = outputf.replace("$(height)", quality[_i].height); cmdline = cmdline.replace(/\$\(vb\)/g, quality[_i].videobitrate); outputf = outputf.replace(/\$\(vb\)/g, quality[_i].videobitrate); quality[_i].audiobitrate = _this4.options.audiobitrate; cmdline = cmdline.replace("$(ab)", quality[_i].audiobitrate); outputf = outputf.replace("$(ab)", quality[_i].audiobitrate); quality[_i].filename = outputf; outputf = _path2.default.join(_this4.get_target_dir(), outputf); outputf = outputf.replace(/\\/g, "/"); cmdline = cmdline.replace("$(outputfile)", outputf); quality[_i].file = outputf; //console.log(cmdline); quality[_i].args = (0, _parseSpawnArgs.parse)(cmdline); quality[_i].status = "ready"; } else { quality[_i].done = true; } } for (var _i2 = 0; _i2 < _this4.options.max_encoders; _i2++) { var idx = _i2; _this4.encode_file(resolve, reject, quality, idx); } }); } }, { key: "package", value: function _package(quality, subdir) { var _this5 = this; return new Promise(function (resolve, reject) { var outdir = subdir; //path.join(this.get_target_dir(), subdir); _this5.mkdirr(outdir, function (err) { if (null != err) { reject(err); return; } //this.emit("processing", 0.85); var args = []; args.push("-k:adaptive"); args.push("-o:" + outdir); var first = true; var cmdline = ""; for (var i = 0; i < quality.length; i++) { if (null != quality[i].file) { if (first) { cmdline += "-i:"; } else { cmdline += "-j:"; } cmdline += quality[i].file; args.push(cmdline); cmdline = ""; args.push("-b:" + quality[i].videobitrate); if (first) { args.push("-s:0"); args.push("-e:0"); } first = false; } } //console.log(args); var outs = _fs2.default.openSync(_path2.default.join(_this5.get_target_dir(), "mgout.log"), "a"); var errs = _fs2.default.openSync(_path2.default.join(_this5.get_target_dir(), "mgerr.log"), "a"); var child = _child_process2.default.spawn("mg", args, { stdio: ["ignore", outs, errs], cwd: process.cwd() // , env: process.env }); /* cmdline, (err, stdout, stderr) =>{ if(null != err){ console.log(stdout + "\n" + stderr); reject(err); return; } resolve(); }); */ child.on("close", function (code /*, signal*/) { if (0 == code) { resolve(); _this5.emit("processing", 1); } else { reject(new Error("mg invalid return code " + code)); } }); child.on("error", function (err) { reject(err); }); }); }); } }]); return Processor; }(_events.EventEmitter); exports.default = Processor; //# sourceMappingURL=index.js.map