Files
video-converter/gui.py

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, 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()