ren4plex
Version:
Script Node.js for rename films and series episodes (plex directives)
247 lines (227 loc) • 9.61 kB
JavaScript
var fs = require('fs');
var pt = require('path');
var program = require('commander');
var package = require('../package.json');
var colors = require('colors/safe');
var archy = require('archy');
var async = require('async');
var ren4plex = {
// config
config: {
preview: false,
recursive: false,
removeTitle: false,
splitChars: /[.,;!:() _+\-\[\]]/,
separator: ' ',
filesToParse: /[^\s]+\.(mkv|m4v|avi|mp4|srt)$/i,
capitaliseFirstLetter: true,
parseYear: true,
parseEpisode: true,
preReplace: [
{ search: /H.265/gi, replace: '' },
{ search: /H.264/gi, replace: '' },
{ search: /Blu-Ray/gi, replace: '' },
{ search: /S.H.I.E.L.D/gi, replace: 'Shield' },
{ search: /Ac3 2.0/gi, replace: '' },
{ search: /Ac3 5.1/gi, replace: '' },
{ search: /iDN_CreW/gi, replace: '' },
{ search: /www.DivxTotaL.com/gi, replace: '' },
{ search: /Yamato Video/gi, replace: '' }
],
ignoreWords: [
'', '480p', '720p', '1080p',
'ITA', 'ENG', 'Subs', 'Sub', 'iTALiAN', 'jap', 'ENGLiSH', 'Fansub', 'Softsub', '0sec', '10bit', 'Encoding','HDi',
'hdtv', 'BluRay', 'DLMux', 'BDMux', 'BRMux', 'DVDRip', 'BrRip', 'BdRip', 'x264', 'x265', 'h264', 'h265', 'xvid', 'MP4', 'WEBRIP', 'PROPER', 'WEB', 'DL', 'DMux', 'Mux', 'Rip', 'Dvd', 'Hdtvmux',
'AC3', 'Mp3', 'aac', '2CH', 'DTS', 'Dts', '2HD', 'HD', 'Webmux', 'IMAX',
// releaser
'TrTd_Team', 'HEVC', 'kh', 'iGM', 'GiuseppeTnT', 'Marco', 'lol', 'SToNeD', 'AlgernonWood', 'NovaRip', 'Pir8', 'KILLERS', 'ZMachine', 'byR02', 'DarkSideMux', 'TeRRa', 'ORGANiC', 'ettv',
'FoV', 'IGM', 'SATOSHi', 'TLA', 'RiVER', 'oRo', 'by', 'IperB', 'rarbg', 'xXTenGXx', 'ASAP', 'TRL', 'NAHOM', 'BLUWORLD', 'Kagome', 'VTV', 'Shiv@', 'QCF', 'Blackbit', 'Maxks', 'Ruskiyspetz', 'iCV', 'T4P3', 'Batv', 'MIRCrew',
'Hditaly', '5ys73m', 'Y99dr4s1l', 'UBi', 'Sciencefun', 'Soulwaxx', 'PirateMKV'
]
},
// Capitalise the first letter
capitaliseFirstLetter: function (string) {
return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
},
// Extract extension from filename
getExtension: function (filename) {
var i = filename.lastIndexOf('.');
return (i < 0) ? '' : filename.substr(i);
},
// Extract episode string from word (if match)
getEpisode: function (word, filename) {
var e, s;
if (filename.indexOf('lol') > -1) {
var int = parseInt(word);
if (int > 99 && int < 9999) {
s = Math.floor(int / 100);
e = int - (s * 100);
return 's' + ('0' + s).slice(-2) + 'e' + ('0' + e).slice(-2);
}
}
// x format (es. 2x11, 1X07)
var match = /\d{1,2}x\d{2}/i.exec(word);
if (match) {
var xPos = word.toLowerCase().indexOf('x');
s = parseInt(word.substr(0, xPos));
e = parseInt(word.substr(xPos + 1));
return 's' + ('0' + s).slice(-2) + 'e' + ('0' + e).slice(-2);
}
// s format (es. s02e11, S01E07)
match = /s\d{2}e\d{2}/i.exec(word);
if (match) {
s = parseInt(word.substr(1, 2));
e = parseInt(word.substr(4));
return 's' + ('0' + s).slice(-2) + 'e' + ('0' + e).slice(-2);
}
return '';
},
// Check if the word must be ignored
isIgnored: function (word) {
word = word.toLowerCase();
for (var i = 0; i < this.config.ignoreWords.length; i++) {
if (this.config.ignoreWords[i].toLowerCase() === word) {
return true;
}
}
return false;
},
preReplace: function (text) {
var res = text;
for (var i = 0; i < this.config.preReplace.length; i++) {
res = res.replace(this.config.preReplace[i].search, this.config.preReplace[i].replace)
}
return res;
},
// Get new filename
getFilename: function (filename) {
// find extension
var i = filename.lastIndexOf('.');
var extension = (i > -1) ? filename.substr(i) : '';
if (i > -1) filename = filename.substr(0, i);
// pre-replace
filename = this.preReplace(filename);
// split on words
var words = filename.split(this.config.splitChars);
var word = '';
var result = '';
var resultWithoutTitle = '';
var year = '';
var episode = '';
// cycle on words (from last)
for (i = words.length - 1; i >= 0; i--) {
// word not on ignore list
if (this.isIgnored(words[i])) continue;
// check if is year
if (this.config.parseYear && year === '') {
var int = parseInt(words[i]);
if (int > 1900 && int <= new Date().getFullYear()) {
year = this.config.separator + '(' + int + ')';
continue;
}
}
// capitalise
word = this.config.capitaliseFirstLetter ? this.capitaliseFirstLetter(words[i]) : words[i];
// check if episode
if (this.config.parseEpisode && episode === '') {
episode = this.getEpisode(word, filename);
if (episode !== '') word = episode;
}
// result without title (words after episode)
if (this.config.removeTitle && episode !== '') {
resultWithoutTitle = word + (resultWithoutTitle.length > 0 ? this.config.separator : '') + resultWithoutTitle;
}
// add word to result
result = word + (result.length > 0 ? this.config.separator : '') + result;
}
var name = (resultWithoutTitle === '' ? result + year : resultWithoutTitle);
return (name.length > 0 ? name : filename) + extension;
},
printFileName: function (oldFile, newFile) {
if (oldFile === newFile)
return colors.green(oldFile);
else
return colors.red(oldFile) + colors.grey(' -> ') + colors.green(newFile);
},
// Parse entire directory
parseDir: function (path, cb) {
fs.readdir(path, function (err, files) {
if (err) {
return cb();
}
var result = { label: colors.white(pt.basename(path)), nodes: [] };
// loop on files
async.each(files, function (file, cb) {
var fullpath = pt.join(path, file);
// check if recursive and is directory
if (ren4plex.config.recursive && fs.lstatSync(fullpath).isDirectory()) {
ren4plex.parseDir(fullpath, function (data) {
if (data) result.nodes.push(data);
cb();
});
return;
}
// check if file name match
if (!ren4plex.config.filesToParse.test(file)) {
return cb();
}
// find new name for file
var newName = ren4plex.getFilename(file);
// check new name black
if (!newName) {
result.nodes.push(ren4plex.printFileName(file, '*** NEW NAME BLACK ***'));
return cb();
}
// check if file already exist with new name
if (newName.toUpperCase() !== file.toUpperCase() && fs.existsSync(pt.join(path, newName))) {
result.nodes.push(ren4plex.printFileName(file, newName + ' *** ERROR, ALREADY EXIST ***'));
return cb();
}
if (ren4plex.config.preview) {
result.nodes.push(ren4plex.printFileName(file, newName));
return cb();
}
// rename file
fs.rename(fullpath, pt.join(path, newName), function (err) {
if (!err)
result.nodes.push(ren4plex.printFileName(file, newName));
return cb();
});
},
function () {
// callback with results
return cb(result);
}
);
});
},
// Start rename on path
start: function (path, cb) {
var result = {
label: colors.white('ren4plex ')
+ (ren4plex.config.preview ? colors.green('[PREVIEW MODE]') : '')
+ (ren4plex.config.recursive ? colors.green('[RECURSIVE]') : '')
+ (ren4plex.config.removeTitle ? colors.green('[REMOVE TITLE]') : '')
};
this.parseDir(path, function (data) {
result.nodes = [data];
cb(result);
});
}
};
module.exports = ren4plex;
program
.version(package.version, '-v, --version')
.option('-p, --preview', 'Preview mode (don\'t rename)')
.option('-r, --recursive', 'Recurse on all sub-directories')
.option('-t, --remove-title', 'Remove episode title')
.usage('[options] [dir]')
.parse(process.argv);
ren4plex.config.preview = program.preview;
ren4plex.config.recursive = program.recursive;
ren4plex.config.removeTitle = program.removeTitle;
ren4plex.start(program.args[0] ? program.args[0] : '.', function (result) {
s = archy(result);
console.log(s);
});