Feat: Implement main content frame and dynamic mode switch text

This commit is contained in:
Ramforth
2025-11-03 01:18:38 +01:00
parent 5ee4b3e8f3
commit 7283966b2e

114
gui.py
View File

@@ -15,17 +15,19 @@ class VideoConverterGUI:
def __init__(self, master):
self.master = master
master.title("Video Converter")
# Set a default size to prevent the narrow window
# You can change the "widthxheight" and add "+x+y" for position.
master.geometry("600x750+100+100")
# --- Set Dark Mode by Default ---
customtkinter.set_appearance_mode("Dark") # Modes: "System", "Dark", "Light"
customtkinter.set_default_color_theme("blue")
customtkinter.set_appearance_mode("Dark")
customtkinter.set_default_color_theme("blue") # Temporarily using built-in theme for build stability
# --- Footer Frame ---
self.footer_frame = customtkinter.CTkFrame(master, fg_color="transparent")
self.footer_frame.pack(side="bottom", fill="x", padx=10, pady=(5, 10))
# Create the main content frame
self.main_frame = customtkinter.CTkFrame(master, fg_color="transparent") # Make it transparent to root
self.main_frame.pack(fill="both", expand=True, padx=10, pady=10) # Add some padding to the main frame
# --- Footer Frame (packed into main_frame) ---
self.footer_frame = customtkinter.CTkFrame(self.main_frame, fg_color="transparent")
self.footer_frame.pack(side="bottom", fill="x", pady=(5, 10))
self.nickname_label = customtkinter.CTkLabel(self.footer_frame, text="by ramforth")
self.nickname_label.pack(side="left", padx=(10, 0))
@@ -33,37 +35,35 @@ class VideoConverterGUI:
self.gitea_link = customtkinter.CTkLabel(self.footer_frame, text="https://gitea.ramforth.net/ramforth", text_color="#4077d1", cursor="hand2")
self.gitea_link.pack(side="right", padx=(0, 10))
self.gitea_link.bind("<Button-1>", lambda e: self.open_link("https://gitea.ramforth.net/ramforth"))
# Add underline font
underline_font = customtkinter.CTkFont(family="sans-serif", size=12, underline=True)
self.gitea_link.configure(font=underline_font)
# --- Pack bottom elements first ---
# This makes them stick to the bottom
self.status_var = customtkinter.StringVar(value="Ready") # Give it a default value
self.status_label = customtkinter.CTkLabel(master, textvariable=self.status_var)
# Add padding (x, y)
# --- Status and Progress (packed into main_frame) ---
self.status_var = customtkinter.StringVar(value="Ready")
self.status_label = customtkinter.CTkLabel(self.main_frame, textvariable=self.status_var)
self.status_label.pack(side="bottom", fill="x", padx=10, pady=(5, 0))
self.progress = customtkinter.CTkProgressBar(master, orientation="horizontal")
self.progress = customtkinter.CTkProgressBar(self.main_frame, orientation="horizontal")
self.progress.pack(side="bottom", fill="x", padx=10, pady=(0, 10))
self.progress.set(0) # Default to 0
self.progress.set(0)
# --- Mode Switch ---
self.mode_switch = customtkinter.CTkSwitch(master, text="Dark Mode", command=self.toggle_mode)
# anchor="w" (west) aligns it to the left
# --- Mode Switch (packed into main_frame) ---
self.mode_switch = customtkinter.CTkSwitch(self.main_frame, command=self.toggle_mode)
self.mode_switch.pack(pady=10, padx=20, anchor="w")
self.mode_switch.select() # Start in the "on" (Dark) state
self.mode_switch.select()
self.mode_label = customtkinter.CTkLabel(self.main_frame, text="Dark Mode")
self.mode_label.pack(pady=0, padx=20, anchor="w")
self.toggle_mode() # Ensure initial mode is set based on switch state
# --- Main Widgets ---
# Create a frame to hold the labels for flexible packing
self.input_label_frame = customtkinter.CTkFrame(master, fg_color="transparent")
# --- Main Widgets (packed into main_frame) ---
self.input_label_frame = customtkinter.CTkFrame(self.main_frame, fg_color="transparent")
self.input_label_frame.pack(pady=(10, 5))
self.label_part1 = customtkinter.CTkLabel(self.input_label_frame, text="Select video file ")
self.label_part1.pack(side="left")
# Create a bold and underlined font for 'or'
or_font = customtkinter.CTkFont(family="sans-serif", weight="bold", underline=True)
self.label_or = customtkinter.CTkLabel(self.input_label_frame, text="or", font=or_font)
self.label_or.pack(side="left")
@@ -71,47 +71,45 @@ class VideoConverterGUI:
self.label_part2 = customtkinter.CTkLabel(self.input_label_frame, text=" enter URL:")
self.label_part2.pack(side="left")
# Use a fg_color to make the label background visible
self.filepath_label = customtkinter.CTkLabel(master, text="No file selected", fg_color=("gray75", "gray25"))
self.filepath_label = customtkinter.CTkLabel(self.main_frame, text="No file selected", fg_color=("gray75", "gray25"))
self.filepath_label.pack(pady=5, padx=20, fill="x")
self.browse_button = customtkinter.CTkButton(master, text="Browse", command=self.browse_file)
self.browse_button = customtkinter.CTkButton(self.main_frame, text="Browse", command=self.browse_file)
self.browse_button.pack(pady=5)
self.url_label = customtkinter.CTkLabel(master, text="URL:")
self.url_label.pack(pady=(15, 5)) # Add extra padding on top to create a group
self.url_label = customtkinter.CTkLabel(self.main_frame, text="URL:")
self.url_label.pack(pady=(15, 5))
# Removed width=50, will use fill="x" in pack() instead
self.url_entry = customtkinter.CTkEntry(master, placeholder_text="https://...")
self.url_entry = customtkinter.CTkEntry(self.main_frame, placeholder_text="https://...")
self.url_entry.pack(pady=5, padx=20, fill="x")
self.quality_label = customtkinter.CTkLabel(master, text="Quality:", fg_color="transparent")
self.quality_label = customtkinter.CTkLabel(self.main_frame, text="Quality:", fg_color="transparent")
self.quality_label.pack(pady=(15, 5))
self.quality_var = customtkinter.StringVar(master, value="medium")
self.quality_menu = customtkinter.CTkOptionMenu(master, variable=self.quality_var, values=["low", "medium", "high", "archive"])
self.quality_menu = customtkinter.CTkOptionMenu(self.main_frame, variable=self.quality_var, values=["low", "medium", "high", "archive"])
self.quality_menu.pack(pady=5)
self.cookies_label = customtkinter.CTkLabel(master, text="Cookies from Browser:", fg_color="transparent")
self.cookies_label = customtkinter.CTkLabel(self.main_frame, text="Cookies from Browser:", fg_color="transparent")
self.cookies_label.pack(pady=(15, 5))
self.cookies_var = customtkinter.StringVar(master, value="none")
self.cookies_menu = customtkinter.CTkOptionMenu(master, variable=self.cookies_var, values=["none", "brave", "chrome", "chromium", "edge", "firefox", "opera", "safari", "vivaldi", "whale"])
self.cookies_menu = customtkinter.CTkOptionMenu(self.main_frame, variable=self.cookies_var, values=["none", "brave", "chrome", "chromium", "edge", "firefox", "opera", "safari", "vivaldi", "whale"])
self.cookies_menu.pack(pady=5)
self.output_dir_label = customtkinter.CTkLabel(master, text="Output Directory:", fg_color="transparent")
self.output_dir_label = customtkinter.CTkLabel(self.main_frame, text="Output Directory:", fg_color="transparent")
self.output_dir_label.pack(pady=(15, 5))
self.output_dir_button = customtkinter.CTkButton(master, text="Select Directory", command=self.select_output_dir)
self.output_dir_button = customtkinter.CTkButton(self.main_frame, text="Select Directory", command=self.select_output_dir)
self.output_dir_button.pack(pady=5)
self.output_dir_path_label = customtkinter.CTkLabel(master, text="No directory selected", fg_color=("gray75", "gray25"))
self.output_dir_path_label = customtkinter.CTkLabel(self.main_frame, text="No directory selected", fg_color=("gray75", "gray25"))
self.output_dir_path_label.pack(pady=5, padx=20, fill="x")
self.convert_button = customtkinter.CTkButton(master, text="Convert", command=self.convert)
self.convert_button.pack(pady=(20, 5)) # Extra top padding
self.convert_button = customtkinter.CTkButton(self.main_frame, text="Convert", command=self.convert)
self.convert_button.pack(pady=(20, 5))
self.cancel_button = customtkinter.CTkButton(master, text="Cancel", command=self.cancel_conversion, state="disabled")
self.cancel_button = customtkinter.CTkButton(self.main_frame, text="Cancel", command=self.cancel_conversion, state="disabled")
self.cancel_button.pack(pady=5)
def open_link(self, url):
@@ -122,19 +120,23 @@ class VideoConverterGUI:
# Check if the switch is on (1) or off (0)
if self.mode_switch.get() == 1:
customtkinter.set_appearance_mode("Dark")
customtkinter.set_default_color_theme("blue") # Temporarily using built-in theme
self.main_frame.configure(fg_color="#121212") # Set main frame background
self.mode_label.configure(text="Dark Mode")
else:
customtkinter.set_appearance_mode("Light")
customtkinter.set_default_color_theme("blue") # Temporarily using built-in theme
self.main_frame.configure(fg_color="#ecd7b2") # Set main frame background
self.mode_label.configure(text="Light Mode")
def browse_file(self):
filepath = filedialog.askopenfilename()
if filepath:
# --- FIX: Use .configure() ---
self.filepath_label.configure(text=filepath)
def select_output_dir(self):
output_dir = filedialog.askdirectory()
if output_dir:
# --- FIX: Use .configure() ---
self.output_dir_path_label.configure(text=output_dir)
def convert(self):
@@ -143,12 +145,11 @@ class VideoConverterGUI:
output_dir = self.output_dir_path_label.cget("text")
quality = self.quality_var.get()
# Clear file path if URL is being used
if url:
input_path = ""
self.filepath_label.configure(text="Using URL")
elif input_path == "No file selected":
input_path = "" # Ensure it's empty
input_path = ""
if not (input_path or url):
messagebox.showerror("Error", "Please select a file or enter a URL.")
@@ -158,7 +159,6 @@ class VideoConverterGUI:
messagebox.showerror("Error", "Please select an output directory.")
return
# --- FIX: Use .configure() and string states ---
self.convert_button.configure(state="disabled")
self.cancel_button.configure(state="normal")
self.status_var.set("Starting...")
@@ -174,12 +174,10 @@ class VideoConverterGUI:
try:
if url:
self.status_var.set("Downloading video...")
# --- FIX: Use .configure() ---
self.progress.configure(mode='indeterminate')
self.progress.start()
input_path = downloader.download_video(url, output_dir, cookies_from_browser)
self.progress.stop()
# --- FIX: Use .configure() ---
self.progress.configure(mode='determinate')
if not input_path:
raise Exception("Download failed.")
@@ -188,15 +186,12 @@ class VideoConverterGUI:
duration = utils.get_video_duration(input_path)
if not duration:
# If we can't get the duration, use indeterminate mode
self.progress.configure(mode='indeterminate')
self.progress.start()
else:
# --- FIX: Set mode to determinate and clear value ---
self.progress.configure(mode='determinate')
self.progress.set(0)
# --- FIX: Pass duration to the progress updater ---
self.progress_thread = threading.Thread(target=self._update_progress, args=(duration,), daemon=True)
self.progress_thread.start()
@@ -204,7 +199,7 @@ class VideoConverterGUI:
if success:
self.status_var.set("Conversion successful!")
self.progress.set(1) # Fill the bar on success
self.progress.set(1)
messagebox.showinfo("Success", "Conversion completed successfully!")
else:
raise Exception("Conversion failed. Check logs for details.")
@@ -222,19 +217,16 @@ class VideoConverterGUI:
messagebox.showerror("Error", f"An error occurred: {e}\n\nCould not read log file: {log_e}")
finally:
# --- FIX: Use .configure() and string states ---
self.convert_button.configure(state="normal")
self.cancel_button.configure(state="disabled")
self.progress.stop()
# --- FIX: Use .set() to reset progress bar ---
if "Error" not in self.status_var.get():
self.progress.set(1) # Keep it full on success
self.progress.set(1)
else:
self.progress.set(0) # Reset on error
if self.status_var.get() == "Starting...": # Handle cancellation before start
self.progress.set(0)
if self.status_var.get() == "Starting...":
self.status_var.set("Ready")
# --- FIX: Accept duration as an argument ---
def _update_progress(self, duration):
progress_file = "ffmpeg_progress.log"
while self.conversion_thread.is_alive():
@@ -246,7 +238,6 @@ class VideoConverterGUI:
if "out_time_ms" in last_line:
time_ms = int(last_line.split("=")[1])
current_time_sec = time_ms / 1000000
# --- FIX: Calculate progress as 0.0-1.0 ---
if duration and duration > 0:
progress_value = max(0, min(1, current_time_sec / duration))
self.progress.set(progress_value)
@@ -255,15 +246,8 @@ class VideoConverterGUI:
time.sleep(0.1)
def cancel_conversion(self):
# This is still a difficult operation.
# A more robust solution involves inter-thread communication
# or sending a 'q' to the ffmpeg process.
self.status_var.set("Cancellation requested...")
messagebox.showinfo("Cancel", "Cancellation requested. The current operation will try to stop.")
# A simple way to try and stop (if converter supports it):
# You would need to build a way to signal the converter thread
# to find and kill the ffmpeg subprocess.
# For now, we just update the UI.
self.convert_button.configure(state="normal")
self.cancel_button.configure(state="disabled")