diff --git a/.gitignore b/.gitignore index 96ead4f0..6bf31e92 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ node_modules *.key torlist www/cache +google-drive-subtitles diff --git a/lib/google2vtt.js b/lib/google2vtt.js new file mode 100644 index 00000000..b9b74f7f --- /dev/null +++ b/lib/google2vtt.js @@ -0,0 +1,139 @@ +var cheerio = require('cheerio'); +var https = require('https'); +var fs = require('fs'); +var path = require('path'); +var querystring = require('querystring'); +var crypto = require('crypto'); + +var Logger = require('./logger'); + +function md5(input) { + var hash = crypto.createHash('md5'); + hash.update(input); + return hash.digest('base64').replace(/\//g, ' ') + .replace(/\+/g, '#') + .replace(/=/g, '-'); +} + +var slice = Array.prototype.slice; +var subtitleDir = path.resolve(__dirname, '..', 'google-drive-subtitles'); + +function padZeros(n) { + n = n.toString(); + if (n.length < 2) n = '0' + n; + return n; +} + +function formatTime(time) { + var hours = Math.floor(time / 3600); + time = time % 3600; + var minutes = Math.floor(time / 60); + time = time % 60; + var seconds = Math.floor(time); + var ms = time - seconds; + + var list = [minutes, seconds]; + if (hours) { + list.unshift(hours); + } + + return list.map(padZeros).join(':') + ms.toFixed(3).substring(1); +} + +function unescapeHtmlEntities(text) { + return text.replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, "'"); +} + +exports.convert = function convertSubtitles(subtitles) { + var $ = cheerio.load(subtitles, { xmlMode: true }); + var lines = slice.call($('transcript text').map(function (index, elem) { + var start = parseFloat(elem.attribs.start); + var end = start + parseFloat(elem.attribs.dur); + var text = elem.children[0].data; + + var line = formatTime(start) + ' --> ' + formatTime(end); + line += '\n' + unescapeHtmlEntities(text) + '\n'; + return line; + })); + + return 'WEBVTT\n\n' + lines.join('\n'); +}; + +exports.attach = function setupRoutes(app) { + app.get('/gdvtt/:id/:lang/:name.vtt', handleGetSubtitles); +}; + +function handleGetSubtitles(req, res) { + var id = req.params.id; + var lang = req.params.lang; + var name = req.params.name; + var vid = req.query.vid; + if (typeof vid !== 'string' || typeof id !== 'string' || typeof lang !== 'string' || + typeof name !== 'string') { + return res.sendStatus(400); + } + var file = [id, lang, md5(name)].join('_') + '.vtt'; + var fileAbsolute = path.join(subtitleDir, file); + + fs.exists(fileAbsolute, function (exists) { + if (exists) { + res.sendFile(file, { root: subtitleDir }); + } else { + fetchSubtitles(id, lang, name, vid, fileAbsolute, function (err) { + if (err) { + Logger.errlog.log(err.stack); + return res.sendStatus(500); + } + + res.sendFile(file, { root: subtitleDir }); + }); + } + }); +} + +function fetchSubtitles(id, lang, name, vid, file, cb) { + var query = { + id: id, + v: id, + vid: vid, + lang: lang, + name: name, + type: 'track', + kind: undefined + }; + + var url = 'https://drive.google.com/timedtext?' + querystring.stringify(query); + https.get(url, function (res) { + if (res.statusCode !== 200) { + return cb(new Error(res.statusMessage)); + } + + var buf = ''; + res.setEncoding('utf-8'); + res.on('data', function (data) { + buf += data; + }); + + res.on('end', function () { + try { + buf = exports.convert(buf); + } catch (e) { + return cb(e); + } + + fs.writeFile(file, buf, function (err) { + if (err) { + cb(err); + } else { + cb(); + } + }); + }); + }).on('error', function (err) { + cb(err); + }); +} diff --git a/lib/server.js b/lib/server.js index 70d47a54..6b956b74 100644 --- a/lib/server.js +++ b/lib/server.js @@ -14,6 +14,11 @@ module.exports = { fs.exists(chandumppath, function (exists) { exists || fs.mkdir(chandumppath); }); + + var gdvttpath = path.join(__dirname, "../google-drive-subtitles"); + fs.exists(gdvttpath, function (exists) { + exists || fs.mkdir(gdvttpath); + }); singleton = new Server(); return singleton; }, diff --git a/lib/web/webserver.js b/lib/web/webserver.js index 2b33d24b..d81c6238 100644 --- a/lib/web/webserver.js +++ b/lib/web/webserver.js @@ -243,6 +243,7 @@ module.exports = { require("./auth").init(app); require("./account").init(app); require("./acp").init(app); + require("../google2vtt").attach(app); app.use(static(path.join(__dirname, "..", "..", "www"), { maxAge: Config.get("http.max-age") || Config.get("http.cache-ttl") }));