SXXXXXXX_DownloaderYouTube/downloaderyoutube/gui/gui.py
2025-09-15 11:06:17 +02:00

208 lines
8.3 KiB
Python

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