| { | { | ||||
| "tmpdir": "tmp" | |||||
| "tmpdir": "tmp", | |||||
| "subtitles": "en" | |||||
| } | } |
| var fs = require("fs"); | |||||
| var pathlib = require("path"); | |||||
| var player = require("./player"); | var player = require("./player"); | ||||
| var httpStream = require("./http-stream"); | var httpStream = require("./http-stream"); | ||||
| var torrentStreamer = require("./torrent-streamer"); | var torrentStreamer = require("./torrent-streamer"); | ||||
| var subtitleFinder = require("./subtitle-finder"); | |||||
| exports.httpPath = player.httpPath; | exports.httpPath = player.httpPath; | ||||
| player.init(app, conf); | player.init(app, conf); | ||||
| httpStream.init(app, conf); | httpStream.init(app, conf); | ||||
| torrentStreamer.init(app, conf); | torrentStreamer.init(app, conf); | ||||
| subtitleFinder.init(app, conf); | |||||
| } | } | ||||
| exports.playFile = function(path, cb) { | 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) { | exports.playTorrent = function(magnet, cb) { | ||||
| torrentStreamer.stream(magnet, err => { | |||||
| // Stream torrent | |||||
| torrentStreamer.stream(magnet, (err, filesize, filename) => { | |||||
| if (err) | if (err) | ||||
| return cb(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); | |||||
| }); | |||||
| }); | }); | ||||
| } | } | ||||
| return child != null; | return child != null; | ||||
| } | } | ||||
| exports.play = function(path, cb) { | |||||
| exports.play = function(path, subFile, cb) { | |||||
| exports.stop(); | exports.stop(); | ||||
| var lchild = spawn("mpv", [ | |||||
| var args = [ | |||||
| path, | path, | ||||
| "--no-cache-pause", | "--no-cache-pause", | ||||
| "--no-resume-playback", | |||||
| "--input-unix-socket", ipcServer | "--input-unix-socket", ipcServer | ||||
| ], { stdio: "inherit" }); | |||||
| ]; | |||||
| if (subFile) { | |||||
| args.push("--sub-file"); | |||||
| args.push(subFile); | |||||
| } | |||||
| var lchild = spawn("mpv", args, { stdio: "inherit" }); | |||||
| child = lchild; | child = lchild; | ||||
| lchild.running = true; | lchild.running = true; | ||||
| lchild.once("close", () => { | lchild.once("close", () => { | ||||
| console.log("child closed"); | |||||
| if (lchild.running) exports.stop(); | if (lchild.running) exports.stop(); | ||||
| }); | }); | ||||
| lchild.on("error", err => console.error(err.toString())); | lchild.on("error", err => console.error(err.toString())); |
| 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(); | |||||
| }); | |||||
| } |
| var torrentStream = require("torrent-stream"); | var torrentStream = require("torrent-stream"); | ||||
| var mediarx = /\.(mp4|mkv|mov|avi|ogv)$/; | var mediarx = /\.(mp4|mkv|mov|avi|ogv)$/; | ||||
| var tmpdir = process.cwd()+"/tmp"; | |||||
| var engine; | var engine; | ||||
| var conf; | var conf; | ||||
| return engine.destroy(() => | return engine.destroy(() => | ||||
| { engine = null; exports.stream(magnet, cb) }); | { 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", () => { | engine.on("ready", () => { | ||||
| var file = null; | var file = null; | ||||
| file.select(); | file.select(); | ||||
| httpStream.readStreamCreator = function(options) { | httpStream.readStreamCreator = function(options) { | ||||
| console.log("creating stream with", options); | |||||
| var rs = file.createReadStream(options); | var rs = file.createReadStream(options); | ||||
| rs.filesize = file.length; | rs.filesize = file.length; | ||||
| rs.filename = file.name; | rs.filename = file.name; | ||||
| rs.on("close", () => console.log("stream closing")); | |||||
| return rs; | return rs; | ||||
| } | } | ||||
| cb(); | |||||
| cb(null, file.length, file.name); | |||||
| }); | }); | ||||
| } | } | ||||
| "license": "ISC", | "license": "ISC", | ||||
| "dependencies": { | "dependencies": { | ||||
| "mime": "^1.3.4", | "mime": "^1.3.4", | ||||
| "opensubtitles-api": "^3.2.0", | |||||
| "torrent-stream": "^1.0.3", | "torrent-stream": "^1.0.3", | ||||
| "webstuff": "^1.4.0" | "webstuff": "^1.4.0" | ||||
| } | } |
| app.static("web"); | app.static("web"); | ||||
| app.post("/play/link", (req, res) => { | |||||
| app.post("/play/url", (req, res) => { | |||||
| req.parseBody((err, fields) => { | req.parseBody((err, fields) => { | ||||
| if (!fields.url) | if (!fields.url) | ||||
| return res.redirect("/"); | return res.redirect("/"); | ||||
| play.playFile(fields.url, () => { | |||||
| play.playUrl(fields.url, () => { | |||||
| res.redirect(play.httpPath); | res.redirect(play.httpPath); | ||||
| }); | }); | ||||
| }); | }); |
| </head> | </head> | ||||
| <body> | <body> | ||||
| <div id="parts"> | <div id="parts"> | ||||
| <form class="part" method="post" action="/play/link"> | |||||
| <form class="part" method="post" action="/play/url"> | |||||
| <div class="name">URL:</div> | <div class="name">URL:</div> | ||||
| <input type="url" name="url" autocomplete="off"> | <input type="url" name="url" autocomplete="off"> | ||||
| <button>Play</button> | <button>Play</button> |
| } | } | ||||
| #parts .part { | #parts .part { | ||||
| border-bottom: 1px solid #eee; | |||||
| border-bottom: 1px solid #ccc; | |||||
| padding: 20px 10px; | padding: 20px 10px; | ||||
| padding-top: 17px; | padding-top: 17px; | ||||
| } | } | ||||
| #parts .part input, | #parts .part input, | ||||
| #parts .part button { | #parts .part button { | ||||
| height: 25px; | |||||
| height: 28px; | |||||
| } | } |