Browse Source

Merge branch 'master' of https://github.com/mortie/mmpc2

master
mortie 7 years ago
parent
commit
cd787a5404
10 changed files with 236 additions and 6 deletions
  1. 1
    0
      .gitignore
  2. 89
    0
      README.md
  3. 2
    1
      conf.json.example
  4. 50
    0
      js/fsutil.js
  5. 26
    3
      js/play/index.js
  6. 1
    1
      js/play/player.js
  7. 29
    0
      server.js
  8. 7
    0
      web/index.html
  9. 18
    0
      web/script.js
  10. 13
    1
      web/style.css

+ 1
- 0
.gitignore View File

@@ -1,2 +1,3 @@
node_modules
conf.json
tmp

+ 89
- 0
README.md View File

@@ -0,0 +1,89 @@
# MMPC2

The second version of Mort's Media PC.

## Goal

MMPC2 is meant to be running constantly on a HTPC, letting you stream media to
a TV and control the playback remotely, all through a web interface. It allows
you to stream three kinds of movies:

* URLs - YouTube, Vimeo, Dailymotion, plain HTTP streams, anything supported by
mpv and youtube-dl.
* Magnet Links - Stream any torrent.
* Files - Upload files and have them play.

For magnet links and files, subtitles will automatically be downloaded if you
want and subtitles exist for it in OpenSubtitles.

## Installation

Install git, a recent version of node, and npm, clone the repository, run `npm
install`, copy `conf.json.example` to `conf.json`, and run `node server.js`.

git clone https://github.com/mortie/mmpc2.git
cd mmpc2
npm install
cp conf.json.example
node server.js

### Getting a recent version of node.js

Many distros ship old versions of node, which won't work with MMPC2. To fix
this, install node.js and npm (`sudo apt-get install nodejs-legacy npm` on
Debian and Ubuntu), then install `n` and install a new version of node with
that.

sudo apt-get install nodejs-legacy npm
sudo npm install -g n
sudo n stable

With rolling release distros, the version of node in the package manager will
generally be new enough - e.g `sudo pacman -S npm node` in arch is enough.

### Getting a recent version of mpv

MMPC2 requires a relatively new version of mpv, newer than what's currently in
Debian and Ubuntu. For Ubuntu, you could just add this ppa to your system:
https://launchpad.net/~mc3man/+archive/Ubuntu/mpv-tests

sudo add-apt-repository ppa:mc3man/mpv-tests
sudo apt-get update
sudo apt-get install mpv

For Debian, you will probably have to either use the testing repositories to
install mpv, get some binary from somewhere, or compile it from source. I
compiled from source when installing it on the Debian stable (jessie), and the
instructions are a bit too long to include here, but you can get the source
from here: https://github.com/mpv-player/mpv/releases/latest and get the source
there. Make sure to compile with luajit support for youtube-dl, and libpulse
for pulseaudio (if you use that).

### Getting a recent version of youtbe-dl

Debian may ship a version of youtube-dl that's so old it doesn't really work
anymore - I had that problem with my Debian box. To fix that, uninstall
youtube-dl if you installed with apt-get (`apt-get remove youtube-dl`), and
install it through pip (installing pip if necessary)

sudo apt-get install python-pip
sudo pip install youtube-dl

## Configuration

`conf.json` contains a couple of configuration options (assuming you copied
`conf.json.example` to `conf.json`). These are:

