Skip to content

Instantly share code, notes, and snippets.

@carlos8f
Last active August 29, 2015 14:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save carlos8f/57d07c4bf4ab56e9164b to your computer and use it in GitHub Desktop.
Save carlos8f/57d07c4bf4ab56e9164b to your computer and use it in GitHub Desktop.
ripper
.DS_Store
node_modules
npm-debug.log
db
module.exports = function (app) {
app.require('modeler');
return app.modeler;
};
var youtubedl = require('youtube-dl');
module.exports = function (app) {
return function (url, args, cb) {
var video = youtubedl(url, args);
var errored = false;
function done (err) {
if (errored) return;
if (err) {
errored = true;
return cb(err);
}
cb(null, video);
}
video.on('info', function (info) {
video.info = info;
video.size = info.size;
done(null, video);
});
video.once('error', done);
};
};
var ffmpegPath = require('ffmpeg-bin').ffmpeg
, spawn = require('child_process').spawn
module.exports = function (app) {
return function (args, opts) {
return spawn(ffmpegPath, args, opts);
};
};
ripper is:
a media library
free
offline
access the raw files forever
no subscription
no waiting for licenses deals and record companies
basically it saves streamed content, for later.
features:
rip from youtube
media browser
media server
library search
create "mixtapes" of any series of media, save to .rmx file
export mixtapes for sharing
encrypt mixtapes for specific identities
standard container format for sharing:
- RMT, Ripper Mixtape
- manifest
- ordered media
- examples: photos, videos, documents, emails, mp3's, csv's, blobs (use at your own risk)
- progressive download
- figure out security scan for all these types
TBD:
cloud sync (sftp, dropbox, s3)
rip from cd or dvd?
bittorrent? p2p?
mobile?
hosted?
three.js
theoretical:
download ripper, install, open.
auto-creates RIP KEYRING and keypair
prompt with search box. what do you want to rip?
search youtube for results, display thumbnails like album covers, animated gif cache?
click on one, add to queue.
see progress bar as the thing downloads.
auto-rip mp3 in background (preference)
ANY type of streaming would be awesome
upon rip, a RIP KEY is created for the file
having the rip key "unlocks" that file
files can be distributed without rip keys. filenames might not matter
if data is hidden
when you friend someone, you choose to merge rip keys
(in a way that can't be traced to either ID?)
your library inherits all their stuff
you can download files from them at will
you can generate any number of anonymous IDs (hashes) that people can access
for your library list with
you can pay bitcoin for a rip key
verification through checksums - recognized "good sums" for certain things
enter someone's RIP ID on ripnet
(optional: confirmation question.
auto-generated questions and answer security questions,
picked at random. fails after 3 tries)
see a list of RMT ID's
IDs correspond to public RMT cover art and track listing
some may be locked
request a key
request pops up on owner's RipDeck, with requester's rip ID
a secret phrase is shown, which requester needs to unlock the key
the secret phrase is tweeted by the requestee
requester finds the tweet and runs it?
var path = require('path');
module.exports = function (app) {
var dir;
app.require('mkdirp');
if (process.env.HOME) dir = path.join(process.env.HOME, app.conf.id);
else dir = app.conf.id;
app.mkdirp.sync(dir, 0700);
return dir;
};
var spawn = require('child_process').spawn
, path = require('path')
, wrap = require('word-wrap')
, colors = require('colors')
module.exports = function (app) {
app.require('media', 'commander', 'pkg', 'library');
var cmd = app.commander
.version(app.pkg.version)
.usage('[options] <url>\n\n rip a video webpage (i.e. YouTube) to local mp3 file\n\n Alternate Usage: ripper search <keywords>\n\n search YouTube and list results')
.option('--no-mp3', 'just keep the video')
.option('--library <path>', 'location of your media library (default: ~/Ripper)')
.option('--both', 'keep video and mp3 files')
.option('--open', 'open after downloading (OSX)')
cmd.on('--help', function () {
console.log(' Examples:');
console.log('');
console.log(' $ ripper --open dQw4w9WgXcQ');
console.log(' $ ripper https://www.youtube.com/watch?v=ho9rZjlsyYY');
console.log(' $ ripper search "dog the bounty hunter song"');
console.log('');
});
cmd.parse(process.argv);
if (cmd.library) app.library = cmd.library;
if (!cmd.args[0]) {
app.commander.outputHelp();
app.close();
}
else if (cmd.args[0].match(/^(https?:\/\/|[a-zA-Z0-9-_]{11}$)/)) {
if (cmd.args[0].match(/^[a-zA-Z0-9-_]{11}$/)) {
cmd.args[0] = 'https://www.youtube.com/watch?v=' + cmd.args[0];
}
app.media.create({url: cmd.args[0], mp3: cmd.mp3, both: cmd.both, manualFilename: cmd.args[1]}, function (err, media) {
if (err) throw err;
console.log('downloaded ' + media.fulltitle.green + '!');
if (cmd.open) {
spawn('open', [media.path])
.on('exit', function () {
app.close();
})
}
else app.close();
});
}
else {
switch (cmd.args[0]) {
case 'search':
app.require('request');
var query = encodeURIComponent(cmd.args.slice(1).join(' '));
app.request({
uri: 'https://www.googleapis.com/youtube/v3/search?part=id%2Csnippet&key=AIzaSyCHg7AtcfUj82AeXNoPpYQTMaagDcn2Rcg&maxResults=25&q=' + query,
json: true
}, function (err, resp, body) {
if (err) throw err;
if (!body) {
console.error('resp code: ' + resp.statusCode);
return app.close();
}
if (!body.items) {
console.error('invalid body: ' + JSON.stringify(body, null, 2));
return app.close();
}
if (!body.items.length) {
console.log('\nno results.\n');
return app.close();
}
var idx = body.items.length;
body.items.reverse();
body.items.forEach(function (e) {
console.log(
idx + '.',
e.snippet.title.cyan,
'\n\n Uploader: '.blue,
e.snippet.channelTitle.grey,
'\n URL:'.blue,
('https://www.youtube.com/watch?v=' + e.id.videoId).grey,
'\n Description:'.blue,
'\n\n' + wrap(e.snippet.description, {width: 40, indent: ' '}).grey,
'\n'
);
idx--;
});
app.require('prompt');
app.prompt('Enter a number to rip: ', function (num) {
if (!num || !num.match(/^[0-9]{1,3}\.?$/)) return app.close();
num = parseInt(num, 10);
if (num > body.items.length) throw new Error('Number must be within range.');
num = body.items.length - num;
var e = body.items[num];
if (!e) throw new Error('number not in range!');
var u = 'https://www.youtube.com/watch?v=' + e.id.videoId;
console.log('ripper', u);
var proc = spawn(path.join(__dirname, 'ripper'), [u]);
proc.stdout.pipe(process.stdout);
proc.stderr.pipe(process.stderr);
proc.on('exit', function () {
app.close();
});
});
app.close();
});
break;
default:
throw new Error('command not supported');
}
}
};
var fs = require('fs')
, path = require('path')
, Progress = require('progress')
module.exports = function (app) {
app.require('collection');
return app.collection({
name: 'media',
save: function (media, cb) {
app.require('dl', 'ffmpeg', 'mkdirp', 'library');
var basePath = app.library;
app.mkdirp(basePath, function (err) {
if (err) return cb(err);
var args = [];
if (media.manualFilename) args.push('-o', media.manualFilename);
app.dl(media.url, args, function (err, video) {
if (err) return cb(err);
var bar = new Progress(':bar', { total: video.size, width: 80 });
var destPath = media.videoPath = path.join(basePath, media.manualFilename || video.info._filename);
var errored = false;
video.on('data', function (buf) {
bar.tick(buf.length);
});
function done (err) {
if (errored) return;
if (err) {
errored = true;
return cb(err);
}
Object.keys(video.info).forEach(function (k) {
if (!k.match(/^(url)$/i)) media[k] = video.info[k];
});
if (!media.mp3) {
media.path = media.videoPath;
return cb(null, media);
}
media.songPath = destPath.replace(/\.[^\.]+$/, '') + '.mp3';
var stderr = '';
console.log('Processing raw video...'.green);
var ffmpeg = app.ffmpeg(['-y', '-i', destPath, '-vn', '-f', 'mp3', media.songPath])
.on('exit', function (code) {
if (code !== 0) return done(new Error('Error ripping audio: ' + stderr));
if (media.both) {
media.path = media.songPath;
return cb(null, media);
}
fs.unlink(destPath, function (err) {
if (err) return done(err);
media.path = media.songPath;
cb(null, media);
});
});
ffmpeg.stderr.on('data', function (data) {
stderr += data;
});
//ffmpeg.stdout.pipe(process.stdout);
//ffmpeg.stderr.pipe(process.stderr);
}
video
.pipe(fs.createWriteStream(destPath))
.once('finish', function () {
console.log('Done!'.green);
done();
})
.once('error', done)
video.once('error', done)
});
});
}
});
};
id: Ripper
minimal: true
collections: "*_collection.js"
plugins: "*_plugin.js"
title: Ripper
{
"name": "ripper",
"version": "0.4.2",
"description": "rip videos and songs from the interwebs",
"bin": "./ripper",
"repository": {
"type": "git",
"url": "git@gist.github.com:/57d07c4bf4ab56e9164b.git"
},
"author": "Carlos Rodriguez <carlos@s8f.org> (http://s8f.org/)",
"license": "MIT",
"dependencies": {
"colors": "^0.6.2",
"ffmpeg-bin": "0.0.1",
"motley": "^1.4.1",
"progress": "^1.1.8",
"word-wrap": "^0.1.3",
"youtube-dl": "^1.5.10"
},
"scripts": {}
}
#!/usr/bin/env node
require('motley')('motley.yml', __dirname)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment