import logging import tkinter as tk from tkinter import ttk, filedialog, messagebox from tkinter.scrolledtext import ScrolledText from typing import Optional, List, Dict, Any 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] = [] # StringVars for video info labels self.title_var = tk.StringVar(value="Titolo: N/A") self.uploader_var = tk.StringVar(value="Autore: N/A") self.duration_var = tk.StringVar(value="Durata: N/A") self.master.title("YouTube Downloader") self.master.geometry("800x650") # Increased height for new info panel 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)) # --- Video Info Panel --- info_frame = ttk.LabelFrame(self, text="Informazioni Video") info_frame.pack(fill=tk.X, pady=5, padx=2) ttk.Label(info_frame, textvariable=self.title_var).pack(anchor="w", padx=5) ttk.Label(info_frame, textvariable=self.uploader_var).pack(anchor="w", padx=5) ttk.Label(info_frame, textvariable=self.duration_var).pack(anchor="w", padx=5) # --- 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.title_var.set("Titolo: Analisi in corso...") self.uploader_var.set("Autore: Analisi in corso...") self.duration_var.set("Durata: Analisi in corso...") self.downloader.get_video_formats(url, self._on_formats_received) def _on_formats_received(self, video_info: Optional[Dict[str, Any]], formats: Optional[List[VideoFormat]]): self.master.after(0, self._update_ui_after_analysis, video_info, formats) def _update_ui_after_analysis(self, video_info: Optional[Dict[str, Any]], formats: Optional[List[VideoFormat]]): self.analyze_button.config(state="normal") if video_info and formats: logger.info("Successfully fetched video info and formats.") self.title_var.set(f"Titolo: {video_info['title']}") self.uploader_var.set(f"Autore: {video_info['uploader']}") self.duration_var.set(f"Durata: {video_info['duration_string']}") self.available_formats = formats self.format_combobox["values"] = [str(f) for f in formats] self.format_combobox.current(0) self.format_combobox.config(state="readonly") self.download_button.config(state="normal") else: logger.error("Failed to fetch video info or formats.") messagebox.showerror("Error", "Could not retrieve video information. Check the URL and logs.") self.format_combobox.set("Analisi fallita.") self.format_combobox["values"] = [] self.title_var.set("Titolo: N/A") self.uploader_var.set("Autore: N/A") self.duration_var.set("Durata: N/A") 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!")