├── .gitignore ├── README.md ├── bin └── psn ├── cli.js ├── lib └── log.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Plex Subtitles Normalizer CLI 2 | ----------------------------- 3 | 4 | This CLI tool guarantee that SRT files have the proper format, 5 | including empty lines. 6 | 7 | As today, PLEX Player has issues when: 8 | 9 | * breaklines (aka `\n`) are added at the top of the file before 10 | the first block 11 | * empty blocks with a single breakline. 12 | 13 | This breaks subtitles, specially when casting them to Chromecast. 14 | 15 | ### Installation 16 | 17 | This tool requires `nodejs`, and should installed as globals. 18 | 19 | ``` 20 | npm install plex-subtitles-normalizer --global 21 | ``` 22 | 23 | ### How to Use 24 | 25 | ``` 26 | $ cd path/to/Movies 27 | $ psn 28 | ``` 29 | 30 | By calling `psn` in a folder, it looks for any file with `.srt` extension, analyzing the file looking for invalid formats, if the file has any, it will create a backup of the file (by adding the `.bak` extension), and it will override the original file with the correct format. 31 | 32 | ### Advanced Options 33 | 34 | #### Backups 35 | 36 | You can pass an option to avoid creating backup files, although it is not recommended: 37 | 38 | ``` 39 | $ psn --no-backup 40 | ``` 41 | 42 | If something weird happen, you can always restore the backup, or simply use the backup as the original source: 43 | 44 | ``` 45 | $ psn --from-backup 46 | ``` 47 | 48 | #### Encoding 49 | 50 | You can also select the encoding, by default `latin1` will be used. The tool will automatically detect some encoding issues, but if you find something weird, you can try to use an alternative encoding from [iconv-lite][], for example, you can use `Big5` for chinese: 51 | 52 | ``` 53 | $ psn --from-backup --encoding Big5 54 | ``` 55 | 56 | [iconv-lite]: https://github.com/ashtuchkin/iconv-lite#supported-encodings 57 | 58 | #### Custom path 59 | 60 | You can customize the pattern to search for subtitles: 61 | 62 | ``` 63 | $ psn --pattern **/*.srt 64 | ``` 65 | -------------------------------------------------------------------------------- /bin/psn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var cli = require('../cli'), 3 | log = require('../lib/log'); 4 | 5 | function done(err, msg) { 6 | if(err) { 7 | if (err instanceof Error) { 8 | log.debug(err); 9 | log.error(err.message); 10 | process.exit(err.errno || 11); 11 | } else { 12 | log.error(err); 13 | process.exit(1); 14 | } 15 | } else if(msg) { 16 | console.log(msg); 17 | } 18 | } 19 | 20 | cli(process.argv.slice(2), process.cwd(), done); 21 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var libfs = require('fs'), 4 | glob = require("glob"), 5 | nopt = require("nopt"), 6 | libpath = require('path'), 7 | log = require('./lib/log'), 8 | parser = require('subtitles-parser'), 9 | iconvlite = require('iconv-lite'); 10 | 11 | function generator(data) { 12 | var i, 13 | lines = [], 14 | id = 0; 15 | for (i in data) { 16 | id += 1; 17 | lines.push(id); 18 | lines.push(data[i].startTime + ' --> ' + data[i].endTime); 19 | lines.push(data[i].text || ''); 20 | lines.push(''); 21 | } 22 | return lines.join('\n'); 23 | } 24 | 25 | function main(argv, cwd, cb) { 26 | var output, original, data, files, i, 27 | options = nopt({ 28 | "pattern": [String, null], 29 | "encoding": [String, null], 30 | "backup": [Boolean, null], 31 | "from-backup": [Boolean, null] 32 | }); 33 | 34 | options.encoding = options.encoding || 'latin1'; 35 | options.backup = options.backup !== false ? true : false; 36 | options.pattern = options.pattern || "**/*.srt"; 37 | 38 | log.info('Encoding in [' + options.encoding + ']'); 39 | log.info('Folder [' + cwd + ']'); 40 | log.info('Looking for [' + options.pattern + ']'); 41 | 42 | // options is optional 43 | files = glob.sync(options.pattern); 44 | if (files.length === 0) { 45 | log.info('No file found: ' + cwd); 46 | return cb(); 47 | } 48 | for (i = 0; i < files.length; i += 1) { 49 | log.info('\nFile [' + files[i] + ']'); 50 | 51 | try { 52 | original = iconvlite.decode(libfs.readFileSync(libpath.join(cwd, files[i] + (options["from-backup"] ? '.bak' : ''))), options.encoding); 53 | } catch (e) { 54 | log.error(' -> Reading error: ' + (e.stack || e)); 55 | continue; 56 | } 57 | 58 | try { 59 | data = parser.fromSrt(original); 60 | } catch (e) { 61 | log.error(' -> Parser error: ' + (e.stack || e)); 62 | continue; 63 | } 64 | 65 | try { 66 | output = generator(data); 67 | } catch (e) { 68 | log.error(' -> Generator error: ' + (e.stack || e)); 69 | continue; 70 | } 71 | 72 | if (output === original) { 73 | log.info(' -> Valid SRT format, skipping.'); 74 | continue; 75 | } 76 | 77 | if (output.indexOf('�') > -1) { 78 | log.error(' -> Invalid encoding, set a valid encoding value `--enconding `.'); 79 | continue; 80 | } 81 | 82 | if (options.backup && !options['from-backup']) { 83 | if (!libfs.existsSync(libpath.join(cwd, files[i] + '.bak'))) { 84 | log.info(' -> Backing up file (*.bak)'); 85 | try { 86 | libfs.writeFileSync(libpath.join(cwd, files[i] + '.bak'), output); 87 | } catch (e) { 88 | log.error(' -> Backup error: ' + (e.stack || e)); 89 | continue; 90 | } 91 | } else { 92 | log.info(' -> Backup already exists'); 93 | } 94 | } 95 | 96 | log.info(' -> Overwritting file'); 97 | try { 98 | libfs.writeFileSync(libpath.join(cwd, files[i]), output); 99 | } catch (e) { 100 | log.error(' -> Write error: ' + (e.stack || e)); 101 | continue; 102 | } 103 | log.info(' -> Done'); 104 | } 105 | log.info('\nOK'); 106 | return cb(null); 107 | } 108 | 109 | module.exports = main; 110 | -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var log = require('npmlog'); 4 | 5 | // cust config 6 | log.addLevel('debug', 999, {fg: 'grey'}, 'debu'); 7 | 8 | // monkey patch npmlog… 9 | log.heading = ''; 10 | log.headingStyle.fg = 'blue'; 11 | log.disp.verbose = 'verb'; 12 | log.disp.error = 'err!'; 13 | log.disp.warn = 'warn'; 14 | log.style.error.bold = true; 15 | log.style.warn.bold = true; 16 | log.style.warn.fg = 'yellow'; 17 | 18 | // no bg colors 19 | log.headingStyle.bg = 20 | log.style.verbose.bg = 21 | log.style.warn.bg = 22 | log.style.error.bg = 23 | log.style.http.bg = null; 24 | 25 | // disable prefixing 26 | Object.keys(log.levels).forEach(function(level) { 27 | log[level] = log[level].bind(log, ''); 28 | }); 29 | 30 | module.exports = log; 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plex-subtitles-normalizer", 3 | "version": "0.3.0", 4 | "description": "CLI tool to fix subtitles needed by Plex Media Center", 5 | "bin": { 6 | "psn": "bin/psn" 7 | }, 8 | "main": "cli.js", 9 | "preferGlobal": true, 10 | "scripts": { 11 | "test": "./bin/psn --from-backup" 12 | }, 13 | "keywords": [ 14 | "plex", 15 | "subtitles", 16 | "parser", 17 | "cli" 18 | ], 19 | "homepage": "https://github.com/caridy/plex-subtitles-normalizer", 20 | "bugs": "https://github.com/caridy/plex-subtitles-normalizer/issues", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/caridy/plex-subtitles-normalizer" 24 | }, 25 | "author": "Caridy Patino ", 26 | "license": "MIT", 27 | "dependencies": { 28 | "subtitles-parser": "0.0.2", 29 | "npmlog": "0.0.2", 30 | "glob": "^3.2.9", 31 | "iconv-lite": "^0.2.11", 32 | "nopt": "^2.2.0" 33 | } 34 | } 35 | --------------------------------------------------------------------------------