diff --git a/Cakefile b/Cakefile new file mode 100644 index 0000000..e064588 --- /dev/null +++ b/Cakefile @@ -0,0 +1,13 @@ +{spawn, exec} = require('child_process') + +task 'build', 'Build project from src/*.coffee to lib/*.js', -> + exec 'coffee -o lib -c src', (err, stdout, stderr) -> + throw err if err + console.log stdout + stderr + +task 'watch', 'Build and watch for changes', -> + child = spawn 'coffee', ['-w', '-o', 'lib', '-c', 'src'] + child.stdout.on 'data', (data) -> + console.log data.toString().replace /\s+$/, '' + child.stderr.on 'data', (data) -> + console.log data.toString() diff --git a/lib/youtube-dl.js b/lib/youtube-dl.js new file mode 100644 index 0000000..b47d9fe --- /dev/null +++ b/lib/youtube-dl.js @@ -0,0 +1,111 @@ +(function() { + var badArgs, cmd, file, fs, hasArg, parseOpts, path, spawn; + spawn = require('child_process').spawn; + fs = require('fs'); + path = require('path'); + badArgs = ['-h', '--help', '-v', '--version', '-U', '--update', '-q', '--quiet', '-s', '--simulate', '-g', '--get-url', '-e', '--get-title', '--get-thumbnail', '--get-description', '--get-filename', '--no-progress', '--console-title']; + parseOpts = function(args) { + var arg, pos, _i, _len; + if (args == null) { + args = []; + } + for (_i = 0, _len = badArgs.length; _i < _len; _i++) { + arg = badArgs[_i]; + if ((pos = hasArg(args, arg)) !== -1) { + args.splice(pos, 1); + } + } + return args; + }; + hasArg = function(arr, arg) { + var a, i, _len; + for (i = 0, _len = arr.length; i < _len; i++) { + a = arr[i]; + if ((a.indexOf(arg)) === 0) { + return i; + } + } + return -1; + }; + file = path.normalize(__dirname + '/../bin/youtube-dl'); + fs.stat(file, function(err, stats) { + if (err) { + require(__dirname + '/../scripts/download'); + return fs.stat(file, function(err, stat) { + if (err) { + throw new Error('youtube-dl file does not exist. tried to download it but failed.'); + } + }); + } + }); + cmd = file; + module.exports.download = function(url, dest, stateChange, download, callback, args) { + var err, regex, size, state, video, youtubedl; + args = parseOpts(args); + args.push(url); + youtubedl = spawn(cmd, args, { + cwd: dest + }); + err = video = size = state = false; + regex = /(\d+\.\d)% of (\d+\.\d+\w) at\s+([^\s]+) ETA ((\d|-)+:(\d|-)+)/; + youtubedl.stdout.on('data', function(data) { + var pos, result; + data = data.toString(); + if (state === 'Downloading video') { + if (result = regex.exec(data)) { + if (size === false) { + stateChange(state, { + video: video, + size: size = result[2] + }); + } + return download({ + percent: result[1], + speed: result[3], + eta: result[4] + }); + } + } else if ((pos = data.indexOf('[download] ')) === 0) { + return state = 'Downloading video'; + } else if ((pos = data.indexOf(']')) !== -1) { + state = data.substring(pos + 2, data.length - 1); + if ((pos = state.indexOf(':')) !== -1) { + video = state.substring(0, pos); + state = state.substring(pos + 2); + } + return stateChange(state, video); + } + }); + youtubedl.stderr.on('data', function(data) { + data = data.toString(); + return err = data.substring(7, data.length - 1); + }); + return youtubedl.on('exit', function(code) { + return callback(err); + }); + }; + module.exports.info = function(url, callback, args) { + var err, info, youtubedl; + args = parseOpts(args); + args = ['--get-url', '--get-title', '--get-thumbnail', '--get-description'].concat(args); + args.push(url); + youtubedl = spawn(cmd, args); + err = info = false; + youtubedl.stdout.on('data', function(data) { + data = data.toString().split("\n"); + return info = { + title: data[0], + url: data[1], + thumbnail: data[2], + description: data[3] + }; + }); + youtubedl.stderr.on('data', function(data) { + data = data.toString(); + return err = data.substring(7, data.length - 1); + }); + return youtubedl.on('exit', function(code) { + return callback(err, info); + }); + }; +}).call(this); diff --git a/src/youtube-dl.coffee b/src/youtube-dl.coffee new file mode 100755 index 0000000..8232680 --- /dev/null +++ b/src/youtube-dl.coffee @@ -0,0 +1,133 @@ +# module dependencies +spawn = require('child_process').spawn +fs = require 'fs' +path = require 'path' + + +# arguments we dont want users to use with youtube-dl +# because they will break this module +badArgs = [ + '-h', '--help' + '-v', '--version' + '-U', '--update' + '-q', '--quiet' + '-s', '--simulate' + '-g', '--get-url' + '-e', '--get-title' + '--get-thumbnail' + '--get-description' + '--get-filename' + '--no-progress' + '--console-title' +] + +# helps parse options used in youtube-dl command +parseOpts = (args = []) -> + for arg in badArgs + if (pos = hasArg args, arg) isnt -1 + args.splice pos, 1 + args + +# returns position if argument is found in array +hasArg = (arr, arg) -> + for a, i in arr + if (a.indexOf arg) is 0 + return i + return -1 + + +# check that youtube-dl file exists +file = path.normalize __dirname + '/../bin/youtube-dl' +fs.stat file, (err, stats) -> + if err + require __dirname + '/../scripts/download' + fs.stat file, (err, stat) -> + if err + throw new Error 'youtube-dl file does not exist. tried to download it but failed.' + + +# command to be called +cmd = file + + +# main download function +module.exports.download = (url, dest, stateChange, download, callback, args) -> + # setup settings + args = parseOpts args + args.push url + + # call youtube-dl + youtubedl = spawn cmd, args, { cwd: dest } + + err = video = size = state = false + regex = /(\d+\.\d)% of (\d+\.\d+\w) at\s+([^\s]+) ETA ((\d|-)+:(\d|-)+)/ + + youtubedl.stdout.on 'data', (data) -> + data = data.toString() + + # check if video is uploading so script can start + # calling the download progress function + if state is 'Downloading video' + if result = regex.exec data + if size is false + stateChange state, + video: video + size: size = result[2] + download + percent: result[1] + speed: result[3] + eta: result[4] + + # about to start downloading video + else if (pos = data.indexOf '[download] ') is 0 + state = 'Downloading video' + + # check if this is any other state + else if (pos = data.indexOf ']') isnt -1 + state = data.substring pos + 2, data.length - 1 + + # get video name + if (pos = state.indexOf ':') isnt -1 + video = state.substring 0, pos + state = state.substring pos + 2 + stateChange state, video + + youtubedl.stderr.on 'data', (data) -> + data = data.toString() + err = data.substring 7, data.length - 1 + + youtubedl.on 'exit', (code) -> + callback err + + +# gets info from a video +module.exports.info = (url, callback, args) -> + # setup settings + args = parseOpts args + args = [ + '--get-url' + '--get-title' + '--get-thumbnail' + '--get-description' + ].concat args + args.push url + + # call youtube-dl + youtubedl = spawn cmd, args + + err = info = false + + youtubedl.stdout.on 'data', (data) -> + data = data.toString().split "\n" + info = + title: data[0] + url: data[1] + thumbnail: data[2] + description: data[3] + + youtubedl.stderr.on 'data', (data) -> + data = data.toString() + err = data.substring 7, data.length - 1 + + youtubedl.on 'exit', (code) -> + callback err, info diff --git a/test/info.js b/test/info.js new file mode 100644 index 0000000..2487372 --- /dev/null +++ b/test/info.js @@ -0,0 +1,13 @@ +(function() { + var youtube; + youtube = require('youtube-dl'); + youtube.info(process.argv[2], function(err, info) { + if (err) { + throw err; + } + console.log('title: ' + info.title); + console.log('url: ' + info.url); + console.log('thumbnail: ' + info.thumbnail); + return console.log('description: ' + info.description); + }); +}).call(this);