259 lines
12 KiB
Python
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)
|