makemkv-auto-rip
Version:
Automatically rips DVDs & Blu-rays using the MakeMKV console, then saves them to unique folders. It can be used from the command line or via a web interface, and is cross-platform. It is also containerized, so it can be run on any system with Docker insta
159 lines (140 loc) • 4.45 kB
JavaScript
import { Logger, colors } from "../utils/logger.js";
import { RipService } from "../services/rip.service.js";
import { APP_INFO, MENU_OPTIONS } from "../constants/index.js";
import { safeExit, isTestEnvironment } from "../utils/process.js";
/**
* Command-line interface for user interaction
*/
export class CLIInterface {
constructor(flags = {}) {
this.ripService = new RipService();
this.flags = flags;
}
/**
* Start the application
*/
async start() {
if (!this.flags.quiet) {
this.displayWelcome();
}
if (this.flags.noConfirm) {
// Skip prompting and go directly to ripping
await this.handleUserChoice(MENU_OPTIONS.RIP);
} else {
await this.promptUser();
}
}
/**
* Display the welcome message and warnings
*/
displayWelcome() {
Logger.header(`${APP_INFO.name} ${APP_INFO.copyright}`);
Logger.headerAlt("This program comes with ABSOLUTELY NO WARRANTY");
Logger.header(
"This is free software, and you are welcome to redistribute it under certain conditions."
);
Logger.headerAlt(
'The full license file can be found in the root folder of this software as "LICENSE.md"'
);
Logger.header(
"Please fully read the README.md file found in the root folder before using this software."
);
Logger.separator();
Logger.separator();
Logger.header(`---Welcome to ${APP_INFO.name} v${APP_INFO.version}---`);
Logger.header(`---Developed by ${APP_INFO.author}---`);
Logger.separator();
Logger.separator();
Logger.warning(
"WARNING--Ensure that you have configured the config.yaml file before ripping--WARNING"
);
Logger.separator();
}
/**
* Prompt the user for their choice and handle the response
*/
async promptUser() {
Logger.underline("Would you like to Auto Rip all inserted discs now?");
Logger.underline(
"This includes both (internal IDE/SATA and USB) Blu-ray and DVD drives."
);
Logger.separator();
Logger.plain("Press" + colors.info(" 1 ") + "to Rip.");
Logger.plain("Press" + colors.error(" 2 ") + "to exit.");
try {
const answer = await this.getPromptInput(
`${colors.info("Rip")} or ${colors.error("Dip")}? `
);
await this.handleUserChoice(answer);
} catch (error) {
Logger.error("Critical Error, Must Abort!", error);
// In test environments, re-throw the error for proper testing
if (isTestEnvironment()) {
throw error;
}
safeExit(1, "Critical Error, Must Abort!");
}
}
/**
* Get input from the user via stdin
* @param {string} question - The prompt question
* @returns {Promise<string>} - User input
*/
getPromptInput(question) {
return new Promise((resolve, reject) => {
const { stdin, stdout } = process;
if (!stdin || !stdout) {
reject(new Error("Standard input/output streams are not available"));
return;
}
stdin.resume();
stdout.write(question);
const onData = (data) => {
stdin.off("data", onData);
stdin.off("error", onError);
stdin.pause();
if (data === null || data === undefined) {
reject(new Error("Received null or undefined data"));
return;
}
resolve(data.toString().trim());
};
const onError = (err) => {
stdin.off("data", onData);
stdin.off("error", onError);
stdin.pause();
reject(err);
};
stdin.on("data", onData);
stdin.on("error", onError);
});
}
/**
* Handle the user's menu choice
* @param {string} choice - User's choice
*/
async handleUserChoice(choice) {
Logger.separator();
switch (choice) {
case MENU_OPTIONS.RIP:
await this.ripService.startRipping();
// After ripping, check if repeat mode is enabled
const { AppConfig } = await import("../config/index.js");
if (AppConfig.isRepeatModeEnabled) {
await this.promptUser();
} else {
Logger.info("Ripping complete. Exiting...");
safeExit(0, "Ripping complete");
}
break;
case MENU_OPTIONS.EXIT:
Logger.info("Exiting...");
safeExit(0, "User requested exit");
break;
default:
Logger.info("Invalid option selected. Exiting...");
safeExit(0, "Invalid option selected");
break;
}
}
}