Automate ripping DVDs

import os, subprocess, tkinter as tk
from tkinter import ttk, messagebox
from threading import Thread, Event
from time import sleep, strftime
import json, re
from functools import partial

SETTINGS_FILE = "settings.json"

# --- Load Settings ---
if not os.path.exists(SETTINGS_FILE):
    settings = {
        "output_root": r"C:\Users\crock\OneDrive\Desktop\rip",
        "drives": ["D:", "E:", "F:"],
        "handbrake_path": r"C:\Program Files\HandBrake\HandBrakeCLI.exe",
        "makemkv_path": r"C:\Program Files\MakeMKV\makemkvcon64.exe",
        "rip_one_at_a_time": True,
        "makemkv_minlength": 120,
        "auto_delete_mkv": True,
        "theme": "light",
    }
    with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
        json.dump(settings, f, indent=4)
else:
    with open(SETTINGS_FILE, "r", encoding="utf-8") as f:
        settings = json.load(f)

OUTPUT_ROOT = settings.get("output_root")
DRIVES = settings.get("drives")
HAND_BRAKE_PATH = settings.get("handbrake_path")
MAKEMKV_PATH = settings.get("makemkv_path")
stop_event = Event()
rip_one_at_a_time = settings.get("rip_one_at_a_time", True)
pause_flags = {}  # per-drive pause flags
card_map = {}     # per-drive GUI widgets

# --- Utilities ---
def save_settings():
    with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
        json.dump(settings, f, indent=4)

def log(msg):
    ts = strftime("[%I:%M:%S %p] ")
    entry = ts + msg
    def append():
        log_text.config(state="normal")
        log_text.insert("end", entry + "\n")
        log_text.see("end")
        log_text.config(state="disabled")
    root.after(0, append)
    print(entry)

def detect_disc(drive):
    if os.path.exists(os.path.join(drive, "BDMV")):
        return "Blu-ray"
    if os.path.exists(os.path.join(drive, "VIDEO_TS")):
        return "DVD"
    return None

def eject_drive(drive):
    try:
        cmd = f'mci sendstring "open {drive} type CDAudio alias cd{drive[0]}"'
        subprocess.run(cmd, shell=True, check=False)
        cmd = f'mci sendstring "set cd{drive[0]} door open"'
        subprocess.run(cmd, shell=True, check=False)
        cmd = f'mci sendstring "close cd{drive[0]}"'
        subprocess.run(cmd, shell=True, check=False)
        log(f"Ejected {drive}")
    except Exception as e:
        log(f"[ERROR] Eject failed: {e}")

def run_handbrake(drive, out_file, card):
    card['status_var'].set("HandBrake")
    try:
        cmd = [HAND_BRAKE_PATH, "-i", drive, "-o", out_file, "-e", "x265", "-q", "20"]
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, creationflags=0x08000000)
        for line in p.stdout:
            if stop_event.is_set():
                p.kill()
                card['status_var'].set("Stopped")
                log(f"HandBrake stopped for {drive}")
                return False
            while pause_flags.get(drive, False):
                sleep(0.5)
            match = re.search(r'(\d+)%', line)
            if match:
                perc = int(match.group(1))
                root.after(0, lambda p=perc: card['progress']['value'].__setattr__('__call__', card['progress'].config(value=p)))
                card['progress']['value'] = perc
            log("HandBrake: " + line.strip())
        p.wait()
        card['progress']['value'] = 100
        return p.returncode == 0
    except Exception as e:
        log(f"[ERROR] HandBrake exception: {e}")
        return False

def run_makemkv(drive, out_folder, card):
    card['status_var'].set("MakeMKV")
    try:
        cmd = [MAKEMKV_PATH, f"--minlength={settings.get('makemkv_minlength',120)}", "mkv", f"disc:{drive}", "all", out_folder]
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, creationflags=0x08000000)
        for line in p.stdout:
            if stop_event.is_set():
                p.kill()
                card['status_var'].set("Stopped")
                log(f"MakeMKV stopped for {drive}")
                return False
            while pause_flags.get(drive, False):
                sleep(0.5)
            log("MakeMKV: " + line.strip())
        p.wait()
        card['progress']['value'] = 100
        return p.returncode == 0
    except Exception as e:
        log(f"[ERROR] MakeMKV exception: {e}")
        return False

def worker(drive):
    card = card_map.get(drive)
    if not card:
        return
    dtype = detect_disc(drive)
    if not dtype:
        log(f"No disc in {drive}")
        card['status_var'].set("No Disc")
        return
    title = drive.replace(":","")
    out_folder = os.path.join(OUTPUT_ROOT, title)
    os.makedirs(out_folder, exist_ok=True)
    out_file = os.path.join(out_folder, f"{title}.mp4")
    card['status_var'].set("Ripping")
    if dtype == "Blu-ray":
        ok = run_makemkv(drive, out_folder, card)
    else:
        ok = run_handbrake(drive, out_file, card)
        if not ok:
            ok = run_makemkv(drive, out_folder, card)
    if ok:
        log(f"Completed {drive}")
        card['status_var'].set("Completed")
        card['progress']['value'] = 100
        if settings.get("auto_delete_mkv", True):
            for f in os.listdir(out_folder):
                if f.lower().endswith(".mkv"):
                    try:
                        os.remove(os.path.join(out_folder, f))
                        log(f"Deleted {f}")
                    except:
                        pass
        eject_drive(drive)
    else:
        log(f"[ERROR] Failed to rip {drive}")
        card['status_var'].set("Error")

