Files
video-converter/gui.py
2025-11-02 12:37:09 +01:00

208 lines
8.2 KiB
Python

import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import os
import sys
import threading
import time
import converter
import downloader
import utils
import config
class VideoConverterGUI:
def __init__(self, master):
self.master = master
master.title("Video Converter")
self.label = tk.Label(master, text="Select video file or enter URL:")
self.label.pack()
self.filepath_label = tk.Label(master, text="")
self.filepath_label.pack()
self.browse_button = tk.Button(master, text="Browse", command=self.browse_file)
self.browse_button.pack()
self.url_label = tk.Label(master, text="URL:")
self.url_label.pack()
self.url_entry = tk.Entry(master, width=50)
self.url_entry.pack()
self.quality_label = tk.Label(master, text="Quality:")
self.quality_label.pack()
self.quality_var = tk.StringVar(master)
self.quality_var.set("medium") # default value
self.quality_menu = tk.OptionMenu(master, self.quality_var, "low", "medium", "high", "archive")
self.quality_menu.pack()
self.cookies_label = tk.Label(master, text="Cookies from Browser:")
self.cookies_label.pack()
self.cookies_var = tk.StringVar(master)
self.cookies_var.set("none") # default value
self.cookies_menu = tk.OptionMenu(master, self.cookies_var, "none", "brave", "chrome", "chromium", "edge", "firefox", "opera", "safari", "vivaldi", "whale")
self.cookies_menu.pack()
self.output_dir_label = tk.Label(master, text="Output Directory:")
self.output_dir_label.pack()
self.output_dir_button = tk.Button(master, text="Select Directory", command=self.select_output_dir)
self.output_dir_button.pack()
self.output_dir_path_label = tk.Label(master, text="")
self.output_dir_path_label.pack()
self.convert_button = tk.Button(master, text="Convert", command=self.convert)
self.convert_button.pack()
self.cancel_button = tk.Button(master, text="Cancel", command=self.cancel_conversion, state=tk.DISABLED)
self.cancel_button.pack()
self.status_var = tk.StringVar()
self.status_label = tk.Label(master, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
self.status_label.pack(side=tk.BOTTOM, fill=tk.X)
self.progress = ttk.Progressbar(master, orient=tk.HORIZONTAL, length=100, mode='determinate')
self.progress.pack(side=tk.BOTTOM, fill=tk.X)
def browse_file(self):
filepath = filedialog.askopenfilename()
self.filepath_label.config(text=filepath)
def select_output_dir(self):
output_dir = filedialog.askdirectory()
self.output_dir_path_label.config(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 not (input_path or url):
messagebox.showerror("Error", "Please select a file or enter a URL.")
return
if not output_dir:
messagebox.showerror("Error", "Please select an output directory.")
return
self.convert_button.config(state=tk.DISABLED)
self.cancel_button.config(state=tk.NORMAL)
self.status_var.set("Starting conversion...")
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...")
# Since we don't have progress for downloads, we'll use indeterminate mode
self.progress['mode'] = 'indeterminate'
self.progress.start()
input_path = downloader.download_video(url, output_dir, cookies_from_browser)
self.progress.stop()
self.progress['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:
# If we can't get the duration, use indeterminate mode
self.progress['mode'] = 'indeterminate'
self.progress.start()
else:
self.progress['maximum'] = duration
self.progress_thread = threading.Thread(target=self._update_progress, daemon=True)
self.progress_thread.start()
success = converter.convert_video(input_path, output_dir, quality)
if success:
self.status_var.set("Conversion successful!")
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.config(state=tk.NORMAL)
self.cancel_button.config(state=tk.DISABLED)
self.progress.stop()
self.progress['value'] = 0
def _update_progress(self):
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])
self.progress['value'] = time_ms / 1000000
except (FileNotFoundError, IndexError, ValueError):
pass
time.sleep(0.1)
self.status_var.set("Converting video...")
success = converter.convert_video(input_path, output_dir, quality)
if success:
self.status_var.set("Conversion successful!")
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.config(state=tk.NORMAL)
self.cancel_button.config(state=tk.DISABLED)
def cancel_conversion(self):
# This is a bit tricky, as we can't easily kill the thread.
# For now, we'll just show a message.
# A more robust solution would involve a more complex mechanism
# to signal the thread to stop.
messagebox.showinfo("Cancel", "Cancellation requested. The current operation will continue until it is finished.")
if __name__ == '__main__':
root = tk.Tk()
gui = VideoConverterGUI(root)
root.mainloop()