node-webplay
Version:
A nodejs streaming server implementation
497 lines (354 loc) • 17.7 kB
JavaScript
;
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