SXXXXXXX_DownloaderYouTube/downloaderyoutube/gui/gui.py
2025-09-15 13:07:35 +02:00

259 lines
12 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
import os
from downloaderyoutube.core.core import Downloader, VideoFormat
from downloaderyoutube.utils.logger import add_tkinter_handler, get_logger
logger = get_logger(__name__)
CONFIG_FILE_NAME = ".last_download_path"
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):
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.url_to_tree_item: Dict[str, str] = {}
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("800x750")
self.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self._create_widgets()
self._setup_logging_handler()
self._load_last_path()
def _create_widgets(self):
self.notebook = ttk.Notebook(self)
self.notebook.pack(fill=tk.BOTH, expand=True)
single_tab = ttk.Frame(self.notebook, padding=10)
batch_tab = ttk.Frame(self.notebook, padding=10)
self.notebook.add(single_tab, text="Download Singolo")
self.notebook.add(batch_tab, text="Download Multiplo")
self._create_single_download_tab(single_tab)
self._create_batch_download_tab(batch_tab)
common_frame = ttk.Frame(self)
common_frame.pack(fill=tk.X, padx=5, pady=5)
path_frame = ttk.Frame(common_frame)
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)
self.progress_bar = ttk.Progressbar(common_frame, orient="horizontal", mode="determinate")
self.progress_bar.pack(fill=tk.X, pady=5)
log_frame = ttk.LabelFrame(self, text="Log")
log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=(5, 0))
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)
def _create_single_download_tab(self, tab: ttk.Frame):
url_frame = ttk.Frame(tab)
url_frame.pack(fill=tk.X, pady=(0, 5))
ttk.Label(url_frame, text="Video URL:").pack(side=tk.LEFT)
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)
info_frame = ttk.LabelFrame(tab, text="Informazioni Video")
info_frame.pack(fill=tk.X, pady=5, expand=True)
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)
format_frame = ttk.Frame(tab)
format_frame.pack(fill=tk.X, pady=5)
ttk.Label(format_frame, text="Formato:").pack(side=tk.LEFT)
self.format_combobox = ttk.Combobox(format_frame, state="disabled")
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_single_download, state="disabled")
self.download_button.pack(side=tk.LEFT)
def _create_batch_download_tab(self, tab: ttk.Frame):
add_url_frame = ttk.Frame(tab)
add_url_frame.pack(fill=tk.X)
ttk.Label(add_url_frame, text="URL:").pack(side=tk.LEFT)
self.batch_url_entry = ttk.Entry(add_url_frame)
self.batch_url_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
self.add_url_button = ttk.Button(add_url_frame, text="Aggiungi", command=self._add_url_to_tree)
self.add_url_button.pack(side=tk.LEFT)
tree_frame = ttk.Frame(tab)
tree_frame.pack(fill=tk.BOTH, expand=True, pady=5)
self.batch_tree = ttk.Treeview(tree_frame, columns=("URL", "Stato"), show="headings")
self.batch_tree.heading("URL", text="URL")
self.batch_tree.heading("Stato", text="Stato")
self.batch_tree.column("URL", width=400)
self.batch_tree.column("Stato", width=100, anchor="center")
self.batch_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
tree_scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.batch_tree.yview)
tree_scrollbar.pack(side=tk.RIGHT, fill="y")
self.batch_tree.config(yscrollcommand=tree_scrollbar.set)
self.batch_tree.tag_configure("downloading", foreground="blue")
self.batch_tree.tag_configure("completed", foreground="green")
self.batch_tree.tag_configure("error", foreground="red")
controls_frame = ttk.Frame(tab)
controls_frame.pack(fill=tk.X, pady=5)
ttk.Label(controls_frame, text="Profilo Qualità:").pack(side=tk.LEFT)
self.quality_profile_combobox = ttk.Combobox(controls_frame, state="readonly", values=["Qualità Massima (1080p+, richiede FFmpeg)", "Alta Qualità (720p)", "Qualità Media (480p)"])
self.quality_profile_combobox.current(1)
self.quality_profile_combobox.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
self.batch_download_button = ttk.Button(controls_frame, text="Scarica Tutto", command=self.start_batch_download)
self.batch_download_button.pack(side=tk.LEFT)
def _add_url_to_tree(self):
url = self.batch_url_entry.get().strip()
if not url: return
if url not in self.url_to_tree_item:
item_id = self.batch_tree.insert("", tk.END, values=(url, "In attesa"))
self.url_to_tree_item[url] = item_id
self.batch_url_entry.delete(0, tk.END)
def _setup_logging_handler(self):
add_tkinter_handler(self.log_widget, self.master, LOGGING_CONFIG)
def _load_last_path(self):
if os.path.exists(CONFIG_FILE_NAME):
with open(CONFIG_FILE_NAME, "r") as f:
path = f.read().strip()
if os.path.isdir(path):
self.download_path = path
self.path_label.config(text=f"Saving to: {self.download_path}")
def save_last_path(self):
if self.download_path:
with open(CONFIG_FILE_NAME, "w") as f: f.write(self.download_path)
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}")
def _set_ui_state(self, enabled: bool):
state = "normal" if enabled else "disabled"
self.analyze_button.config(state=state)
self.download_button.config(state=state if self.available_formats and enabled else "disabled")
self.batch_download_button.config(state=state)
self.add_url_button.config(state=state)
self.path_button.config(state=state)
for entry in [self.url_entry, self.batch_url_entry]:
entry.config(state="normal" if enabled else "disabled")
self.quality_profile_combobox.config(state="readonly" if enabled else "disabled")
def _reset_forms(self):
logger.info("Resetting forms to initial state.")
self.progress_bar["value"] = 0
self.url_entry.delete(0, tk.END)
self.title_var.set("Titolo: N/A")
self.uploader_var.set("Autore: N/A")
self.duration_var.set("Durata: N/A")
self.available_formats = []
self.format_combobox["values"] = []
self.format_combobox.set("")
for item in self.batch_tree.get_children():
self.batch_tree.delete(item)
self.url_to_tree_item.clear()
def _analyze_url(self):
url = self.url_entry.get().strip()
if not url: return
self._set_ui_state(False)
self.format_combobox.set("Analisi in corso...")
self.downloader.get_video_formats(url, self._on_formats_received)
def _on_formats_received(self, video_info, formats):
self.master.after(0, self._update_ui_after_analysis, video_info, formats)
def _update_ui_after_analysis(self, video_info, formats):
if 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")
else:
messagebox.showerror("Error", "Could not retrieve video information.")
self.format_combobox.set("Analisi fallita.")
# ALWAYS update UI state at the end
self._set_ui_state(True)
def start_single_download(self):
if not self.download_path: messagebox.showerror("Error", "Please select a download folder."); return
idx = self.format_combobox.current()
if idx < 0: messagebox.showerror("Error", "Please select a format."); return
self._set_ui_state(False)
self.progress_bar["value"] = 0
url = self.url_entry.get().strip()
self.downloader.download_video(url, self.download_path, self.available_formats[idx].format_id, self._on_download_progress, self._on_download_complete)
def start_batch_download(self):
if not self.download_path: messagebox.showerror("Error", "Please select a download folder."); return
urls = [self.batch_tree.item(item, "values")[0] for item in self.batch_tree.get_children()]
if not urls: messagebox.showerror("Error", "Please add at least one URL to the list."); return
self._set_ui_state(False)
self.progress_bar["value"] = 0
quality = self.quality_profile_combobox.get()
self.downloader.download_batch(urls, self.download_path, quality, self._on_download_progress, self._on_batch_complete, self._on_video_started, self._on_video_completed, self._on_video_error)
def _on_video_started(self, url):
self.master.after(0, self._update_batch_item, url, "In corso...", "downloading")
def _on_video_completed(self, url):
self.master.after(0, self._update_batch_item, url, "Completato", "completed")
def _on_video_error(self, url, error):
self.master.after(0, self._update_batch_item, url, "Errore", "error")
def _update_batch_item(self, url, status, tag):
if url in self.url_to_tree_item:
item_id = self.url_to_tree_item[url]
self.batch_tree.set(item_id, "Stato", status)
self.batch_tree.item(item_id, tags=(tag,))
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, "Download completato con successo!")
def _on_batch_complete(self, error_message: Optional[str]):
self.master.after(0, self._finalize_download, error_message, "Download multiplo completato!")
def _finalize_download(self, error_message: Optional[str], success_message: str):
self._reset_forms()
self._set_ui_state(True)
if error_message:
messagebox.showerror("Download Failed", f"A problem occurred: {error_message}")
else:
messagebox.showinfo("Success", success_message)