diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c274ebe --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/rip/ diff --git a/bell.oga b/bell.oga new file mode 100644 index 0000000..144d2b3 Binary files /dev/null and b/bell.oga differ diff --git a/rip_dvd.py b/rip_dvd.py new file mode 100644 index 0000000..ee52307 --- /dev/null +++ b/rip_dvd.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 + +import argparse +import subprocess +import os +from datetime import datetime +from playsound import playsound +import time +import re + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Rip content of dvds" # , formatter_class=argparse.RawTextHelpFormatter + ) + parser.add_argument( + "type", + choices=("movie", "series"), + help="If the dvd contains one movie, or multiple episodes of a series", + ) + parser.add_argument( + "title", + help='The title of the movie. Series must end with "Sx Ex-x". E.g.: "Lost S01 E1-04"', + ) + parser.add_argument( + "--dev", + help=f"Dvd device to rip from. Defaults to {DEFAULT_DVD_DEVICE}", + default=DEFAULT_DVD_DEVICE, + ) + + return parser.parse_args() + + +def validate_series_title(title): + if not is_series_title_valid(title): + print("Invalid series title!") + exit(1) + + +def is_series_title_valid(title): + try: + episode_matches = re.search(SERIES_TITLE_REGEX, title) + if episode_matches is None: + return False + episode_from = int(episode_matches.group(1)) + episode_to = int(episode_matches.group(2)) + if episode_from < episode_to: + return True + except: + return False + + return False + + +def chdir_to_script_dir(): + os.chdir(os.path.dirname(__file__)) + + +def mkdirs(): + os.makedirs(LOGS_DIR, exist_ok=True) + os.makedirs(RIPPED_DIR, exist_ok=True) + + +def create_log_line(args): + date = datetime.now().strftime("%d/%m/%Y %H:%M:%S") + return f'{date} - {args.type} - "{args.title}"' + + +def write_line_to_logfile(line, filename): + with open(filename, "a") as file: + file.write(line + "\n") + + +def do_rip(args) -> bool: + """Returns: success of command""" + command = create_rip_command(args) + proc = subprocess.run(command, shell=True, capture_output=False) + + return proc.returncode == 0 + + +def create_rip_command(args): + dest = RIPPED_DIR + dev = args.dev + title = args.title + + if args.type == "movie": + return f"dvdbackup -i '{dev}' -o '{dest}' -F -n '{title}'" + else: + return f"dvdbackup -i '{dev}' -o '{dest}' -M -n '{title}'" + + +def notify_ripping_success(args, program_execution_time_str): + print("Success!") + print(f"Ripping took {program_execution_time_str}") + send_notification( + f'{args.type.capitalize()} "{args.title}" ripped successfully in {program_execution_time_str}!' + ) + playsound(NOTIFICATION_SOUND) + + +def notify_ripping_error(args, program_execution_time_str): + print(f"Ripping failure after {program_execution_time_str}") + send_notification( + f'Error ripping {args.type.capitalize()} "{args.title}" after {program_execution_time_str}!' + ) + playsound(NOTIFICATION_SOUND) + + +def send_notification(text): + subprocess.run( + ["notify-send", text], + shell=False, + ) + + +def transfer_ripped_to_transcoder(args) -> bool: + """Returns: success of command""" + src = os.path.join(RIPPED_DIR, args.title) + destination = None + if args.type == "movie": + destination = f"{TRANSCODER_INPUT_FOLDER}/movies/" + else: + destination = f"{TRANSCODER_INPUT_FOLDER}/series/" + + proc = subprocess.run(["rsync", "-azv", src, destination]) + success = proc.returncode == 0 + if success: + print("Transfer successful") + else: + print("Transfer failed!") + return success + + +def delete_ripped(args): + try: + os.rmdir(os.path.join(RIPPED_DIR, args.title)) + except FileNotFoundError: + pass + + +TRANSCODER_FOLDER = "transcode" # pi@192.168.xxx:/home/pi/transcode +TRANSCODER_INPUT_FOLDER = f"{TRANSCODER_FOLDER}/raw" +DEFAULT_DVD_DEVICE = "/dev/cdrom" +SERIES_TITLE_REGEX = r"S\d+[ _]?E(\d+)-(\d+)$" +RIPPED_DIR = "rip/ripped" +LOGS_DIR = "rip/logs" +RIP_LOGFILE = f"{LOGS_DIR}/rip.log" +RIP_ERR_LOGFILE = f"{LOGS_DIR}/rip-err.log" +RIP_SUCCESS_LOGFILE = f"{LOGS_DIR}/rip-success.log" +NOTIFICATION_SOUND = "bell.oga" + +if __name__ == "__main__": + program_start_time = time.time() + args = parse_args() + + if args.type == "series": + validate_series_title(args.title) + chdir_to_script_dir() + mkdirs() + + log_line = create_log_line(args) + write_line_to_logfile(log_line, RIP_LOGFILE) + + success = do_rip(args) + program_execution_time_minutes = (program_start_time - time.time()) / 60.0 + program_execution_time_minutes = max(0.0, program_execution_time_minutes) + program_execution_time_str = f"{program_execution_time_minutes:.1f} minutes" + if success: + write_line_to_logfile(log_line, RIP_SUCCESS_LOGFILE) + notify_ripping_success(args, program_execution_time_str) + transfer_success = transfer_ripped_to_transcoder(args) + if transfer_success: + print("Deleting output") + delete_ripped(args) + else: + write_line_to_logfile(log_line, RIP_ERR_LOGFILE) + notify_ripping_error(args, program_execution_time_str) + print("Deleting partial output") + delete_ripped(args)