add multiple download
This commit is contained in:
parent
e42fd65115
commit
ee123b93d4
Binary file not shown.
|
Before Width: | Height: | Size: 0 B After Width: | Height: | Size: 47 KiB |
@ -4,131 +4,109 @@ from threading import Thread
|
||||
from dataclasses import dataclass
|
||||
import yt_dlp
|
||||
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
|
||||
|
||||
# Get a logger instance for this module
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@dataclass
|
||||
class VideoFormat:
|
||||
"""A simple class to hold structured information about a video format."""
|
||||
format_id: str
|
||||
resolution: str
|
||||
ext: str
|
||||
filesize: Optional[int]
|
||||
note: str # e.g., "Video Only", "Audio Only", "Progressive"
|
||||
note: 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"
|
||||
return f"{self.resolution} ({self.ext}) - {size_mb} - {self.note}"
|
||||
|
||||
|
||||
class Downloader:
|
||||
"""
|
||||
Handles video operations using yt-dlp in separate threads to avoid
|
||||
blocking the GUI.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# Callbacks for single/overall progress
|
||||
self.progress_callback: Optional[Callable[[int], 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
|
||||
# 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]):
|
||||
if d["status"] == "downloading":
|
||||
if d["status"] == "downloading" and self.progress_callback:
|
||||
if d.get("total_bytes") and d.get("downloaded_bytes"):
|
||||
percentage = (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.")
|
||||
self.progress_callback(int((d["downloaded_bytes"] / d["total_bytes"]) * 100))
|
||||
|
||||
def _get_formats_task(self, url: str):
|
||||
"""Task to fetch video formats and info in a thread."""
|
||||
try:
|
||||
logger.info(f"Fetching formats for URL: {url}")
|
||||
ydl_opts = {"noplaylist": True}
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
with yt_dlp.YoutubeDL({"noplaylist": True, "quiet": True}) as ydl:
|
||||
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"),
|
||||
}
|
||||
|
||||
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
|
||||
))
|
||||
|
||||
video_info = {"title": info.get("title", "N/A"), "uploader": info.get("uploader", "N/A"), "duration_string": info.get("duration_string", "N/A")}
|
||||
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"]
|
||||
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:
|
||||
self.formats_callback(video_info, formats)
|
||||
|
||||
except Exception as e:
|
||||
error_message = f"Failed to fetch formats: {e}"
|
||||
logger.error(error_message)
|
||||
logger.debug(traceback.format_exc())
|
||||
logger.error(f"Failed to fetch formats: {e}")
|
||||
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]):
|
||||
"""Starts the format fetching process in a new thread."""
|
||||
def get_video_formats(self, url: str, formats_callback: Callable):
|
||||
self.formats_callback = formats_callback
|
||||
thread = Thread(target=self._get_formats_task, args=(url,), daemon=True)
|
||||
thread.start()
|
||||
Thread(target=self._get_formats_task, args=(url,), daemon=True).start()
|
||||
|
||||
def _download_task(self, url: str, download_path: str, format_id: str):
|
||||
"""The actual download logic that runs in a separate thread."""
|
||||
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:
|
||||
ydl.download([url])
|
||||
|
||||
logger.info(f"Successfully downloaded video from URL: {url}")
|
||||
if self.completion_callback:
|
||||
self.completion_callback(None)
|
||||
|
||||
except Exception as e:
|
||||
error_message = f"An unexpected error occurred: {e}"
|
||||
logger.error(error_message)
|
||||
logger.debug(traceback.format_exc())
|
||||
logger.error(f"An unexpected error occurred: {e}")
|
||||
if self.completion_callback:
|
||||
self.completion_callback(error_message)
|
||||
self.completion_callback(str(e))
|
||||
|
||||
def download_video(
|
||||
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."""
|
||||
def download_video(self, url: str, download_path: str, format_id: str, progress_callback: Callable, completion_callback: Callable):
|
||||
self.progress_callback = progress_callback
|
||||
self.completion_callback = completion_callback
|
||||
thread = Thread(
|
||||
target=self._download_task, args=(url, download_path, format_id), daemon=True
|
||||
)
|
||||
thread.start()
|
||||
Thread(target=self._download_task, args=(url, download_path, format_id), daemon=True).start()
|
||||
|
||||
def _get_format_from_profile(self, profile: str) -> str:
|
||||
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()
|
||||
@ -1,3 +1,4 @@
|
||||
|
||||
import logging
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox
|
||||
@ -8,227 +9,250 @@ import os
|
||||
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__)
|
||||
|
||||
# --- Constants ---
|
||||
CONFIG_FILE_NAME = ".last_download_path"
|
||||
|
||||
# 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",
|
||||
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] = []
|
||||
self.url_to_tree_item: Dict[str, str] = {}
|
||||
|
||||
# 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")
|
||||
|
||||
self.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
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):
|
||||
"""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))
|
||||
self.notebook = ttk.Notebook(self)
|
||||
self.notebook.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
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))
|
||||
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")
|
||||
|
||||
# --- Video Info Panel ---
|
||||
info_frame = ttk.LabelFrame(self, text="Informazioni Video")
|
||||
info_frame.pack(fill=tk.X, pady=5, padx=2)
|
||||
self._create_single_download_tab(single_tab)
|
||||
self._create_batch_download_tab(batch_tab)
|
||||
|
||||
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)
|
||||
common_frame = ttk.Frame(self)
|
||||
common_frame.pack(fill=tk.X, padx=5, pady=5)
|
||||
|
||||
# --- Path Frame ---
|
||||
path_frame = ttk.Frame(self)
|
||||
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 = 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)
|
||||
self.progress_bar = ttk.Progressbar(common_frame, orient="horizontal", mode="determinate")
|
||||
self.progress_bar.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)
|
||||
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):
|
||||
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.")
|
||||
add_tkinter_handler(self.log_widget, self.master, LOGGING_CONFIG)
|
||||
|
||||
def _load_last_path(self):
|
||||
"""Loads the last used download path from the config file."""
|
||||
try:
|
||||
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}")
|
||||
logger.info(f"Loaded last download path: {path}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not load last download path: {e}")
|
||||
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):
|
||||
"""Saves the current download path to the config file."""
|
||||
if self.download_path:
|
||||
try:
|
||||
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}")
|
||||
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}")
|
||||
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):
|
||||
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")
|
||||
url = self.url_entry.get().strip()
|
||||
if not url: return
|
||||
self._set_ui_state(False)
|
||||
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]]):
|
||||
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: Optional[Dict[str, Any]], formats: Optional[List[VideoFormat]]):
|
||||
self.analyze_button.config(state="normal")
|
||||
def _update_ui_after_analysis(self, video_info, formats):
|
||||
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.")
|
||||
messagebox.showerror("Error", "Could not retrieve video information.")
|
||||
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")
|
||||
# ALWAYS update UI state at the end
|
||||
self._set_ui_state(True)
|
||||
|
||||
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")
|
||||
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)
|
||||
|
||||
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 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)
|
||||
self.master.after(0, self._finalize_download, error_message, "Download completato con successo!")
|
||||
|
||||
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")
|
||||
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", error_message)
|
||||
messagebox.showerror("Download Failed", f"A problem occurred: {error_message}")
|
||||
else:
|
||||
messagebox.showinfo("Success", "Video downloaded successfully!")
|
||||
messagebox.showinfo("Success", success_message)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user