258 lines
12 KiB
Python
258 lines
12 KiB
Python
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("<Button-1>", 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() |