add multiple download

This commit is contained in:
VALLONGOL 2025-09-15 13:07:35 +02:00
parent e42fd65115
commit ee123b93d4
3 changed files with 228 additions and 226 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 0 B

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -4,131 +4,109 @@ from threading import Thread
from dataclasses import dataclass from dataclasses import dataclass
import yt_dlp import yt_dlp
from typing import Callable, Optional, List, Dict, Any from typing import Callable, Optional, List, Dict, Any
# Import the get_logger function from your custom logger module
from downloaderyoutube.utils.logger import get_logger from downloaderyoutube.utils.logger import get_logger
# Get a logger instance for this module
logger = get_logger(__name__) logger = get_logger(__name__)
@dataclass @dataclass
class VideoFormat: class VideoFormat:
"""A simple class to hold structured information about a video format."""
format_id: str format_id: str
resolution: str resolution: str
ext: str ext: str
filesize: Optional[int] filesize: Optional[int]
note: str # e.g., "Video Only", "Audio Only", "Progressive" note: str
def __str__(self) -> str: def __str__(self) -> str:
"""User-friendly string representation for the combobox."""
size_mb = f"{self.filesize / (1024 * 1024):.2f} MB" if self.filesize else "N/A" size_mb = f"{self.filesize / (1024 * 1024):.2f} MB" if self.filesize else "N/A"
return f"{self.resolution} ({self.ext}) - {size_mb} - {self.note}" return f"{self.resolution} ({self.ext}) - {size_mb} - {self.note}"
class Downloader: class Downloader:
"""
Handles video operations using yt-dlp in separate threads to avoid
blocking the GUI.
"""
def __init__(self): def __init__(self):
# Callbacks for single/overall progress
self.progress_callback: Optional[Callable[[int], None]] = None self.progress_callback: Optional[Callable[[int], None]] = None
self.completion_callback: Optional[Callable[[Optional[str]], None]] = None self.completion_callback: Optional[Callable[[Optional[str]], None]] = None
# Callbacks for single video analysis
self.formats_callback: Optional[Callable[[Optional[Dict[str, Any]], Optional[List[VideoFormat]]], None]] = None self.formats_callback: Optional[Callable[[Optional[Dict[str, Any]], Optional[List[VideoFormat]]], None]] = None
# Callbacks for per-video status in a batch
self.video_started_callback: Optional[Callable[[str], None]] = None
self.video_completed_callback: Optional[Callable[[str], None]] = None
self.video_error_callback: Optional[Callable[[str, str], None]] = None
def _progress_hook(self, d: Dict[str, Any]): def _progress_hook(self, d: Dict[str, Any]):
if d["status"] == "downloading": if d["status"] == "downloading" and self.progress_callback:
if d.get("total_bytes") and d.get("downloaded_bytes"): if d.get("total_bytes") and d.get("downloaded_bytes"):
percentage = (d["downloaded_bytes"] / d["total_bytes"]) * 100 self.progress_callback(int((d["downloaded_bytes"] / d["total_bytes"]) * 100))
if self.progress_callback:
self.progress_callback(int(percentage))
elif d["status"] == "finished":
logger.info("yt-dlp finished downloading.")
def _get_formats_task(self, url: str): def _get_formats_task(self, url: str):
"""Task to fetch video formats and info in a thread."""
try: try:
logger.info(f"Fetching formats for URL: {url}") with yt_dlp.YoutubeDL({"noplaylist": True, "quiet": True}) as ydl:
ydl_opts = {"noplaylist": True}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=False) info = ydl.extract_info(url, download=False)
video_info = {"title": info.get("title", "N/A"), "uploader": info.get("uploader", "N/A"), "duration_string": info.get("duration_string", "N/A")}
video_info = { formats = [VideoFormat(f["format_id"], f.get("format_note", f.get("resolution", "N/A")), f["ext"], f.get("filesize"), "Progressive" if f.get("acodec") != "none" else "Video Only") for f in info.get("formats", []) if f.get("vcodec", "none") != "none" and f.get("ext") == "mp4"]
"title": info.get("title", "N/A"),
"uploader": info.get("uploader", "N/A"),
"duration_string": info.get("duration_string", "N/A"),
}
formats = []
for f in info.get("formats", []):
if f.get("vcodec", "none") != "none" and f.get("ext") == "mp4":
note = "Progressive" if f.get("acodec", "none") != "none" else "Video Only"
formats.append(VideoFormat(
format_id=f["format_id"],
resolution=f.get("format_note", f.get("resolution", "N/A")),
ext=f["ext"],
filesize=f.get("filesize"),
note=note
))
formats.sort(key=lambda x: (x.note != "Progressive", -int(x.resolution.replace("p", "")) if x.resolution.replace("p", "").isdigit() else 0)) formats.sort(key=lambda x: (x.note != "Progressive", -int(x.resolution.replace("p", "")) if x.resolution.replace("p", "").isdigit() else 0))
logger.info(f"Found {len(formats)} suitable formats.")
if self.formats_callback: if self.formats_callback:
self.formats_callback(video_info, formats) self.formats_callback(video_info, formats)
except Exception as e: except Exception as e:
error_message = f"Failed to fetch formats: {e}" logger.error(f"Failed to fetch formats: {e}")
logger.error(error_message)
logger.debug(traceback.format_exc())
if self.formats_callback: if self.formats_callback:
self.formats_callback(None, None) # Indicate failure self.formats_callback(None, None)
def get_video_formats(self, url: str, formats_callback: Callable[[Optional[Dict[str, Any]], Optional[List[VideoFormat]]], None]): def get_video_formats(self, url: str, formats_callback: Callable):
"""Starts the format fetching process in a new thread."""
self.formats_callback = formats_callback self.formats_callback = formats_callback
thread = Thread(target=self._get_formats_task, args=(url,), daemon=True) Thread(target=self._get_formats_task, args=(url,), daemon=True).start()
thread.start()
def _download_task(self, url: str, download_path: str, format_id: str): def _download_task(self, url: str, download_path: str, format_id: str):
"""The actual download logic that runs in a separate thread."""
try: try:
logger.info(f"Starting download for URL: {url} with format: {format_id}") ydl_opts = {"format": format_id, "outtmpl": f"{download_path}/%(uploader)s_%(title)s_%(format_note)s.%(ext)s", "progress_hooks": [self._progress_hook], "logger": logger, "noplaylist": True}
ydl_opts = {
"format": format_id,
"outtmpl": f"{download_path}/%(uploader)s_%(title)s_%(format_note)s.%(ext)s",
"progress_hooks": [self._progress_hook],
"logger": logger,
"noplaylist": True,
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl: with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([url]) ydl.download([url])
logger.info(f"Successfully downloaded video from URL: {url}")
if self.completion_callback: if self.completion_callback:
self.completion_callback(None) self.completion_callback(None)
except Exception as e: except Exception as e:
error_message = f"An unexpected error occurred: {e}" logger.error(f"An unexpected error occurred: {e}")
logger.error(error_message)
logger.debug(traceback.format_exc())
if self.completion_callback: if self.completion_callback:
self.completion_callback(error_message) self.completion_callback(str(e))
def download_video( def download_video(self, url: str, download_path: str, format_id: str, progress_callback: Callable, completion_callback: Callable):
self,
url: str,
download_path: str,
format_id: str,
progress_callback: Callable[[int], None],
completion_callback: Callable[[Optional[str]], None],
):
"""Starts the video download in a new thread."""
self.progress_callback = progress_callback self.progress_callback = progress_callback
self.completion_callback = completion_callback self.completion_callback = completion_callback
thread = Thread( Thread(target=self._download_task, args=(url, download_path, format_id), daemon=True).start()
target=self._download_task, args=(url, download_path, format_id), daemon=True
) def _get_format_from_profile(self, profile: str) -> str:
thread.start() if "720p" in profile:
return "bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720][ext=mp4]/best"
elif "480p" in profile:
return "bestvideo[height<=480][ext=mp4]+bestaudio[ext=m4a]/best[height<=480][ext=mp4]/best"
else:
return "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"
def _batch_download_task(self, urls: List[str], download_path: str, quality_profile: str):
format_string = self._get_format_from_profile(quality_profile)
logger.info(f"Starting batch download for {len(urls)} videos with profile: {quality_profile}")
for i, url in enumerate(urls):
if not url.strip(): continue
if self.video_started_callback:
self.video_started_callback(url)
self.progress_callback(0)
try:
ydl_opts = {"format": format_string, "outtmpl": f"{download_path}/%(uploader)s_%(title)s_%(format_note)s.%(ext)s", "progress_hooks": [self._progress_hook], "logger": logger, "noplaylist": True}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([url])
if self.video_completed_callback:
self.video_completed_callback(url)
except Exception as e:
error_msg = str(e)
logger.error(f"Failed to download {url}. Reason: {error_msg}")
if self.video_error_callback:
self.video_error_callback(url, error_msg)
logger.info("--- Batch download finished ---")
if self.completion_callback:
self.completion_callback(None)
def download_batch(self, urls: List[str], download_path: str, quality_profile: str, progress_callback: Callable, completion_callback: Callable, video_started_callback: Callable, video_completed_callback: Callable, video_error_callback: Callable):
self.progress_callback = progress_callback
self.completion_callback = completion_callback
self.video_started_callback = video_started_callback
self.video_completed_callback = video_completed_callback
self.video_error_callback = video_error_callback
Thread(target=self._batch_download_task, args=(urls, download_path, quality_profile), daemon=True).start()

View File

@ -1,3 +1,4 @@
import logging import logging
import tkinter as tk import tkinter as tk
from tkinter import ttk, filedialog, messagebox from tkinter import ttk, filedialog, messagebox
@ -8,227 +9,250 @@ import os
from downloaderyoutube.core.core import Downloader, VideoFormat from downloaderyoutube.core.core import Downloader, VideoFormat
from downloaderyoutube.utils.logger import add_tkinter_handler, get_logger from downloaderyoutube.utils.logger import add_tkinter_handler, get_logger
# Get a logger instance for this module
logger = get_logger(__name__) logger = get_logger(__name__)
# --- Constants ---
CONFIG_FILE_NAME = ".last_download_path" CONFIG_FILE_NAME = ".last_download_path"
# A basic logging config dictionary for the Tkinter handler
LOGGING_CONFIG = { LOGGING_CONFIG = {
"colors": { "colors": {
logging.DEBUG: "gray", logging.DEBUG: "gray", logging.INFO: "black",
logging.INFO: "black", logging.WARNING: "orange", logging.ERROR: "red", logging.CRITICAL: "red",
logging.WARNING: "orange",
logging.ERROR: "red",
logging.CRITICAL: "red",
}, },
"queue_poll_interval_ms": 100, "queue_poll_interval_ms": 100,
} }
class App(tk.Frame): class App(tk.Frame):
"""
The main graphical user interface for the Downloader application.
"""
def __init__(self, master: tk.Tk): def __init__(self, master: tk.Tk):
super().__init__(master) super().__init__(master)
self.master = master self.master = master
self.downloader = Downloader() self.downloader = Downloader()
self.download_path: Optional[str] = None self.download_path: Optional[str] = None
self.available_formats: List[VideoFormat] = [] self.available_formats: List[VideoFormat] = []
self.url_to_tree_item: Dict[str, str] = {}
# StringVars for video info labels
self.title_var = tk.StringVar(value="Titolo: N/A") self.title_var = tk.StringVar(value="Titolo: N/A")
self.uploader_var = tk.StringVar(value="Autore: N/A") self.uploader_var = tk.StringVar(value="Autore: N/A")
self.duration_var = tk.StringVar(value="Durata: N/A") self.duration_var = tk.StringVar(value="Durata: N/A")
self.master.title("YouTube Downloader") self.master.title("YouTube Downloader")
self.master.geometry("800x650") self.master.geometry("800x750")
self.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self._create_widgets() self._create_widgets()
self._setup_logging_handler() self._setup_logging_handler()
self._load_last_path() self._load_last_path()
def _create_widgets(self): def _create_widgets(self):
"""Creates and lays out the widgets in the application window.""" self.notebook = ttk.Notebook(self)
# --- Top Frame for URL Input --- self.notebook.pack(fill=tk.BOTH, expand=True)
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)) single_tab = ttk.Frame(self.notebook, padding=10)
self.url_entry = ttk.Entry(url_frame) batch_tab = ttk.Frame(self.notebook, padding=10)
self.url_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) self.notebook.add(single_tab, text="Download Singolo")
self.analyze_button = ttk.Button( self.notebook.add(batch_tab, text="Download Multiplo")
url_frame, text="Analizza URL", command=self._analyze_url
)
self.analyze_button.pack(side=tk.LEFT, padx=(5, 0))
# --- Video Info Panel --- self._create_single_download_tab(single_tab)
info_frame = ttk.LabelFrame(self, text="Informazioni Video") self._create_batch_download_tab(batch_tab)
info_frame.pack(fill=tk.X, pady=5, padx=2)
ttk.Label(info_frame, textvariable=self.title_var).pack(anchor="w", padx=5) common_frame = ttk.Frame(self)
ttk.Label(info_frame, textvariable=self.uploader_var).pack(anchor="w", padx=5) common_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Label(info_frame, textvariable=self.duration_var).pack(anchor="w", padx=5)
path_frame = ttk.Frame(common_frame)
# --- Path Frame ---
path_frame = ttk.Frame(self)
path_frame.pack(fill=tk.X, pady=5) 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 = ttk.Button(
path_frame, text="Choose Folder...", command=self.select_download_path
)
self.path_button.pack(side=tk.LEFT) self.path_button.pack(side=tk.LEFT)
self.path_label = ttk.Label(path_frame, text="No download folder selected.") 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.path_label.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10)
# --- Format Selection Frame --- self.progress_bar = ttk.Progressbar(common_frame, orient="horizontal", mode="determinate")
format_frame = ttk.Frame(self) self.progress_bar.pack(fill=tk.X, pady=5)
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 = ttk.LabelFrame(self, text="Log")
log_frame.pack(fill=tk.BOTH, expand=True) log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=(5, 0))
self.log_widget = ScrolledText( self.log_widget = ScrolledText(log_frame, state=tk.DISABLED, wrap=tk.WORD, font=("Courier New", 9))
log_frame, state=tk.DISABLED, wrap=tk.WORD, font=("Courier New", 9) self.log_widget.pack(fill=tk.BOTH, expand=True)
)
self.log_widget.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) 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): def _setup_logging_handler(self):
logger.info("Setting up GUI logging handler.") add_tkinter_handler(self.log_widget, self.master, LOGGING_CONFIG)
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 _load_last_path(self): def _load_last_path(self):
"""Loads the last used download path from the config file.""" if os.path.exists(CONFIG_FILE_NAME):
try: with open(CONFIG_FILE_NAME, "r") as f:
if os.path.exists(CONFIG_FILE_NAME): path = f.read().strip()
with open(CONFIG_FILE_NAME, "r") as f: if os.path.isdir(path):
path = f.read().strip() self.download_path = path
if os.path.isdir(path): self.path_label.config(text=f"Saving to: {self.download_path}")
self.download_path = path
self.path_label.config(text=f"Saving to: {self.download_path}")
logger.info(f"Loaded last download path: {path}")
except Exception as e:
logger.warning(f"Could not load last download path: {e}")
def save_last_path(self): def save_last_path(self):
"""Saves the current download path to the config file."""
if self.download_path: if self.download_path:
try: with open(CONFIG_FILE_NAME, "w") as f: f.write(self.download_path)
with open(CONFIG_FILE_NAME, "w") as f:
f.write(self.download_path)
logger.info(f"Saved last download path: {self.download_path}")
except Exception as e:
logger.warning(f"Could not save last download path: {e}")
def select_download_path(self): def select_download_path(self):
path = filedialog.askdirectory() path = filedialog.askdirectory()
if path: if path:
self.download_path = path self.download_path = path
self.path_label.config(text=f"Saving to: {self.download_path}") self.path_label.config(text=f"Saving to: {self.download_path}")
logger.info(f"Download path set 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): def _analyze_url(self):
url = self.url_entry.get() url = self.url_entry.get().strip()
if not url: if not url: return
messagebox.showerror("Error", "Please enter a YouTube URL.") self._set_ui_state(False)
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.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) 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]]): def _on_formats_received(self, video_info, formats):
self.master.after(0, self._update_ui_after_analysis, video_info, formats) 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]]): def _update_ui_after_analysis(self, video_info, formats):
self.analyze_button.config(state="normal")
if video_info and formats: if video_info and formats:
logger.info("Successfully fetched video info and formats.")
self.title_var.set(f"Titolo: {video_info['title']}") self.title_var.set(f"Titolo: {video_info['title']}")
self.uploader_var.set(f"Autore: {video_info['uploader']}") self.uploader_var.set(f"Autore: {video_info['uploader']}")
self.duration_var.set(f"Durata: {video_info['duration_string']}") self.duration_var.set(f"Durata: {video_info['duration_string']}")
self.available_formats = formats self.available_formats = formats
self.format_combobox["values"] = [str(f) for f in formats] self.format_combobox["values"] = [str(f) for f in formats]
self.format_combobox.current(0) self.format_combobox.current(0)
self.format_combobox.config(state="readonly") self.format_combobox.config(state="readonly")
self.download_button.config(state="normal")
else: else:
logger.error("Failed to fetch video info or formats.") messagebox.showerror("Error", "Could not retrieve video information.")
messagebox.showerror("Error", "Could not retrieve video information. Check the URL and logs.")
self.format_combobox.set("Analisi fallita.") self.format_combobox.set("Analisi fallita.")
self.format_combobox["values"] = [] # ALWAYS update UI state at the end
self.title_var.set("Titolo: N/A") self._set_ui_state(True)
self.uploader_var.set("Autore: N/A")
self.duration_var.set("Durata: N/A")
def start_download(self): def start_single_download(self):
url = self.url_entry.get() if not self.download_path: messagebox.showerror("Error", "Please select a download folder."); return
if not self.download_path: idx = self.format_combobox.current()
messagebox.showerror("Error", "Please select a download folder.") if idx < 0: messagebox.showerror("Error", "Please select a format."); return
return self._set_ui_state(False)
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.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)
self.downloader.download_video( def start_batch_download(self):
url=url, if not self.download_path: messagebox.showerror("Error", "Please select a download folder."); return
download_path=self.download_path, urls = [self.batch_tree.item(item, "values")[0] for item in self.batch_tree.get_children()]
format_id=selected_format.format_id, if not urls: messagebox.showerror("Error", "Please add at least one URL to the list."); return
progress_callback=self._on_download_progress, self._set_ui_state(False)
completion_callback=self._on_download_complete, 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): def _on_download_progress(self, percentage: int):
self.master.after(0, self.progress_bar.config, {"value": percentage}) self.master.after(0, self.progress_bar.config, {"value": percentage})
def _on_download_complete(self, error_message: Optional[str]): def _on_download_complete(self, error_message: Optional[str]):
self.master.after(0, self._finalize_download, error_message) self.master.after(0, self._finalize_download, error_message, "Download completato con successo!")
def _finalize_download(self, error_message: Optional[str]): def _on_batch_complete(self, error_message: Optional[str]):
self.progress_bar["value"] = 0 self.master.after(0, self._finalize_download, error_message, "Download multiplo completato!")
self.download_button.config(state="normal")
self.analyze_button.config(state="normal")
def _finalize_download(self, error_message: Optional[str], success_message: str):
self._reset_forms()
self._set_ui_state(True)
if error_message: if error_message:
messagebox.showerror("Download Failed", error_message) messagebox.showerror("Download Failed", f"A problem occurred: {error_message}")
else: else:
messagebox.showinfo("Success", "Video downloaded successfully!") messagebox.showinfo("Success", success_message)