Skip to content

Instantly share code, notes, and snippets.

@JALsnipe
Forked from adammw/download.js
Last active August 29, 2015 14:07
Show Gist options
  • Save JALsnipe/ba8820318dd3f854d4fe to your computer and use it in GitHub Desktop.
Save JALsnipe/ba8820318dd3f854d4fe to your computer and use it in GitHub Desktop.
/*
* CLI Tool to download streamable songs from SoundCloud API
* Requires Node.js, Flow-JS and cli libraries
*/
var cli = require('cli').enable('status'),
fs = require('fs'),
flow = require('flow'),
path = require('path'),
SC = require('./node-soundcloud.js');
SC.initialize({
client_id: "00000000000000000000000000000000" // Replace this with your client ID
});
cli.parse({
tag: ['t', 'Restrict to tag', 'string', null],
outdir: ['D', 'Directory to download files to', 'dir', '.'],
max: ['n', 'Number of files to download', 'num', 10]
});
cli.main(function(args, options) {
cli.debug("Downloading track listing");
flow.exec(
function() {
// Get the track listing
SC.get('/tracks', {limit: options.max, filter: 'streamable', order: 'hotness', tags: options.tag}, this);
},
function(tracks, error) {
if (error) {
cli.fatal(error.message);
}
// Download each track sequentially
flow.serialForEach(tracks,
function(track) {
cli.info("Downloading \"" + track.title + "\" by " + track.user.username);
cli.debug("Stream url is: " + track.stream_url);
var outpath = path.join(options.outdir, (track.user.username + " - " + track.title + ".mp3").replace(/[\/\\:]/g,''));
var nextTrack = this;
flow.exec(
function() {
// Check if the file has already been downloaded (already exists)
path.exists(outpath, this);
},
function(exists) {
// If it exists, get the file size and do a HEAD request on the file to be downloaded
if (exists) {
fs.stat(outpath, this.MULTI());
SC.stream(track.stream_url, this.MULTI(), 'HEAD');
} else {
return this();
}
},
function(args) {
// If we have the stats and response, check if the file sizes match
if (args && args.length == 2 && args[0].length >= 2 && args[1].length >= 1) {
var stats = args[0][1];
var res = args[1][0];
if (args.length == 2 && res.headers['content-length'] && stats.size && stats.size >= res.headers['content-length']) {
cli.info("Track already downloaded fully, skipping...");
return nextTrack();
}
}
// Otherwise, download the track
SC.stream(track.stream_url, this);
},
function(res) {
var callback = this;
var file = fs.createWriteStream(outpath);
res.pipe(file);
if (res.headers['content-length']) {
res.on('data', function(chunk) {
cli.progress(file.bytesWritten / res.headers['content-length']);
});
}
res.on('end', function() {
cli.progress(1)
cli.ok('Download Complete!');
callback();
});
},
nextTrack
);
}
);
}
);
});
var http = require('http'),
querystring = require('querystring'),
url = require('url');
const SOUNDCLOUD_HOST = 'api.soundcloud.com';
const SOUNDCLOUD_PORT = 80;
var SC = module.exports = {};
SC.initialize = function(options) {
SC._config = options;
};
SC.get = function(path, params, callback) {
var httpopts = (typeof path == "string") ? url.parse(path, true) : path;
if (typeof params == "function") {
callback = params;
params = {};
}
for (var i in params) {
httpopts.query[i] = params[i];
}
httpopts.query.client_id = SC._config.client_id;
httpopts.host = SOUNDCLOUD_HOST;
httpopts.port = SOUNDCLOUD_PORT;
httpopts.path = httpopts.pathname + ".json?" + querystring.stringify(httpopts.query);
var req = http.get(httpopts, function(res) {
var buffer = "";
res.on('data', function(chunk) {
buffer += chunk;
});
res.on('end', function() {
try {
var data = JSON.parse(buffer);
callback.call(data, data, null);
} catch (e) {
callback.call(null, null, e);
}
});
});
req.on('error', function(e) {
callback.call(null, null, e);
});
};
SC.stream = function(stream_url, callback, method) {
var httpopts = (typeof stream_url == "string") ? url.parse(stream_url, true) : stream_url;
httpopts.query.client_id = SC._config.client_id;
httpopts.path = httpopts.pathname + "?" + querystring.stringify(httpopts.query);
httpopts.method = method || "GET";
(function request(httpopts) {
var req = http.get(httpopts, function(res) {
if (res.headers.location) {
httpopts = url.parse(res.headers.location);
httpopts.method = method || "GET";
request(httpopts);
} else {
callback.call(res, res, null);
}
});
req.on('error', function(e) {
callback.call(null, null, e);
});
})(httpopts);
};
/* If you want to download in parallel without the flow library, try:
for (var i in tracks) {
var track = tracks[i];
var totalsize = 0;
var byteswritten = 0;
SC.stream(track.stream_url, function(res) {
if (res.headers['content-length'] && path.existsSync(outpath) && path.statSync(outpath).size >= res.headers['content-length']) {
cli.info("Track already downloaded fully, skipping...");
// Note that we can't really stop the download - we are wasting the data by ignoring it
} else {
var file = fs.createWriteStream(outpath);
res.pipe(file);
if (res.headers['content-length']) {
totalsize += parseInt(res.headers['content-length']);
var lastbyteswritten = 0;
var lastdelta = 0;
res.on('data', function(chunk) {
lastdelta = (file.bytesWritten - lastbyteswritten);
byteswritten += lastdelta;
cli.progress(byteswritten / totalsize);
});
file.on('drain', function() { // needed because of the pause/resume done by pipe - data events done twice
byteswritten -= lastdelta;
});
}
}
});
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment