Merge pull request #483 from calzoneman/ffmpeg-preflight
Preflight ffprobe requests with node to check headers/error conditions
This commit is contained in:
commit
a4cd0659b6
159
lib/ffmpeg.js
159
lib/ffmpeg.js
|
@ -1,6 +1,10 @@
|
||||||
var Logger = require("./logger");
|
var Logger = require("./logger");
|
||||||
var Config = require("./config");
|
var Config = require("./config");
|
||||||
var spawn = require("child_process").spawn;
|
var spawn = require("child_process").spawn;
|
||||||
|
var https = require("https");
|
||||||
|
var http = require("http");
|
||||||
|
var urlparse = require("url");
|
||||||
|
var statusMessages = require("./status-messages");
|
||||||
|
|
||||||
var USE_JSON = true;
|
var USE_JSON = true;
|
||||||
|
|
||||||
|
@ -21,6 +25,50 @@ var audioOnlyContainers = {
|
||||||
"mp3": true
|
"mp3": true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function testUrl(url, cb, redirCount) {
|
||||||
|
if (!redirCount) redirCount = 0;
|
||||||
|
var data = urlparse.parse(url);
|
||||||
|
if (!/https?:/.test(data.protocol)) {
|
||||||
|
return cb("Video links must start with http:// or https://");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.hostname) {
|
||||||
|
return cb("Invalid link");
|
||||||
|
}
|
||||||
|
|
||||||
|
var transport = (data.protocol === "https:") ? https : http;
|
||||||
|
data.method = "HEAD";
|
||||||
|
var req = transport.request(data, function (res) {
|
||||||
|
req.abort();
|
||||||
|
|
||||||
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
||||||
|
if (redirCount > 2) {
|
||||||
|
return cb("Too many redirects. Please provide a direct link to the " +
|
||||||
|
"file");
|
||||||
|
}
|
||||||
|
return testUrl(res.headers['location'], cb, redirCount + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
var message = statusMessages[res.statusCode];
|
||||||
|
if (!message) message = "";
|
||||||
|
return cb("HTTP " + res.statusCode + " " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^audio|^video/.test(res.headers['content-type'])) {
|
||||||
|
return cb("Server did not return an audio or video file");
|
||||||
|
}
|
||||||
|
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on("error", function (err) {
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
|
||||||
function readOldFormat(buf) {
|
function readOldFormat(buf) {
|
||||||
var lines = buf.split("\n");
|
var lines = buf.split("\n");
|
||||||
var tmp = { tags: {} };
|
var tmp = { tags: {} };
|
||||||
|
@ -149,64 +197,69 @@ exports.query = function (filename, cb) {
|
||||||
"or HTTPS");
|
"or HTTPS");
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.ffprobe(filename, function (err, data) {
|
testUrl(filename, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.code && err.code === "ENOENT") {
|
return cb(err);
|
||||||
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);
|
exports.ffprobe(filename, function (err, data) {
|
||||||
if (m) return cb(m[1]);
|
if (err) {
|
||||||
|
if (err.code && err.code === "ENOENT") {
|
||||||
|
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 " +
|
||||||
|
"version of ffmpeg");
|
||||||
|
|
||||||
Logger.errlog.log(err.stack || err);
|
Logger.errlog.log(err.stack || err);
|
||||||
|
return cb("Unable to query file data with ffmpeg");
|
||||||
|
} else {
|
||||||
|
Logger.errlog.log(err.stack || err);
|
||||||
|
return cb("Unable to query file data with ffmpeg");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = reformatData(data);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.errlog.log(e.stack || e);
|
||||||
return cb("Unable to query file data with ffmpeg");
|
return cb("Unable to query file data with ffmpeg");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.medium === "video") {
|
||||||
|
if (!acceptedCodecs.hasOwnProperty(data.type)) {
|
||||||
|
return cb("Unsupported video codec " + data.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
title: data.title || "Raw Video",
|
||||||
|
duration: data.duration,
|
||||||
|
bitrate: data.bitrate,
|
||||||
|
codec: data.type
|
||||||
|
};
|
||||||
|
|
||||||
|
cb(null, data);
|
||||||
|
} else if (data.medium === "audio") {
|
||||||
|
if (!acceptedAudioCodecs.hasOwnProperty(data.acodec)) {
|
||||||
|
return cb("Unsupported audio codec " + data.acodec);
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
title: data.title || "Raw Audio",
|
||||||
|
duration: data.duration,
|
||||||
|
bitrate: data.bitrate,
|
||||||
|
codec: data.acodec
|
||||||
|
};
|
||||||
|
|
||||||
|
cb(null, data);
|
||||||
} else {
|
} else {
|
||||||
Logger.errlog.log(err.stack || err);
|
return cb("Parsed metadata did not contain a valid video or audio " +
|
||||||
return cb("Unable to query file data with ffmpeg");
|
"stream. Either the file is invalid or it has a format " +
|
||||||
|
"unsupported by this server's version of ffmpeg.");
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
try {
|
|
||||||
data = reformatData(data);
|
|
||||||
} catch (e) {
|
|
||||||
Logger.errlog.log(e.stack || e);
|
|
||||||
return cb("Unable to query file data with ffmpeg");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.medium === "video") {
|
|
||||||
if (!acceptedCodecs.hasOwnProperty(data.type)) {
|
|
||||||
return cb("Unsupported video codec " + data.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
title: data.title || "Raw Video",
|
|
||||||
duration: data.duration,
|
|
||||||
bitrate: data.bitrate,
|
|
||||||
codec: data.type
|
|
||||||
};
|
|
||||||
|
|
||||||
cb(null, data);
|
|
||||||
} else if (data.medium === "audio") {
|
|
||||||
if (!acceptedAudioCodecs.hasOwnProperty(data.acodec)) {
|
|
||||||
return cb("Unsupported audio codec " + data.acodec);
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
title: data.title || "Raw Audio",
|
|
||||||
duration: data.duration,
|
|
||||||
bitrate: data.bitrate,
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
83
lib/status-messages.js
Normal file
83
lib/status-messages.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
// This status message map is taken from the node.js source code. The original
|
||||||
|
// copyright notice for lib/_http_server.js is reproduced below.
|
||||||
|
//
|
||||||
|
// Copyright Joyent, Inc. and other Node contributors.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
// copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
// persons to whom the Software is furnished to do so, subject to the
|
||||||
|
// following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included
|
||||||
|
// in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||||
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
100: "Continue",
|
||||||
|
101: "Switching Protocols",
|
||||||
|
102: "Processing",
|
||||||
|
200: "OK",
|
||||||
|
201: "Created",
|
||||||
|
202: "Accepted",
|
||||||
|
203: "Non-Authoritative Information",
|
||||||
|
204: "No Content",
|
||||||
|
205: "Reset Content",
|
||||||
|
206: "Partial Content",
|
||||||
|
207: "Multi-Status",
|
||||||
|
300: "Multiple Choices",
|
||||||
|
301: "Moved Permanently",
|
||||||
|
302: "Moved Temporarily",
|
||||||
|
303: "See Other",
|
||||||
|
304: "Not Modified",
|
||||||
|
305: "Use Proxy",
|
||||||
|
307: "Temporary Redirect",
|
||||||
|
308: "Permanent Redirect",
|
||||||
|
400: "Bad Request",
|
||||||
|
401: "Unauthorized",
|
||||||
|
402: "Payment Required",
|
||||||
|
403: "Forbidden",
|
||||||
|
404: "Not Found",
|
||||||
|
405: "Method Not Allowed",
|
||||||
|
406: "Not Acceptable",
|
||||||
|
407: "Proxy Authentication Required",
|
||||||
|
408: "Request Time-out",
|
||||||
|
409: "Conflict",
|
||||||
|
410: "Gone",
|
||||||
|
411: "Length Required",
|
||||||
|
412: "Precondition Failed",
|
||||||
|
413: "Request Entity Too Large",
|
||||||
|
414: "Request-URI Too Large",
|
||||||
|
415: "Unsupported Media Type",
|
||||||
|
416: "Requested Range Not Satisfiable",
|
||||||
|
417: "Expectation Failed",
|
||||||
|
418: "I'm a teapot",
|
||||||
|
422: "Unprocessable Entity",
|
||||||
|
423: "Locked",
|
||||||
|
424: "Failed Dependency",
|
||||||
|
425: "Unordered Collection",
|
||||||
|
426: "Upgrade Required",
|
||||||
|
428: "Precondition Required",
|
||||||
|
429: "Too Many Requests",
|
||||||
|
431: "Request Header Fields Too Large",
|
||||||
|
500: "Internal Server Error",
|
||||||
|
501: "Not Implemented",
|
||||||
|
502: "Bad Gateway",
|
||||||
|
503: "Service Unavailable",
|
||||||
|
504: "Gateway Time-out",
|
||||||
|
505: "HTTP Version Not Supported",
|
||||||
|
506: "Variant Also Negotiates",
|
||||||
|
507: "Insufficient Storage",
|
||||||
|
509: "Bandwidth Limit Exceeded",
|
||||||
|
510: "Not Extended",
|
||||||
|
511: "Network Authentication Required"
|
||||||
|
};
|
Loading…
Reference in a new issue