def rip_drive(drive):
    if rip_one_at_a_time:
        Thread(target=worker, args=(drive,), daemon=True).start()
    else:
        Thread(target=worker, args=(drive,), daemon=True).start()

# --- GUI ---
root = tk.Tk()
root.title("RipMedia Pro")
root.geometry("1100x820")
root.configure(bg="#FFFFFF")

# Top bar
top_bar = tk.Frame(root, bg="#203A66", height=70)
top_bar.pack(fill="x")
top_bar.pack_propagate(False)
tk.Label(top_bar, text="RipMedia Pro", bg="#203A66", fg="white", font=("Segoe UI", 18, "bold")).pack(side="left", padx=18)
tk.Label(top_bar, text="v1.0.0", bg="#203A66", fg="white").pack(side="right", padx=16)

# Controls
controls = tk.Frame(root, bg="#FFFFFF")
controls.pack(fill="x", pady=12)
stop_btn = tk.Button(controls, text="Stop All", bg="#234880", fg="white", font=("Segoe UI", 12, "bold"),
                     width=12, command=lambda: stop_event.set())
stop_btn.pack(side="left", padx=8)

# Rip One at a Time toggle
def toggle_mode_btn():
    global rip_one_at_a_time
    rip_one_at_a_time = not rip_one_at_a_time
    settings["rip_one_at_a_time"] = rip_one_at_a_time
    save_settings()
    rip_one_btn.config(text="One at a time" if rip_one_at_a_time else "Parallel")
rip_one_btn = tk.Button(controls, text="One at a time" if rip_one_at_a_time else "Parallel", bg="#2F6FD9", fg="white",
                        font=("Segoe UI", 11, "bold"), width=16, command=toggle_mode_btn)
rip_one_btn.pack(side="right", padx=8)

# Drive cards
cards_container = tk.Frame(root, bg="#FFFFFF")
cards_container.pack(padx=24)
cols = 3
for idx, drive in enumerate(DRIVES):
    row = idx // cols
    col = idx % cols
    cframe = tk.Frame(cards_container, bg="#FFFFFF", width=320, height=180)
    cframe.grid(row=row, column=col, padx=18, pady=10)
    cframe.grid_propagate(False)
    icon = tk.Canvas(cframe, width=56, height=56, bg="#FFFFFF", highlightthickness=0)
    icon.create_oval(4,4,52,52, fill="#A9D1FF", outline="")
    icon.create_oval(20,20,36,36, fill="#FFFFFF", outline="")
    icon.pack(side="left", padx=14, pady=18)
    info = tk.Frame(cframe, bg="#FFFFFF")
    info.pack(side="left", fill="both", expand=True, padx=6, pady=10)
    tk.Label(info, text=f"{drive}", bg="#FFFFFF", font=("Segoe UI", 12, "bold")).pack(anchor="w")
    type_var = tk.StringVar(value="Idle")
    tk.Label(info, textvariable=type_var, bg="#FFFFFF", font=("Segoe UI", 10)).pack(anchor="w")
    status_var = tk.StringVar(value="Idle")
    tk.Label(info, textvariable=status_var, bg="#FFFFFF", font=("Segoe UI", 11)).pack(anchor="w", pady=6)
    prog = ttk.Progressbar(info, orient="horizontal", length=220, mode="determinate")
    prog.pack(anchor="w", pady=2)

    # Buttons: Rip, Pause/Resume, Eject
    btn_row = tk.Frame(info, bg="#FFFFFF")
    btn_row.pack(anchor="w", pady=6)
    pause_flags[drive] = False

    pause_btn = tk.Button(btn_row, text="Pause", bg="#2F6FD9", fg="white", relief="flat", width=10)
    pause_btn.pack(side="left", padx=6)
    pause_btn.config(command=partial(lambda d, b: pause_flags.update({d: not pause_flags[d]}) or b.config(text="Resume" if pause_flags[d] else "Pause"), drive, pause_btn))

    eject_btn = tk.Button(btn_row, text="Eject", bg="#2F6FD9", fg="white", relief="flat", width=10,
                          command=lambda d=drive: eject_drive(d))
    eject_btn.pack(side="left", padx=6)

    rip_btn = tk.Button(btn_row, text="Rip", bg="#28A745", fg="white", relief="flat", width=10,
                        command=partial(rip_drive, drive))
    rip_btn.pack(side="left", padx=6)

    card_map[drive] = {"frame": cframe, "type_var": type_var, "status_var": status_var, "progress": prog,
                       "eject_btn": eject_btn, "pause_btn": pause_btn, "rip_btn": rip_btn}

# Log area
log_frame = tk.Frame(root, bg="#FFFFFF")
log_frame.pack(fill="both", expand=True, padx=24, pady=12)
tk.Label(log_frame, text="Log", bg="#FFFFFF", font=("Segoe UI", 12, "bold")).pack(anchor="w")
log_text = tk.Text(log_frame, height=9, bg="#FFFFFF", fg="#111111", bd=0)
log_text.pack(fill="both", expand=True, pady=6)

log("RipMedia Pro ready.")
root.mainloop()

the code will not rip dvd/bd and will not eject a disc at all.

can you help to get work so I rip 5,000 disc?