├── .gitignore ├── README.md ├── client ├── example_1.png ├── example_2.png ├── example_3.png ├── example_4.png ├── favicon.png ├── index.html └── script.js └── server ├── .ebextensions └── nodecommand.config ├── .gitignore ├── app.js ├── bin └── www ├── package.json ├── public └── stylesheets │ └── style.css ├── routes └── api.js └── views ├── error.jade └── layout.jade /.gitignore: -------------------------------------------------------------------------------- 1 | .elasticbeanstalk/ 2 | 3 | # Elastic Beanstalk Files 4 | .elasticbeanstalk/* 5 | !.elasticbeanstalk/*.cfg.yml 6 | !.elasticbeanstalk/*.global.yml 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Torrent Pizza 2 | 3 | *Stream video from torrents in your browser* 4 | 5 | Powered by [WebTorrent](https://github.com/feross/webtorrent) 6 | 7 | **Update: Not working anymore!** It was a fun experiment, but it couldn't last :) 8 | -------------------------------------------------------------------------------- /client/example_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berpj/torrent-pizza/4f7d27026c5074cc10b8a4fa22b8bb22f99d3b16/client/example_1.png -------------------------------------------------------------------------------- /client/example_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berpj/torrent-pizza/4f7d27026c5074cc10b8a4fa22b8bb22f99d3b16/client/example_2.png -------------------------------------------------------------------------------- /client/example_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berpj/torrent-pizza/4f7d27026c5074cc10b8a4fa22b8bb22f99d3b16/client/example_3.png -------------------------------------------------------------------------------- /client/example_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berpj/torrent-pizza/4f7d27026c5074cc10b8a4fa22b8bb22f99d3b16/client/example_4.png -------------------------------------------------------------------------------- /client/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berpj/torrent-pizza/4f7d27026c5074cc10b8a4fa22b8bb22f99d3b16/client/favicon.png -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | Torrent Pizza 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 137 | 138 | 139 | 147 | 148 |
149 |

TorrentPizza

150 | 151 |

Stream video torrents in your browser.

