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?