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 Config = require("./config");
|
||||
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;
|
||||
|
||||
|
@ -21,6 +25,50 @@ var audioOnlyContainers = {
|
|||
"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) {
|
||||
var lines = buf.split("\n");
|
||||
var tmp = { tags: {} };
|
||||
|
@ -149,64 +197,69 @@ exports.query = function (filename, cb) {
|
|||
"or HTTPS");
|
||||
}
|
||||
|
||||
exports.ffprobe(filename, function (err, data) {
|
||||
testUrl(filename, function (err) {
|
||||
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 ffmpeg");
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
var m = err.message.match(/(http error .*)/i);
|
||||
if (m) return cb(m[1]);
|
||||
exports.ffprobe(filename, function (err, data) {
|
||||
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");
|
||||
}
|
||||
|
||||
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 {
|
||||
Logger.errlog.log(err.stack || err);
|
||||
return cb("Unable to query file data with ffmpeg");
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
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