| @@ -1,3 +1,4 @@ | |||
| { | |||
| "tmpdir": "tmp" | |||
| "tmpdir": "tmp", | |||
| "subtitles": "en" | |||
| } | |||
| @@ -1,6 +1,9 @@ | |||
| var fs = require("fs"); | |||
| var pathlib = require("path"); | |||
| var player = require("./player"); | |||
| var httpStream = require("./http-stream"); | |||
| var torrentStreamer = require("./torrent-streamer"); | |||
| var subtitleFinder = require("./subtitle-finder"); | |||
| exports.httpPath = player.httpPath; | |||
| @@ -11,18 +14,42 @@ exports.init = function(_app, conf) { | |||
| player.init(app, conf); | |||
| httpStream.init(app, conf); | |||
| torrentStreamer.init(app, conf); | |||
| subtitleFinder.init(app, conf); | |||
| } | |||
| exports.playFile = function(path, cb) { | |||
| player.play(path, cb); | |||
| // Find file's length | |||
| fs.stat(path, (err, stat) => { | |||
| // Find subtitles | |||
| subtitleFinder.find(stat.size, pathlib.basename(path), subFile => { | |||
| // Play! | |||
| player.play(path, subFile, cb); | |||
| }); | |||
| }); | |||
| } | |||
| exports.playUrl = function(path, cb) { | |||
| // Just play, we won't bother finding subtitles | |||
| player.play(path, null, cb); | |||
| } | |||
| exports.playTorrent = function(magnet, cb) { | |||
| torrentStreamer.stream(magnet, err => { | |||
| // Stream torrent | |||
| torrentStreamer.stream(magnet, (err, filesize, filename) => { | |||
| if (err) | |||
| return cb(err); | |||
| player.play(app.getAddress()+httpStream.httpPath, cb); | |||
| // Find subtitles | |||
| subtitleFinder.find(filesize, filename, subFile => { | |||
| // Play! | |||
| player.play(app.getAddress()+httpStream.httpPath, subFile, cb); | |||
| }); | |||
| }); | |||
| } | |||
| @@ -83,20 +83,27 @@ exports.isPlaying = function() { | |||
| return child != null; | |||
| } | |||
| exports.play = function(path, cb) { | |||
| exports.play = function(path, subFile, cb) { | |||
| exports.stop(); | |||
| var lchild = spawn("mpv", [ | |||
| var args = [ | |||
| path, | |||
| "--no-cache-pause", | |||
| "--no-resume-playback", | |||
| "--input-unix-socket", ipcServer | |||
| ], { stdio: "inherit" }); | |||
| ]; | |||
| if (subFile) { | |||
| args.push("--sub-file"); | |||
| args.push(subFile); | |||
| } | |||
| var lchild = spawn("mpv", args, { stdio: "inherit" }); | |||
| child = lchild; | |||
| lchild.running = true; | |||
| lchild.once("close", () => { | |||
| console.log("child closed"); | |||
| if (lchild.running) exports.stop(); | |||
| }); | |||
| lchild.on("error", err => console.error(err.toString())); | |||
| @@ -0,0 +1,53 @@ | |||
| var OpenSubtitles = require("opensubtitles-api"); | |||
| var fs = require("fs"); | |||
| var http = require("http"); | |||
| var urllib = require("url"); | |||
| var subs = new OpenSubtitles({ useragent: "mmpc-media-streamer" }); | |||
| var conf; | |||
| exports.init = function(app, _conf) { | |||
| conf = _conf; | |||
| } | |||
| exports.find = function(filesize, filename, cb) { | |||
| if (!conf.subtitles) | |||
| return cb(); | |||
| var subFile = conf.tmpdir+"/subs.srt"; | |||
| try { | |||
| fs.unlinkSync(subFile); | |||
| } catch (err) {} | |||
| subs.search({ | |||
| sublanguageid: conf.subtitles, | |||
| filesize: filesize, | |||
| filename: filename | |||
| }).then(subtitles => { | |||
| var sub = subtitles[conf.subtitles]; | |||
| if (!sub || !sub.url) | |||
| return cb(); | |||
| try { | |||
| var ws = fs.createWriteStream(subFile); | |||
| } catch (err) { | |||
| console.trace(err); | |||
| cb(); | |||
| } | |||
| http.request(urllib.parse(sub.url), res => { | |||
| res.pipe(ws); | |||
| res | |||
| .on("error", err => { | |||
| console.trace(err); | |||
| cb(); | |||
| }) | |||
| .on("end", () => { | |||
| ws.close(); | |||
| cb(subFile); | |||
| }); | |||
| }).end(); | |||
| }); | |||
| } | |||
| @@ -2,7 +2,6 @@ var httpStream = require("./http-stream"); | |||
| var torrentStream = require("torrent-stream"); | |||
| var mediarx = /\.(mp4|mkv|mov|avi|ogv)$/; | |||
| var tmpdir = process.cwd()+"/tmp"; | |||
| var engine; | |||
| var conf; | |||
| @@ -16,9 +15,13 @@ exports.stream = function(magnet, cb) { | |||
| return engine.destroy(() => | |||
| { engine = null; exports.stream(magnet, cb) }); | |||
| engine = torrentStream(magnet, { | |||
| tmp: conf.tmpdir | |||
| }); | |||
| try { | |||
| engine = torrentStream(magnet, { | |||
| tmp: conf.tmpdir | |||
| }); | |||
| } catch (err) { | |||
| return cb(err.toString()); | |||
| } | |||
| engine.on("ready", () => { | |||
| var file = null; | |||
| @@ -36,17 +39,14 @@ exports.stream = function(magnet, cb) { | |||
| file.select(); | |||
| httpStream.readStreamCreator = function(options) { | |||
| console.log("creating stream with", options); | |||
| var rs = file.createReadStream(options); | |||
| rs.filesize = file.length; | |||
| rs.filename = file.name; | |||
| rs.on("close", () => console.log("stream closing")); | |||
| return rs; | |||
| } | |||
| cb(); | |||
| cb(null, file.length, file.name); | |||
| }); | |||
| } | |||
| @@ -11,6 +11,7 @@ | |||
| "license": "ISC", | |||
| "dependencies": { | |||
| "mime": "^1.3.4", | |||
| "opensubtitles-api": "^3.2.0", | |||
| "torrent-stream": "^1.0.3", | |||
| "webstuff": "^1.4.0" | |||
| } | |||
| @@ -18,12 +18,12 @@ app.express.use((req, res, next) => { | |||
| app.static("web"); | |||
| app.post("/play/link", (req, res) => { | |||
| app.post("/play/url", (req, res) => { | |||
| req.parseBody((err, fields) => { | |||
| if (!fields.url) | |||
| return res.redirect("/"); | |||
| play.playFile(fields.url, () => { | |||
| play.playUrl(fields.url, () => { | |||
| res.redirect(play.httpPath); | |||
| }); | |||
| }); | |||
| @@ -8,7 +8,7 @@ | |||
| </head> | |||
| <body> | |||
| <div id="parts"> | |||
| <form class="part" method="post" action="/play/link"> | |||
| <form class="part" method="post" action="/play/url"> | |||
| <div class="name">URL:</div> | |||
| <input type="url" name="url" autocomplete="off"> | |||
| <button>Play</button> | |||
| @@ -8,7 +8,7 @@ | |||
| } | |||
| #parts .part { | |||
| border-bottom: 1px solid #eee; | |||
| border-bottom: 1px solid #ccc; | |||
| padding: 20px 10px; | |||
| padding-top: 17px; | |||
| } | |||
| @@ -28,5 +28,5 @@ | |||
| #parts .part input, | |||
| #parts .part button { | |||
| height: 25px; | |||
| height: 28px; | |||
| } | |||