{
"tmpdir": String. The directory to store temporary files in.
Default: "tmp"

"subtitles": String (or false). The language code for the subtitles,
or false for no subtitles. Default: "en" (english).

"additional_links": Array of objects which look like this:
{ "name": "some name", "url": "some URL" }
Lets you add more parts to the index page. I use it to link to an
instance of guacamole, a web based VNC client.
( https://guacamole.incubator.apache.org/ )
}

+ 2
- 1
conf.json.example View File

@@ -1,4 +1,5 @@
{
"tmpdir": "tmp",
"subtitles": "en"
"subtitles": "en",
"additional_links": []
}

+ 50
- 0
js/fsutil.js View File

@@ -0,0 +1,50 @@
var fs = require("fs");
var pathlib = require("path");

/*
* Move a file by copying it, to let it move across devices
*/
exports.move = function(src, dst, cb) {
var ws;
try {
ws = fs.createWriteStream(dst);
} catch (err) { return cb(err) }

var rs;
try {
rs = fs.createReadStream(src);
} catch (err) { return cb(err) }

rs
.on("data", d => ws.write(d))
.on("end", () => {
ws.end();
cb();
})
.on("error", cb);
}

/*
* Remove directory, deleting its content in the process
*/
exports.rmdir = function(dir) {
console.log("rmdir", dir);
try {
fs.accessSync(dir, fs.F_OK)
} catch (err) {
console.trace(err);
return;
}

fs.readdirSync(dir).forEach(f => {
var fname = pathlib.join(dir, f);

var stat = fs.statSync(fname);
if (stat.isDirectory())
exports.rmdir(fname);
else
fs.unlinkSync(fname)
});

fs.rmdir(dir);
}

+ 26
- 3
js/play/index.js View File

@@ -1,5 +1,6 @@
var fs = require("fs");
var pathlib = require("path");
var fsutil = require("../fsutil");
var player = require("./player");
var httpStream = require("./http-stream");
var torrentStreamer = require("./torrent-streamer");
@@ -7,23 +8,37 @@ var subtitleFinder = require("./subtitle-finder");

exports.httpPath = player.httpPath;

exports.cleanupFiles = [];

var app;
var conf

exports.init = function(_app, conf) {
exports.init = function(_app, _conf) {
app = _app;
conf = _conf;
player.init(app, conf);
httpStream.init(app, conf);
torrentStreamer.init(app, conf);
subtitleFinder.init(app, conf);
}

exports.playFile = function(path, cb) {
/*
* Filename is optional; in case you want to provide a filename for subtitles
* but want a different path
*/
exports.playFile = function(path, cb, filename) {
filename = filename || pathlib.basename(path);

// Find file's length
fs.stat(path, (err, stat) => {
if (err) {
console.trace(err);
return cb();
}

// Find subtitles
subtitleFinder.find(stat.size, pathlib.basename(path), subFile => {
subtitleFinder.find(stat.size, filename, subFile => {
exports.cleanupFiles.push(subFile);

// Play!
player.play(path, subFile, cb);
@@ -46,6 +61,7 @@ exports.playTorrent = function(magnet, cb) {

// Find subtitles
subtitleFinder.find(filesize, filename, subFile => {
exports.cleanupFiles.push(subFile);

// Play!
player.play(app.getAddress()+httpStream.httpPath, subFile, cb);
@@ -58,4 +74,11 @@ exports.isPlaying = player.isPlaying;
player.onstop = function() {
torrentStreamer.stop();
httpStream.stop();

exports.cleanupFiles.forEach(f => {
fs.unlink(f, err => { if (err) console.trace(err) });
});
exports.cleanupFiles = [];

fsutil.rmdir(conf.tmpdir+"/torrent-stream");
}

+ 1
- 1
js/play/player.js View File

@@ -90,7 +90,7 @@ exports.play = function(path, subFile, cb) {
path,
"--no-cache-pause",
"--no-resume-playback",
"--input-unix-socket", ipcServer
"--input-ipc-server", ipcServer
];

if (subFile) {

+ 29
- 0
server.js View File

@@ -1,6 +1,8 @@
var fs = require("fs");
var pathlib = require("path");
var web = require("webstuff");
var play = require("./js/play");
var fsutil = require("./js/fsutil");

var conf = JSON.parse(fs.readFileSync("conf.json"));

@@ -37,3 +39,30 @@ app.post("/play/magnet", (req, res) => {
});
});
});

app.post("/play/file", (req, res) => {
req.parseBody((err, fields, files) => {
var file = files.file;

if (file == null || !file.name || file.size === 0)
return res.redirect("/");

var fname = conf.tmpdir+"/uploaded-file"+pathlib.extname(file.name);
fsutil.move(file.path, fname, err => {
if (err) {
console.trace(err);
return res.redirect("/");
}

play.cleanupFiles.push(fname);

play.playFile(fname, () => {
res.redirect(play.httpPath);
}, file.name);
});
});
});

app.get("/additional-links", (req, res) => {
res.json(conf.additional_links);
});

+ 7
- 0
web/index.html View File

@@ -19,8 +19,15 @@
<input type="url" name="magnet" autocomplete="off">
<button>Play</button>
</form>

<form class="part" method="post" enctype="multipart/form-data" action="/play/file">
<div class="name">File:</div>
<input type="file" name="file" autocomplete="off">
<button>Play</button>
</form>
</div>

<script src="/webstuff.js"></script>
<script src="/script.js"></script>
</body>
</html>

+ 18
- 0
web/script.js View File

@@ -0,0 +1,18 @@
get("/additional-links", function(err, res) {
if (err)
return console.trace(err);

JSON.parse(res).forEach(function(link) {
var form = document.createElement("form");
form.className = "part";
form.method = "get";
form.action = link.url;

var btn = document.createElement("button");
btn.className = "link";
btn.innerHTML = link.name;
form.appendChild(btn);

$$("#parts").appendChild(form);
});
});

+ 13
- 1
web/style.css View File

@@ -21,12 +21,24 @@
#parts .part input {
width: calc(100% - 80px);
}
#parts .part input[type=url],
#parts .part input[type=text] {
padding-left: 6px;
}

#parts .part button {
width: 75px;
float: right;
}

#parts .part button.link {
width: 100%;
height: 42px;
float: none;
}

#parts .part input,
#parts .part button {
height: 28px;
height: 34px;
line-height: 0px;
}

Loading…
Cancel
Save