152 | 153 |
154 | 155 |
156 | 157 | 158 | 159 |
160 |
161 | 162 | 165 | 166 |
167 | 209 |
210 | 211 | 212 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /client/script.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var api = 'api.torrent.pizza'; 3 | 4 | $('#input').on('input', function(e){ 5 | if ($('#input').val().length > 0) 6 | $('#instructions').show(); 7 | else 8 | $('#instructions').hide(); 9 | }); 10 | 11 | $('form').submit(function(e){ 12 | e.preventDefault(); 13 | 14 | $('form').hide(); 15 | $('h2').hide(); 16 | $('#examples').hide(); 17 | 18 | $('#player-holder').html(''); 19 | 20 | $('#console').html('Starting...'); 21 | $('#console').show(); 22 | 23 | //magnet = $('#input').val().trim().replace('magnet:?xt=urn:btih:', '').substr(0, 40); 24 | 25 | magnet = encodeURIComponent($('#input').val().trim()); 26 | 27 | $.get('https://' + api + '/add/' + magnet, function(data) { 28 | $.get('https://' + api + '/metadata/' + magnet, function(data) { 29 | console.log(data); 30 | 31 | window.location.hash = data.torrent 32 | 33 | $('#console').html(data.name.substr(0, data.name.lastIndexOf('.')).split('.').join(' ')); 34 | 35 | $('#player').html(''); 36 | }); 37 | }); 38 | }); 39 | 40 | if (window.location.hash) { 41 | $('#input').val(window.location.hash.substr(window.location.hash.indexOf('#')+1)); 42 | 43 | $('form').submit(); 44 | } 45 | 46 | $('.hash-link').on('click', function(){ 47 | location.reload(); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /server/.ebextensions/nodecommand.config: -------------------------------------------------------------------------------- 1 | option_settings: 2 | - namespace: aws:elasticbeanstalk:container:nodejs 3 | option_name: NodeCommand 4 | value: "npm start" 5 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | downloads 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 29 | node_modules 30 | 31 | # Optional npm cache directory 32 | .npm 33 | 34 | # Optional REPL history 35 | .node_repl_history 36 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var logger = require('morgan'); 4 | var cookieParser = require('cookie-parser'); 5 | var bodyParser = require('body-parser'); 6 | 7 | var routes = require('./routes/api'); 8 | 9 | var app = express(); 10 | 11 | // view engine setup 12 | app.set('view engine', 'jade'); 13 | 14 | app.use(logger('dev')); 15 | app.use(bodyParser.json()); 16 | app.use(bodyParser.urlencoded({ extended: false })); 17 | app.use(cookieParser()); 18 | app.use(express.static(path.join(__dirname, 'public'))); 19 | 20 | app.use(function(req, res, next) { 21 | res.header("Access-Control-Allow-Origin", "*"); 22 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 23 | next(); 24 | }); 25 | 26 | app.use('/', routes); 27 | 28 | // catch 404 and forward to error handler 29 | app.use(function(req, res, next) { 30 | var err = new Error('Not Found'); 31 | err.status = 404; 32 | next(err); 33 | }); 34 | 35 | // error handlers 36 | 37 | // development error handler 38 | // will print stacktrace 39 | if (app.get('env') === 'development') { 40 | app.use(function(err, req, res, next) { 41 | res.status(err.status || 500); 42 | res.render('error', { 43 | message: err.message, 44 | error: err 45 | }); 46 | }); 47 | } 48 | 49 | // production error handler 50 | // no stacktraces leaked to user 51 | app.use(function(err, req, res, next) { 52 | res.status(err.status || 500); 53 | res.render('error', { 54 | message: err.message, 55 | error: {} 56 | }); 57 | }); 58 | 59 | 60 | module.exports = app; 61 | -------------------------------------------------------------------------------- /server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('server:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.13.2", 10 | "cookie-parser": "~1.3.5", 11 | "debug": "~2.2.0", 12 | "express": "~4.13.1", 13 | "jade": "~1.11.0", 14 | "morgan": "~1.6.1", 15 | "webtorrent": "~0.62.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /server/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /server/routes/api.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | var webtorrent = require('webtorrent'); 5 | var client = new webtorrent() 6 | 7 | getMovie = function (torrent) { 8 | var file; 9 | 10 | for (i = 0; i < torrent.files.length; i++) { 11 | if (!file || file.length < torrent.files[i].length) { 12 | file = torrent.files[i]; 13 | } 14 | } 15 | 16 | return file 17 | } 18 | 19 | function cb () {} 20 | 21 | router.get('/add/:torrent', function(req, res, next) { 22 | var torrent = req.params.torrent; 23 | 24 | console.log(torrent); 25 | 26 | try { 27 | client.add(torrent, function (torrent) { 28 | var file = getMovie(torrent); 29 | 30 | console.log('Adding torrent'); 31 | 32 | torrent.swarm.on('upload', function() { 33 | console.log((torrent.ratio * 100).toFixed(2) + '%'); 34 | 35 | if (torrent.length == torrent.downloaded && torrent.ratio >= 1.5) { 36 | console.log('Deleting torrent'); 37 | torrent.swarm.destroy(cb); 38 | torrent.discovery.stop(cb); 39 | } 40 | }); 41 | 42 | res.end(); 43 | }); 44 | } 45 | catch (err) { 46 | console.error('got error', err); 47 | } 48 | }); 49 | 50 | router.get('/metadata/:torrent', function(req, res, next) { 51 | var torrent = req.params.torrent; 52 | 53 | try { 54 | var torrent = client.get(torrent); 55 | var file = getMovie(torrent); 56 | 57 | res.json({ torrent: torrent.infoHash, name: file.name, size: file.length }); 58 | } 59 | catch (err) { 60 | console.error('got error', err); 61 | } 62 | }); 63 | 64 | router.get('/stream/:torrent', function(req, res, next) { 65 | var torrent = req.params.torrent; 66 | 67 | try { 68 | var torrent = client.get(torrent); 69 | var file = getMovie(torrent); 70 | var total = file.length; 71 | 72 | var range = req.headers.range; 73 | var parts = range.replace(/bytes=/, "").split("-"); 74 | var partialstart = parts[0]; 75 | var partialend = parts[1]; 76 | var start = parseInt(partialstart, 10); 77 | var end = partialend ? parseInt(partialend, 10) : total - 1; 78 | var chunksize = (end - start) + 1; 79 | 80 | console.log('RANGE: ' + start + ' - ' + end + ' = ' + chunksize); 81 | 82 | var stream = file.createReadStream({start: start, end: end}); 83 | res.writeHead(206, { 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4' }); 84 | stream.pipe(res); 85 | } 86 | catch (err) { 87 | console.error('got error', err); 88 | } 89 | }); 90 | 91 | module.exports = router; 92 | -------------------------------------------------------------------------------- /server/views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | -------------------------------------------------------------------------------- /server/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content 8 | --------------------------------------------------------------------------------