SXXXXXXX_DownloaderYouTube/downloaderyoutube/gui/gui.py
2025-09-15 10:49:26 +02:00

183 lines
6.9 KiB
Python

import logging
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinter.scrolledtext import ScrolledText
from typing import Optional, List
from downloaderyoutube.core.core import Downloader, VideoFormat
from downloaderyoutube.utils.logger import add_tkinter_handler, get_logger
# Get a logger instance for this module
logger = get_logger(__name__)
# A basic logging config dictionary for the Tkinter handler
LOGGING_CONFIG = {
"colors": {
logging.DEBUG: "gray",
logging.INFO: "black",
logging.WARNING: "orange",
logging.ERROR: "red",
logging.CRITICAL: "red",
},
"queue_poll_interval_ms": 100,
}
class App(tk.Frame):
"""
The main graphical user interface for the Downloader application.
"""
def __init__(self, master: tk.Tk):
super().__init__(master)
self.master = master
self.downloader = Downloader()
self.download_path: Optional[str] = None
self.available_formats: List[VideoFormat] = []
self.master.title("YouTube Downloader")
self.master.geometry("800x600")
self.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self._create_widgets()
self._setup_logging_handler()
def _create_widgets(self):
"""Creates and lays out the widgets in the application window."""
# --- Top Frame for URL Input ---
url_frame = ttk.Frame(self)
url_frame.pack(fill=tk.X, pady=(0, 5))
ttk.Label(url_frame, text="Video URL:").pack(side=tk.LEFT, padx=(0, 5))
self.url_entry = ttk.Entry(url_frame)
self.url_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
self.analyze_button = ttk.Button(
url_frame, text="Analizza URL", command=self._analyze_url
)
self.analyze_button.pack(side=tk.LEFT, padx=(5, 0))
# --- Path Frame ---
path_frame = ttk.Frame(self)
path_frame.pack(fill=tk.X, pady=5)
self.path_button = ttk.Button(
path_frame, text="Choose Folder...", command=self.select_download_path
)
self.path_button.pack(side=tk.LEFT)
self.path_label = ttk.Label(path_frame, text="No download folder selected.")
self.path_label.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10)
# --- Format Selection Frame ---
format_frame = ttk.Frame(self)
format_frame.pack(fill=tk.X, pady=5)
ttk.Label(format_frame, text="Formato:").pack(side=tk.LEFT, padx=(0, 5))
self.format_combobox = ttk.Combobox(format_frame, state="readonly")
self.format_combobox.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
self.download_button = ttk.Button(
format_frame, text="Download", command=self.start_download, state="disabled"
)
self.download_button.pack(side=tk.LEFT, padx=(5, 0))
# --- Progress Bar ---
self.progress_bar = ttk.Progressbar(
self, orient="horizontal", length=100, mode="determinate"
)
self.progress_bar.pack(fill=tk.X, pady=(10, 5))
# --- Log Viewer ---
log_frame = ttk.LabelFrame(self, text="Log")
log_frame.pack(fill=tk.BOTH, expand=True)
self.log_widget = ScrolledText(
log_frame, state=tk.DISABLED, wrap=tk.WORD, font=("Courier New", 9)
)
self.log_widget.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
def _setup_logging_handler(self):
logger.info("Setting up GUI logging handler.")
add_tkinter_handler(
gui_log_widget=self.log_widget,
root_tk_instance_for_gui_handler=self.master,
logging_config_dict=LOGGING_CONFIG,
)
logger.info("GUI logging handler configured.")
def select_download_path(self):
path = filedialog.askdirectory()
if path:
self.download_path = path
self.path_label.config(text=f"Saving to: {self.download_path}")
logger.info(f"Download path set to: {self.download_path}")
def _analyze_url(self):
url = self.url_entry.get()
if not url:
messagebox.showerror("Error", "Please enter a YouTube URL.")
return
logger.info(f"Analyze button clicked. Fetching formats for {url}")
self.analyze_button.config(state="disabled")
self.download_button.config(state="disabled")
self.format_combobox.set("Analisi in corso...")
self.downloader.get_video_formats(url, self._on_formats_received)
def _on_formats_received(self, formats: Optional[List[VideoFormat]]):
# This is called from a different thread, so we schedule GUI updates
self.master.after(0, self._update_formats_combobox, formats)
def _update_formats_combobox(self, formats: Optional[List[VideoFormat]]):
self.analyze_button.config(state="normal")
if formats:
logger.info("Successfully fetched formats. Populating combobox.")
self.available_formats = formats
self.format_combobox["values"] = [str(f) for f in formats]
self.format_combobox.current(0)
self.download_button.config(state="normal")
else:
logger.error("Failed to fetch video formats.")
messagebox.showerror("Error", "Could not retrieve video formats. Check the URL and logs.")
self.format_combobox.set("Analisi fallita.")
self.format_combobox["values"] = []
def start_download(self):
url = self.url_entry.get()
if not self.download_path:
messagebox.showerror("Error", "Please select a download folder.")
return
selected_index = self.format_combobox.current()
if selected_index < 0:
messagebox.showerror("Error", "Please select a format to download.")
return
selected_format = self.available_formats[selected_index]
logger.info(f"Download button clicked for format: {selected_format.format_id}")
self.download_button.config(state="disabled")
self.analyze_button.config(state="disabled")
self.progress_bar["value"] = 0
self.downloader.download_video(
url=url,
download_path=self.download_path,
format_id=selected_format.format_id,
progress_callback=self._on_download_progress,
completion_callback=self._on_download_complete,
)
def _on_download_progress(self, percentage: int):
self.master.after(0, self.progress_bar.config, {"value": percentage})
def _on_download_complete(self, error_message: Optional[str]):
self.master.after(0, self._finalize_download, error_message)
def _finalize_download(self, error_message: Optional[str]):
self.progress_bar["value"] = 0
self.download_button.config(state="normal")
self.analyze_button.config(state="normal")
if error_message:
messagebox.showerror("Download Failed", error_message)
else:
messagebox.showinfo("Success", "Video downloaded successfully!")