refactor import by youtube url into plugin

This commit is contained in:
Andrew Kelley 2014-05-04 20:28:07 -07:00
parent cb6077bc87
commit fa21bdc3d2
2 changed files with 130 additions and 87 deletions

View file

@ -17,7 +17,6 @@ var safePath = require('./safe_path');
var PassThrough = require('stream').PassThrough;
var url = require('url');
var superagent = require('superagent');
var ytdl = require('ytdl');
module.exports = Player;
@ -26,16 +25,6 @@ groove.setLogging(groove.LOG_WARNING);
var cpuCount = require('os').cpus().length;
// sorted from worst to best
var YTDL_AUDIO_ENCODINGS = [
'mp3',
'aac',
'wma',
'vorbis',
'wav',
'flac',
];
var PLAYER_KEY_PREFIX = "Player.";
var LIBRARY_KEY_PREFIX = "Library.";
var LIBRARY_DIR_PREFIX = "LibraryDir.";
@ -268,6 +257,8 @@ function Player(db, musicDirectory, instantBufferBytes) {
this.expectHeaders = true;
this.playlistItemDeleteQueue = [];
this.importUrlFilters = [];
}
Player.prototype.initialize = function(cb) {
@ -797,88 +788,78 @@ Player.prototype.importUrl = function(urlString, cb) {
cb = cb || logIfError;
var tmpDir = path.join(self.musicDirectory, '.tmp');
var filterIndex = 0;
mkdirp(tmpDir, function(err) {
if (err) return cb(err);
var parsedUrl = url.parse(urlString);
// detect youtube downloads
if ((parsedUrl.hostname === 'youtube.com' || parsedUrl.hostname === 'www.youtube.com') &&
parsedUrl.pathname === '/watch')
{
var bestFormat = null;
ytdl.getInfo(urlString, gotYouTubeInfo);
} else {
var remoteFilename = path.basename(parsedUrl.pathname);
var decodedFilename;
try {
decodedFilename = decodeURI(remoteFilename);
} catch (err) {
decodedFilename = remoteFilename;
}
var req = superagent.get(urlString);
handleDownload(req, decodedFilename);
}
function gotYouTubeInfo(err, info) {
if (err) return cb(err);
for (var i = 0; i < info.formats.length; i += 1) {
var format = info.formats[i];
if (bestFormat == null || format.audioBitrate > bestFormat.audioBitrate ||
(format.audioBitrate === bestFormat.audioBitrate &&
YTDL_AUDIO_ENCODINGS.indexOf(format.audioEncoding) >
YTDL_AUDIO_ENCODINGS.indexOf(bestFormat.audioEncoding)))
{
bestFormat = format;
}
}
if (YTDL_AUDIO_ENCODINGS.indexOf(bestFormat.audioEncoding) === -1) {
console.warn("YouTube Import: unrecognized audio format:", bestFormat.audioEncoding);
}
var req = ytdl(urlString, {filter: filter});
handleDownload(req, info.title + '.' + bestFormat.container);
function filter(format) {
return format.audioBitrate === bestFormat.audioBitrate &&
format.audioEncoding === bestFormat.audioEncoding;
}
}
function handleDownload(req, remoteFilename) {
var ext = path.extname(remoteFilename);
var destPath = path.join(tmpDir, uuid() + ext);
var ws = fs.createWriteStream(destPath);
var calledCallback = false;
req.pipe(ws);
ws.on('close', function(){
if (calledCallback) return;
self.importFile(ws.path, remoteFilename, function(err, dbFile) {
if (err) {
cleanAndCb(err);
} else {
calledCallback = true;
cb(null, dbFile);
}
});
});
ws.on('error', cleanAndCb);
req.on('error', cleanAndCb);
function cleanAndCb(err) {
fs.unlink(destPath, function(err) {
if (err) {
console.warn("Unable to clean up temp file:", err.stack);
}
});
if (calledCallback) return;
calledCallback = true;
cb(err);
}
}
tryImportFilter();
});
function tryImportFilter() {
var importPlugin = self.importUrlFilters[filterIndex];
if (importPlugin) {
importPlugin.importUrl(urlString, callNextFilter);
} else {
downloadRaw();
}
function callNextFilter(err, dlStream, filename) {
if (err || !dlStream) {
if (err) console.warn("import filter error, skipping:", err.stack);
filterIndex += 1;
tryImportFilter();
return;
}
handleDownload(dlStream, filename);
}
}
function downloadRaw() {
var parsedUrl = url.parse(urlString);
var remoteFilename = path.basename(parsedUrl.pathname);
var decodedFilename;
try {
decodedFilename = decodeURI(remoteFilename);
} catch (err) {
decodedFilename = remoteFilename;
}
var req = superagent.get(urlString);
handleDownload(req, decodedFilename);
}
function handleDownload(req, remoteFilename) {
var ext = path.extname(remoteFilename);
var destPath = path.join(tmpDir, uuid() + ext);
var ws = fs.createWriteStream(destPath);
var calledCallback = false;
req.pipe(ws);
ws.on('close', function(){
if (calledCallback) return;
self.importFile(ws.path, remoteFilename, function(err, dbFile) {
if (err) {
cleanAndCb(err);
} else {
calledCallback = true;
cb(null, dbFile);
}
});
});
ws.on('error', cleanAndCb);
req.on('error', cleanAndCb);
function cleanAndCb(err) {
fs.unlink(destPath, function(err) {
if (err) {
console.warn("Unable to clean up temp file:", err.stack);
}
});
if (calledCallback) return;
calledCallback = true;
cb(err);
}
}
function logIfError(err) {
if (err) {
console.error("Unable to import by URL.", err.stack, "URL:", urlString);

62
lib/plugins/ytdl.js Normal file
View file

@ -0,0 +1,62 @@
var ytdl = require('ytdl');
var url = require('url');
module.exports = YtDlPlugin;
// sorted from worst to best
var YTDL_AUDIO_ENCODINGS = [
'mp3',
'aac',
'wma',
'vorbis',
'wav',
'flac',
];
function YtDlPlugin(gb) {
gb.player.importUrlFilters.push(this);
}
YtDlPlugin.prototype.importUrl = function(urlString, cb) {
var parsedUrl = url.parse(urlString);
var isYouTube = (parsedUrl.pathname === '/watch' &&
(parsedUrl.hostname === 'youtube.com' ||
parsedUrl.hostname === 'www.youtube.com' ||
parsedUrl.hostname === 'm.youtube.com')) ||
parsedUrl.hostname === 'youtu.be' ||
parsedUrl.hostname === 'www.youtu.be';
if (!isYouTube) {
cb();
return;
}
var bestFormat = null;
ytdl.getInfo(urlString, gotYouTubeInfo);
function gotYouTubeInfo(err, info) {
if (err) return cb(err);
for (var i = 0; i < info.formats.length; i += 1) {
var format = info.formats[i];
if (bestFormat == null || format.audioBitrate > bestFormat.audioBitrate ||
(format.audioBitrate === bestFormat.audioBitrate &&
YTDL_AUDIO_ENCODINGS.indexOf(format.audioEncoding) >
YTDL_AUDIO_ENCODINGS.indexOf(bestFormat.audioEncoding)))
{
bestFormat = format;
}
}
if (YTDL_AUDIO_ENCODINGS.indexOf(bestFormat.audioEncoding) === -1) {
console.warn("YouTube Import: unrecognized audio format:", bestFormat.audioEncoding);
}
var req = ytdl(urlString, {filter: filter});
var filename = info.title + '.' + bestFormat.container;
cb(null, req, filename);
function filter(format) {
return format.audioBitrate === bestFormat.audioBitrate &&
format.audioEncoding === bestFormat.audioEncoding;
}
}
};