├── bin.js ├── package.json ├── README.md ├── LICENSE └── index.js /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const mkvSubtitleExtractor = require('.') 5 | const z = require('zero-fill') 6 | 7 | if (process.argv.length < 3 || process.argv[2] === '--help') { 8 | const pkg = require('./package.json') 9 | console.log(`Version ${pkg.version}`) 10 | console.log(`Usage: ${pkg.name} `) 11 | process.exit(0) 12 | } 13 | 14 | const mkvPaths = process.argv.slice(2) 15 | 16 | let promise = Promise.resolve() 17 | 18 | mkvPaths.forEach(path => { 19 | promise = promise.then(() => mkvSubtitleExtractor(path) 20 | .then(tracks => { 21 | console.log(path) 22 | if (tracks.length === 0) return console.log(' No subtitle tracks found.') 23 | tracks.forEach(track => console.log(` Track ${z(2, track.number)} → ${track.path}`)) 24 | }) 25 | .catch(err => { 26 | console.error(`Error while processing ${path}:`, err.message) 27 | process.exit(1) 28 | }) 29 | ) 30 | }) 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mkv-subtitle-extractor", 3 | "version": "1.1.1", 4 | "description": "Extract subtitles from .mkv files", 5 | "main": "index.js", 6 | "bin": { 7 | "mkv-subtitle-extractor": "bin.js" 8 | }, 9 | "scripts": { 10 | "test": "standard" 11 | }, 12 | "keywords": [ 13 | "matroska", 14 | "mkv", 15 | "srt", 16 | "rip", 17 | "ripper", 18 | "extract" 19 | ], 20 | "author": "Mathias Rasmussen ", 21 | "license": "MIT", 22 | "dependencies": { 23 | "file-exists": "^4.0.0", 24 | "matroska-subtitles": "^2.0.1", 25 | "zero-fill": "^2.2.3" 26 | }, 27 | "devDependencies": {}, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/mathiasvr/mkv-subtitle-extractor.git" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/mathiasvr/mkv-subtitle-extractor/issues" 34 | }, 35 | "homepage": "https://github.com/mathiasvr/mkv-subtitle-extractor#readme" 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mkv-subtitle-extractor [![npm][npm-img]][npm-url] [![dependencies][dep-img]][dep-url] [![license][lic-img]][lic-url] 2 | 3 | [npm-img]: https://img.shields.io/npm/v/mkv-subtitle-extractor.svg 4 | [npm-url]: https://www.npmjs.com/package/mkv-subtitle-extractor 5 | [dep-img]: https://david-dm.org/mathiasvr/mkv-subtitle-extractor.svg 6 | [dep-url]: https://david-dm.org/mathiasvr/mkv-subtitle-extractor 7 | [lic-img]: http://img.shields.io/:license-MIT-blue.svg 8 | [lic-url]: http://mvr.mit-license.org 9 | 10 | Extract subtitles from .mkv files. 11 | 12 | > Currently only supports the .srt format. 13 | 14 | ## install 15 | 16 | ``` 17 | npm install -g mkv-subtitle-extractor 18 | ``` 19 | 20 | ## usage 21 | 22 | ``` 23 | mkv-subtitle-extractor file.mkv ... 24 | ``` 25 | 26 | All supported subtitle tracks are written to the same destination with non-overwriting (incremental) names. 27 | > file.srt, file.2.srt, file.eng.srt, file.ger.srt, file.ita.srt, ... 28 | 29 | ## license 30 | 31 | MIT 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mathias Rasmussen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const z = require('zero-fill') 6 | const fileExists = require('file-exists').sync 7 | const MatroskaSubtitles = require('matroska-subtitles') 8 | 9 | // https://stackoverflow.com/questions/9763441/milliseconds-to-time-in-javascript 10 | function msToTime (s) { 11 | const ms = s % 1000 12 | s = (s - ms) / 1000 13 | const secs = s % 60 14 | s = (s - secs) / 60 15 | const mins = s % 60 16 | const hrs = (s - mins) / 60 17 | 18 | return z(2, hrs) + ':' + z(2, mins) + ':' + z(2, secs) + ',' + z(3, ms) 19 | } 20 | 21 | /** 22 | * Reads mkv file and writes srt files in same location or in outputDir 23 | */ 24 | const mkvSubtitleExtractor = (mkvPath, outputDir) => new Promise((resolve, reject) => { 25 | const dir = outputDir || path.dirname(mkvPath) 26 | const name = path.basename(mkvPath, path.extname(mkvPath)) 27 | 28 | // create srt path from language suffix 29 | const srtPath = function (language) { 30 | const languageSuffix = language ? '.' + language : '' 31 | return path.join(dir, name + languageSuffix + '.srt') 32 | } 33 | 34 | const tracks = new Map() 35 | const subs = new MatroskaSubtitles() 36 | 37 | subs.once('tracks', tracks_ => { 38 | tracks_.forEach(track => { 39 | // sometimes `und` (undefined) is used as the default value, instead of leaving the tag unassigned 40 | const language = track.language !== 'und' ? track.language : null 41 | let subtitlePath = srtPath(language) 42 | 43 | // obtain unique filename (don't overwrite) 44 | for (let i = 2; fileExists(subtitlePath); i++) { 45 | subtitlePath = language ? srtPath(language + i) : srtPath(i) 46 | } 47 | 48 | tracks.set(track.number, { 49 | index: 1, 50 | file: fs.createWriteStream(subtitlePath), 51 | language 52 | }) 53 | }) 54 | }) 55 | 56 | subs.on('subtitle', (sub, trackNumber) => { 57 | const track = tracks.get(trackNumber) 58 | 59 | // convert to srt format 60 | track.file.write(`${track.index++}\r\n`) 61 | track.file.write(`${msToTime(sub.time)} --> ${msToTime(sub.time + sub.duration)}\r\n`) 62 | track.file.write(`${sub.text}\r\n\r\n`) 63 | }) 64 | 65 | subs.on('finish', () => { 66 | const tracks_ = [] 67 | 68 | tracks.forEach((track, i) => { 69 | track.file.end() 70 | tracks_.push({number: i, path: track.file.path, language: track.language}) 71 | }) 72 | resolve(tracks_) 73 | }) 74 | 75 | const file = fs.createReadStream(mkvPath) 76 | file.on('error', err => reject(err)) 77 | file.pipe(subs) 78 | }) 79 | 80 | module.exports = mkvSubtitleExtractor 81 | --------------------------------------------------------------------------------