import customtkinter import tkinter as tk from tkinter import filedialog, messagebox import os import sys import threading import time import converter import webbrowser import downloader import utils import config class VideoConverterGUI: def __init__(self, master): self.master = master master.title("Video Converter") master.geometry("600x750+100+100") # --- Set Dark Mode by Default --- customtkinter.set_appearance_mode("Dark") customtkinter.set_default_color_theme("blue") # Temporarily using built-in theme for build stability # 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) # 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)) 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("", lambda e: self.open_link("https://gitea.ramforth.net/ramforth")) underline_font = customtkinter.CTkFont(family="sans-serif", size=12, underline=True) self.gitea_link.configure(font=underline_font) # --- 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(self.main_frame, orientation="horizontal") self.progress.pack(side="bottom", fill="x", padx=10, pady=(0, 10)) self.progress.set(0) # --- Mode Switch (packed into main_frame) --- self.mode_switch = customtkinter.CTkSwitch(self.main_frame, text="", command=self.toggle_mode) self.mode_switch.pack(pady=10, padx=20, anchor="w") self.mode_switch.select() self.mode_label = customtkinter.CTkLabel(self.main_frame) self.mode_label.pack(pady=0, padx=20, anchor="w") self.toggle_mode() # Ensure initial mode is set based on switch state # --- 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") 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") self.label_part2 = customtkinter.CTkLabel(self.input_label_frame, text=" enter URL:") self.label_part2.pack(side="left") 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(self.main_frame, text="Browse", command=self.browse_file) self.browse_button.pack(pady=5) self.url_label = customtkinter.CTkLabel(self.main_frame, text="URL:") self.url_label.pack(pady=(15, 5)) 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(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(self.main_frame, variable=self.quality_var, values=["low", "medium", "high", "archive"]) self.quality_menu.pack(pady=5) 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(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(self.main_frame, text="Output Directory:", fg_color="transparent") self.output_dir_label.pack(pady=(15, 5)) 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(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(self.main_frame, text="Convert", command=self.convert) self.convert_button.pack(pady=(20, 5)) 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): """Opens the given URL in a web browser.""" webbrowser.open_new(url) def toggle_mode(self): # 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: self.filepath_label.configure(text=filepath) def select_output_dir(self): output_dir = filedialog.askdirectory() if output_dir: self.output_dir_path_label.configure(text=output_dir) def convert(self): input_path = self.filepath_label.cget("text") url = self.url_entry.get() output_dir = self.output_dir_path_label.cget("text") quality = self.quality_var.get() if url: input_path = "" self.filepath_label.configure(text="Using URL") elif input_path == "No file selected": input_path = "" if not (input_path or url): messagebox.showerror("Error", "Please select a file or enter a URL.") return if output_dir == "No directory selected": messagebox.showerror("Error", "Please select an output directory.") return self.convert_button.configure(state="disabled") self.cancel_button.configure(state="normal") self.status_var.set("Starting...") self.conversion_thread = threading.Thread( target=self._run_conversion, args=(input_path, url, output_dir, quality, self.cookies_var.get()), daemon=True ) self.conversion_thread.start() def _run_conversion(self, input_path, url, output_dir, quality, cookies_from_browser): try: if url: self.status_var.set("Downloading video...") self.progress.configure(mode='indeterminate') self.progress.start() input_path = downloader.download_video(url, output_dir, cookies_from_browser) self.progress.stop() self.progress.configure(mode='determinate') if not input_path: raise Exception("Download failed.") self.status_var.set("Converting video...") duration = utils.get_video_duration(input_path) if not duration: self.progress.configure(mode='indeterminate') self.progress.start() else: self.progress.configure(mode='determinate') self.progress.set(0) self.progress_thread = threading.Thread(target=self._update_progress, args=(duration,), daemon=True) self.progress_thread.start() success = converter.convert_video(input_path, output_dir, quality) if success: self.status_var.set("Conversion successful!") self.progress.set(1) messagebox.showinfo("Success", "Conversion completed successfully!") else: raise Exception("Conversion failed. Check logs for details.") except Exception as e: self.status_var.set(f"Error: {e}") try: with open(config.FFMPEG_LOG_FILE_PATH, "r") as f: log_lines = f.readlines() last_10_lines = "".join(log_lines[-10:]) messagebox.showerror("Error", f"An error occurred: {e}\n\nLast 10 lines of log:\n{last_10_lines}") except FileNotFoundError: messagebox.showerror("Error", f"An error occurred: {e}\n\nCould not find log file.") except Exception as log_e: messagebox.showerror("Error", f"An error occurred: {e}\n\nCould not read log file: {log_e}") finally: self.convert_button.configure(state="normal") self.cancel_button.configure(state="disabled") self.progress.stop() if "Error" not in self.status_var.get(): self.progress.set(1) else: self.progress.set(0) if self.status_var.get() == "Starting...": self.status_var.set("Ready") def _update_progress(self, duration): progress_file = "ffmpeg_progress.log" while self.conversion_thread.is_alive(): try: with open(progress_file, "r") as f: lines = f.readlines() if lines: last_line = lines[-1] if "out_time_ms" in last_line: time_ms = int(last_line.split("=")[1]) current_time_sec = time_ms / 1000000 if duration and duration > 0: progress_value = max(0, min(1, current_time_sec / duration)) self.progress.set(progress_value) except (FileNotFoundError, IndexError, ValueError): pass time.sleep(0.1) def cancel_conversion(self): self.status_var.set("Cancellation requested...") messagebox.showinfo("Cancel", "Cancellation requested. The current operation will try to stop.") self.convert_button.configure(state="normal") self.cancel_button.configure(state="disabled") if __name__ == '__main__': root = customtkinter.CTk() gui = VideoConverterGUI(root) root.mainloop()