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!")