makemkv-auto-rip
Version:
Automatically rips DVDs using the MakeMKV console and saves them to unique folders.
424 lines (341 loc) • 16.4 kB
JavaScript
//All of the constants required throughout the script
const user = {};
const moment = require('moment');
const config = require('config');
const mkvDir = config.get('Path.mkvDir.Dir');
const movieRips = config.get('Path.movieRips.Dir');
const fileLog = config.get('Path.logToFiles.Enabled').toLowerCase();
const logDir = config.get('Path.logToFiles.Dir');
const eject = config.get('Path.ejectDVDs.Enabled').toLowerCase();
const makeMKV = '\"' + mkvDir + '\\makemkvcon.exe' + '\"';
const exec = require('child_process').exec;
const fs = require('fs');
const winEject = require('win-eject');
var goodVideoArray = [];
var badVideoArray = [];
colors = require('colors/safe');
//Color theme settings for colored text
colors.setTheme({
info: 'green',
error: 'red',
time: 'yellow',
dash: 'gray',
title: 'cyan',
line1: ['white', 'bgBlack'],
line2: ['black', 'bgWhite'],
warning: ['white', 'bgRed']
});
// getCopyCompleteMSG();
Opener();
ripOrDip();
//Opening boilerplate
function Opener() {
console.info(colors.line1('MakeMKV Auto Rip Copyright (C) 2018 Zac Ingoglia'));
console.info(colors.line2('This program comes with ABSOLUTELY NO WARRANTY'));
console.info(colors.line1('This is free software, and you are welcome to redistribute it under certain conditions.'));
console.info(colors.line2('The full licence file can be found in the root folder of this software as "LICENSE.md"'));
console.info(colors.line1('Please fully read the README.md file found in the root folder before using this software.'));
console.info('');
console.info('');
console.info(colors.line1('---Welcome to MakeMKV Auto Rip v0.6.0---'));
console.info(colors.line1('---Devloped by Zac Ingoglia (Poisonite101)---'));
console.info('');
console.info('');
console.info(colors.warning('WARNING--Ensure that you have configured the Default.json file before ripping--WARNING'));
console.info('');
}
//Run program or exit
function ripOrDip() {
console.info(colors.white.underline('Would you like to Auto Rip all inserted discs now?'));
console.info(colors.white.underline('This includes both internal and USB DVD and Bluray drives.'));
console.info('');
console.info('Press' + colors.info(' 1 ') + 'to Rip.');
console.info('Press' + colors.error(' 2 ') + 'to exit.');
prompt(colors.info("Rip") + " or " + colors.error("Dip") + "? ")
.then((TA) => {
user.TA = TA;
switch (TA) {
case '1':
console.info('');
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('Beginning AutoRip... Please Wait.'));
ripDVDs(movieRips);
break;
case '2':
console.info('');
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.error('Exiting...'));
process.exit();
break;
default:
process.exit();
break;
}
})
.catch((error) => {
console.error(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.error("Critical Error, Must Abort!"));
console.error(error);
process.exit();
})
function prompt(question) {
return new Promise((resolve, reject) => {
const { stdin, stdout } = process;
stdin.resume();
stdout.write(question);
stdin.on('data', data => resolve(data.toString().trim()));
stdin.on('error', err => reject(err));
});
}
}
function validateFileDate() {
//This is the section for checking the MakeMKV version when the title command is run
// check data to make sure that you opened a valid file.
//is the length of data greater than 0
//split first line
//did we get more than one line
//does the first line have the expected number of elements in the array
//get version number of app/file
//does match our expected version
//if we don't get an true result on any of the above then raise exception to let the user know that the file or version isn't valid.
return; // "You have a bad file";
}
function validateDriveFileDate() {
//This is the section for checking the MakeMKV version when the drive number command is run
// check data to make sure that you opened a valid file.
//is the length of data greater than 0
//split first line
//did we get more than one line
//does the first line have the expected number of elements in the array
//get version number of app/file
//does match our expected version
//if we don't get an true result on any of the above then raise exception to let the user know that the file or version isn't valid.
return; // "You have a bad file";
}
function getDriveInfo(data) {
var validationMessage = validateDriveFileDate(data);
if (validationMessage) {
return validationMessage;
}
var lines = data.split("\n");
var validLines = lines
.filter(line => {
//Get array of line attributes
var lineArray = line.split(",");
//make sure that the first element starts with "DRV:"
if ((lineArray[0].startsWith("DRV:"))) {
//Ensure that the number in the second element is = 2...meaning we have media
return (lineArray[1] == 2);
}
})
.map(line => {
var driveInfo = {
driveNumber: line.split(",")[0].substring(4),
title: makeTitleValidFolderPath(line.split(",")[5])
}
return driveInfo;
});
return validLines;
}
function getFileNumber(data) {
var myTitleSectionValue = null,
maxValue = 0;
var validationMessage = validateFileDate(data);
if (validationMessage) {
return validationMessage;
}
var lines = data.split("\n");
var validLines = lines
.filter(line => {
var lineArray = line.split(",");
if ((lineArray[0].startsWith("TINFO:"))) {
//Ensure that the number in the second element is = 9. This is the "message code" that indicates title legnth
return (lineArray[1] == 9);
}
});
validLines.forEach(line => {
//gets element of good lines that contains the title legnth and removes the quotes from it (leaving just the time in HH:MM:SS format)
var videoTimeString = line
.split(",")[3]
.replace(/['"]+/g, '');
var videoTimeArray = videoTimeString.split(':');
var videoTimeSeconds = getTimeInSeconds(videoTimeArray);
//process the largest file. Then return title number for longest title.
if (videoTimeSeconds > maxValue) {
maxValue = videoTimeSeconds;
myTitleSectionValue = line
.split(",")[0] //split by comma and get the element with the title.
.replace("TINFO:", '') //Stip begnning info off element 0 to get only the title number.
}
});
return myTitleSectionValue;
};
function getCopyCompleteMSG(data, commandDataItem) {
var lines = data.split("\n");
//console.log(lines);
var validLines = lines.filter(line => line.startsWith("MSG:5036"));
//var validLines = 'MSG:5036,260,1,"Copy complete. 1 titles saved.","Copy complete. %1 titles saved.","1"'
//console.log(validLines);
var titleName = commandDataItem.title
//var titleName = createUniqueFolder(commandDataItem.title)
//if (validLines == lines.filter(line => line.startsWith("MSG:5036"))) {
if (validLines.length > 0) {
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('Done Ripping ') + colors.title(titleName));
goodVideoArray.push(titleName);
} else {
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('Unable to rip ') + colors.title(titleName) + colors.info(' Try ripping with MakeMKV GUI.'));
badVideoArray.push(titleName);
}
}
function makeTitleValidFolderPath(title) {
//escape out any chars that are not valid for file name.
return title.replace("\\", '')
.replace("/", '')
.replace(":", '')
.replace("*", '')
.replace("?", '')
.replace("<", '')
.replace(">", '')
.replace("|", '')
.replace(/['"]+/g, '');;
}
function getTimeInSeconds(timeArray) {
return (+timeArray[0]) * 60 * 60 + (+timeArray[1]) * 60 + (+timeArray[2]);
}
function createUniqueFolder(outputPath, folderName) {
var fs = require('fs');
//console.log(outputPath, folderName)
var dir = outputPath + '\\' + folderName;
var folderCounter = 1;
if (fs.existsSync(dir)) {
while (fs.existsSync(dir + '-' + folderCounter)) {
folderCounter++;
}
dir += '-' + folderCounter;
}
fs.mkdirSync(dir);
return dir;
}
function createUniqueFile(logDir, fileName) {
var fs = require('fs');
var dir = logDir + '\\' + 'Log' + '-' + fileName;
var fileCounter = 1;
if (fs.existsSync(dir + '.txt')) {
while (fs.existsSync(dir + '-' + fileCounter + '.txt')) {
fileCounter++;
}
dir += '-' + fileCounter;
}
//fs.writeFileSync(dir + '.txt', "") //saving for future update, this may be the solution but may also bork everything
return dir + '.txt';
}
function getCommandData() {
return new Promise((resolve, reject) => {
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('Getting info for all discs...'));
//console.log('mkv command', makeMKV + ' -r info disc:index')
exec(makeMKV + ' -r info disc:index', (err, stdout, stderr) => {
if (stderr) {
reject(stderr);
}
//get the data for drives with discs.
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('Getting drive info...'));
//console.log('Got Command Data Items', stdout);
var driveInfo = getDriveInfo(stdout);
//get an array of promises to get the file numbers for the longest file from each valid disc.
var drivePromises = driveInfo.map(driveInfo => {
return new Promise((resolve, reject) => {
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('Getting file number for drive title ') + colors.title(driveInfo.driveNumber) + colors.info('-') + colors.title(driveInfo.title) + colors.info('.'));
exec(makeMKV + ' -r info disc:' + driveInfo.driveNumber, (err, stdout, stderr) => {
if (stderr) {
reject(stderr);
}
var fileNumber = getFileNumber(stdout);
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('Got file info for ') + colors.title(driveInfo.driveNumber) + colors.info('-') + colors.title(driveInfo.title) + colors.info(('.')));
resolve({
driveNumber: driveInfo.driveNumber,
title: driveInfo.title,
fileNumber: fileNumber
});
});
});
});
Promise.all(drivePromises)
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
});
}
function processArray(array, fn, outputPath) {
var results = [];
return array.reduce((p, item) => {
return p.then(() => {
return fn(item, outputPath).then((data) => {
results.push(data);
return results;
});
});
}, Promise.resolve());
}
function ripDVD(commandDataItem, outputPath) {
return new Promise((resolve, reject) => {
var dir = createUniqueFolder(outputPath, commandDataItem.title);
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('Ripping Title ') + colors.title(commandDataItem.title) + colors.info((' to ' + dir + '...')));
exec(makeMKV + ' -r mkv disc:' + commandDataItem.driveNumber + ' ' + commandDataItem.fileNumber + ' ' + '\"' + dir + '\"', (err, stdout, stderr) => {
if (stderr) {
console.error(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.error('Critical Error Ripping ' + colors.title(commandDataItem.title), stderr));
reject(stderr);
} else {
//console.log(colors.blue('OUTPUT', stdout)); //Outputs full log data to console after ripping (or attempting to rip) each DVD
var fileName = createUniqueFile(logDir, commandDataItem.title);
if (fileLog == 'true') {
fs.writeFile(fileName, stdout, 'utf8',
function (err) {
if (err) console.error(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.error('Directory for logs does not exist. Please Create it.'));
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('Full Log file for ') + colors.title(commandDataItem.title) + colors.info(' has been written to file'));
// console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('Done Ripping ' + colors.title(commandDataItem.title)));
console.info(getCopyCompleteMSG(stdout, commandDataItem));
resolve(commandDataItem.title);
console.info('');
});
} else {
// console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('Done Ripping ' + colors.title(commandDataItem.title)));
console.info(getCopyCompleteMSG(stdout, commandDataItem));
resolve(commandDataItem.title);
console.info('');
}
}
});
});
}
function ripDVDs(outputPath) {
getCommandData()
.then(commandDataItems => {
//Rip the DVDs synchonously.
processArray(commandDataItems, ripDVD, outputPath)
.then((result) => {
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('The following DVD titles have been successfully ripped.'), colors.title(goodVideoArray));
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('The following DVD titles failed to rip.'), colors.title(badVideoArray));
ejectDVDs();
//process.exit();
// all done here
// array of data here in result
}, (reason) => {
console.error(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.error('Uncorrectable Error Ripping One or More DVDs.'), colors.blue(reason));
// rejection happened
});
})
.catch(err => {
console.error(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.error(err));
});
}
function ejectDVDs() {
if (eject == 'true') {
winEject.eject('', function () {
console.info(colors.time(moment().format('LTS')) + colors.dash(' - ') + colors.info('All DVDs have been ejected.'));
process.exit();
});
} else {
process.exit();
}
}