From 6656b75c1b0878cfab889c3fb459e28a18341c1d Mon Sep 17 00:00:00 2001 From: Mathias Rasmussen Date: Fri, 26 Aug 2016 02:09:25 +0200 Subject: [PATCH] WIP: add embedded mkv subtitles --- package.json | 1 + .../controllers/subtitles-controller.js | 76 +++++++++++++++++-- src/renderer/webtorrent.js | 28 +++++++ 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index aa6fa447..46bca271 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "languagedetect": "^1.1.1", "location-history": "^1.0.0", "material-ui": "^0.15.4", + "matroska-subtitles": "^2.0.0", "musicmetadata": "^2.0.2", "network-address": "^1.1.0", "parse-torrent": "^5.7.3", diff --git a/src/renderer/controllers/subtitles-controller.js b/src/renderer/controllers/subtitles-controller.js index d67e433e..7db3e04d 100644 --- a/src/renderer/controllers/subtitles-controller.js +++ b/src/renderer/controllers/subtitles-controller.js @@ -2,8 +2,10 @@ const electron = require('electron') const fs = require('fs-extra') const path = require('path') const parallel = require('run-parallel') +const zeroFill = require('zero-fill') const remote = electron.remote +const ipcRenderer = electron.ipcRenderer const {dispatch} = require('../lib/dispatcher') @@ -74,6 +76,7 @@ module.exports = class SubtitlesController { torrentSummary.progress.files.forEach((fp, ix) => { if (fp.numPieces !== fp.numPiecesPresent) return // ignore incomplete files var file = torrentSummary.files[ix] + if (this.state.playing.fileIndex === ix) return this.checkForEmbeddedMKVSubtitles(file) if (!this.isSubtitle(file.name)) return var filePath = path.join(torrentSummary.path, file.path) this.addSubtitles([filePath], false) @@ -85,12 +88,31 @@ module.exports = class SubtitlesController { var ext = path.extname(name).toLowerCase() return ext === '.srt' || ext === '.vtt' } + + checkForEmbeddedMKVSubtitles (file) { + var playing = this.state.playing + // var playingFile = this.state.getPlayingFileSummary() + // var playingPath = path.join(torrentSummary.path, playingFile.path) + + if (path.extname(file.name).toLowerCase() === '.mkv') { + ipcRenderer.send('wt-get-mkv-subtitles', playing.infoHash, playing.fileIndex) + + ipcRenderer.once('wt-mkv-subtitles', function (e, tracks) { + tracks.forEach(function (trackEntry) { + var track = loadEmbeddedSubtitle(trackEntry) + console.log('loaded emb subs', track) + playing.subtitles.tracks.push(track) + }) + + if (tracks.length > 0) relabelSubtitles(playing.subtitles) + }) + } + } } function loadSubtitle (file, cb) { // Lazy load to keep startup fast var concat = require('simple-concat') - var LanguageDetect = require('languagedetect') var srtToVtt = require('srt-to-vtt') // Read the .SRT or .VTT file, parse it, add subtitle track @@ -101,11 +123,7 @@ function loadSubtitle (file, cb) { concat(vttStream, function (err, buf) { if (err) return dispatch('error', 'Can\'t parse subtitles file.') - // Detect what language the subtitles are in - var vttContents = buf.toString().replace(/(.*-->.*)/g, '') - var langDetected = (new LanguageDetect()).detect(vttContents, 2) - langDetected = langDetected.length ? langDetected[0][0] : 'subtitle' - langDetected = langDetected.slice(0, 1).toUpperCase() + langDetected.slice(1) + var langDetected = detectVTTLanguage(buf) var track = { buffer: 'data:text/vtt;base64,' + buf.toString('base64'), @@ -137,3 +155,49 @@ function relabelSubtitles (subtitles) { track.label = counts[lang] > 1 ? (lang + ' ' + counts[lang]) : lang }) } + +function detectVTTLanguage (buffer) { + var LanguageDetect = require('languagedetect') + + // Detect what language the subtitles are in + var vttContents = buffer.toString().replace(/(.*-->.*)/g, '') // remove numbers? + var langDetected = (new LanguageDetect()).detect(vttContents, 2) + langDetected = langDetected.length ? langDetected[0][0] : 'subtitle' + langDetected = langDetected.slice(0, 1).toUpperCase() + langDetected.slice(1) + + return langDetected +} + +function loadEmbeddedSubtitle (trackEntry) { + // convert to .vtt format + var vtt = 'WEBVTT FILE\r\n\r\n' + trackEntry.subtitles.forEach(function (sub, i) { + vtt += `${i + 1}\r\n` + vtt += `${msToTime(sub.time)} --> ${msToTime(sub.time + sub.duration)}\r\n` + vtt += `${sub.text}\r\n\r\n` + }) + + function msToTime (s) { + var ms = s % 1000 + s = (s - ms) / 1000 + var secs = s % 60 + s = (s - secs) / 60 + var mins = s % 60 + var hrs = (s - mins) / 60 + + var z = zeroFill + return z(2, hrs) + ':' + z(2, mins) + ':' + z(2, secs) + '.' + z(3, ms) + } + + var buf = new Buffer(vtt) + var langDetected = detectVTTLanguage(buf) + + var track = { + buffer: 'data:text/vtt;base64,' + buf.toString('base64'), + language: langDetected, + label: langDetected, + filePath: null + } + + return track +} diff --git a/src/renderer/webtorrent.js b/src/renderer/webtorrent.js index fb6a2c33..14d1b600 100644 --- a/src/renderer/webtorrent.js +++ b/src/renderer/webtorrent.js @@ -83,6 +83,8 @@ function init () { generateTorrentPoster(torrentKey)) ipc.on('wt-get-audio-metadata', (e, infoHash, index) => getAudioMetadata(infoHash, index)) + ipc.on('wt-get-mkv-subtitles', (e, infoHash, index) => + getMKVSubtitles(infoHash, index)) ipc.on('wt-start-server', (e, infoHash, index) => startServer(infoHash, index)) ipc.on('wt-stop-server', (e) => @@ -342,6 +344,32 @@ function getAudioMetadata (infoHash, index) { }) } +function getMKVSubtitles (infoHash, index) { + var torrent = client.get(infoHash) + var file = torrent.files[index] + + var MatroskaSubtitles = require('matroska-subtitles') + var subtitleParser = new MatroskaSubtitles() + + var textTracks = new Map() + + subtitleParser.once('tracks', function (tracks) { + tracks.forEach(function (track) { + textTracks.set(track.number, { track: track, subtitles: [] }) + }) + }) + + subtitleParser.on('subtitle', function (subtitle, trackNumber) { + textTracks.get(trackNumber).subtitles.push(subtitle) + }) + + subtitleParser.on('finish', function () { + ipc.send('wt-mkv-subtitles', Array.from(textTracks.values())) + }) + + file.createReadStream().pipe(subtitleParser) +} + function selectFiles (torrentOrInfoHash, selections) { // Get the torrent object var torrent