├── .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 |
150 |
151 |
Stream video torrents in your browser.
152 |
153 |
154 |
155 |
160 |
161 |
162 |
163 | ...
164 |
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 |
--------------------------------------------------------------------------------