sync/lib/ffmpeg.js

213 lines
6.4 KiB
JavaScript
Raw Normal View History

var Logger = require("./logger");
var Config = require("./config");
var spawn = require("child_process").spawn;
2015-05-20 02:07:55 +00:00
var USE_JSON = true;
2015-05-19 23:48:08 +00:00
var acceptedCodecs = {
"mov/h264": true,
2014-06-04 04:21:00 +00:00
"flv/h264": true,
"matroska/vp8": true,
"matroska/vp9": true,
2014-06-07 17:45:52 +00:00
"ogg/theora": true
};
var acceptedAudioCodecs = {
"mp3": true,
"vorbis": true
};
2014-06-07 17:45:52 +00:00
var audioOnlyContainers = {
"mp3": true
};
2015-05-19 23:48:08 +00:00
function readOldFormat(buf) {
var lines = buf.split("\n");
var tmp = { tags: {} };
var data = {
streams: []
};
lines.forEach(function (line) {
if (line.match(/\[stream\]|\[format\]/i)) {
return;
} else if (line.match(/\[\/stream\]/i)) {
data.streams.push(tmp);
tmp = { tags: {} };
} else if (line.match(/\[\/format\]/i)) {
data.format = tmp;
tmp = { tags: {} };
} else {
var kv = line.split("=");
var key = kv[0].toLowerCase();
if (key.indexOf("tag:") === 0) {
tmp.tags[key.split(":")[1]] = kv[1];
} else {
tmp[key] = kv[1];
}
}
});
return data;
}
function reformatData(data) {
var reformatted = {};
var duration = parseInt(data.format.duration, 10);
if (isNaN(duration)) duration = "--:--";
reformatted.duration = Math.ceil(duration);
var bitrate = parseInt(data.format.bit_rate, 10) / 1000;
if (isNaN(bitrate)) bitrate = 0;
reformatted.bitrate = bitrate;
2015-05-20 02:07:55 +00:00
reformatted.title = data.format.tags ? data.format.tags.title : null;
2015-05-19 23:48:08 +00:00
var container = data.format.format_name.split(",")[0];
data.streams.forEach(function (stream) {
if (stream.codec_type === "video") {
reformatted.vcodec = stream.codec_name;
2015-05-20 02:07:55 +00:00
if (!reformatted.title && stream.tags) {
2015-05-19 23:48:08 +00:00
reformatted.title = stream.tags.title;
}
} else if (stream.codec_type === "audio") {
reformatted.acodec = stream.codec_name;
}
});
if (reformatted.vcodec && !(audioOnlyContainers.hasOwnProperty(container))) {
reformatted.type = [container, reformatted.vcodec].join("/");
reformatted.medium = "video";
} else if (reformatted.acodec) {
reformatted.type = [container, reformatted.acodec].join("/");
reformatted.medium = "audio";
}
return reformatted;
}
exports.ffprobe = function ffprobe(filename, cb) {
var childErr;
var args = ["-show_streams", "-show_format", filename];
2015-05-20 02:07:55 +00:00
if (USE_JSON) args = ["-of", "json"].concat(args);
2015-05-19 23:48:08 +00:00
var child = spawn(Config.get("ffmpeg.ffprobe-exec"), args);
var stdout = "";
var stderr = "";
child.on("error", function (err) {
childErr = err;
});
child.stdout.on("data", function (data) {
stdout += data;
});
child.stderr.on("data", function (data) {
stderr += data;
});
child.on("close", function (code) {
if (code !== 0) {
2015-05-20 02:07:55 +00:00
if (stderr.match(/unrecognized option|json/i) && USE_JSON) {
2015-05-19 23:48:08 +00:00
Logger.errlog.log("Warning: ffprobe does not support -of json. " +
"Assuming it will have old output format.");
2015-05-20 02:07:55 +00:00
USE_JSON = false;
2015-05-19 23:48:08 +00:00
return ffprobe(filename, cb);
}
if (!childErr) childErr = new Error(stderr);
return cb(childErr);
}
var result;
2015-05-20 02:07:55 +00:00
if (USE_JSON) {
2015-05-19 23:48:08 +00:00
try {
result = JSON.parse(stdout);
} catch (e) {
return cb(new Error("Unable to parse ffprobe output: " + e.message));
}
} else {
try {
result = readOldFormat(stdout);
} catch (e) {
return cb(new Error("Unable to parse ffprobe output: " + e.message));
}
}
return cb(null, result);
});
}
exports.query = function (filename, cb) {
if (!Config.get("ffmpeg.enabled")) {
return cb("Raw file playback is not enabled on this server");
}
2014-06-07 17:45:52 +00:00
if (!filename.match(/^https?:\/\//)) {
return cb("Raw file playback is only supported for links accessible via HTTP " +
"or HTTPS");
}
2015-05-19 23:48:08 +00:00
exports.ffprobe(filename, function (err, data) {
if (err) {
if (err.code && err.code === "ENOENT") {
2015-05-19 23:48:08 +00:00
return cb("Failed to execute `ffprobe`. Set ffmpeg.ffprobe-exec to " +
"the correct name of the executable in config.yaml. If " +
"you are using Debian or Ubuntu, it is probably avprobe.");
} else if (err.message) {
if (err.message.match(/protocol not found/i))
return cb("Link uses a protocol unsupported by this server's ffmpeg");
var m = err.message.match(/(http error .*)/i);
if (m) return cb(m[1]);
Logger.errlog.log(err.stack || err);
return cb("Unable to query file data with ffmpeg");
} else {
2015-05-17 04:36:04 +00:00
Logger.errlog.log(err.stack || err);
return cb("Unable to query file data with ffmpeg");
}
}
2015-05-19 23:48:08 +00:00
try {
data = reformatData(data);
} catch (e) {
2015-05-20 02:07:55 +00:00
Logger.errlog.log(e.stack || e);
2015-05-19 23:48:08 +00:00
return cb("Unable to query file data with ffmpeg");
}
2015-05-19 23:48:08 +00:00
if (data.medium === "video") {
if (!acceptedCodecs.hasOwnProperty(data.type)) {
return cb("Unsupported video codec " + data.type);
}
2015-05-19 23:48:08 +00:00
data = {
title: data.title || "Raw Video",
duration: data.duration,
bitrate: data.bitrate,
codec: data.type
};
cb(null, data);
2015-05-19 23:48:08 +00:00
} else if (data.medium === "audio") {
2015-05-20 02:07:55 +00:00
if (!acceptedAudioCodecs.hasOwnProperty(data.acodec)) {
return cb("Unsupported audio codec " + data.acodec);
}
2015-05-19 23:48:08 +00:00
data = {
title: data.title || "Raw Audio",
duration: data.duration,
bitrate: data.bitrate,
2015-05-20 02:07:55 +00:00
codec: data.acodec
};
cb(null, data);
} else {
return cb("Parsed metadata did not contain a valid video or audio stream. " +
"Either the file is invalid or it has a format unsupported by " +
"this server's version of ffmpeg.");
}
});
};