Show completion percentage and pop up when download is done

This commit is contained in:
2025-08-10 13:42:11 -05:00
parent cd8476b506
commit 2fcd202532

View File

@@ -37,6 +37,9 @@ class YouTubeArchiverStandalone:
self.download_process = None
self.is_downloading = False
self.dependencies_checked = False
self.total_videos = 0
self.downloaded_videos = 0
self.current_video = ""
self.create_widgets()
@@ -178,15 +181,19 @@ class YouTubeArchiverStandalone:
self.download_btn.state(['disabled']) # Disabled until dependencies are ready
# Progress bar
self.progress = ttk.Progressbar(main_frame, mode='indeterminate')
self.progress.grid(row=9, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
self.progress = ttk.Progressbar(main_frame, mode='determinate', maximum=100)
self.progress.grid(row=9, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 5))
# Progress label
self.progress_label = ttk.Label(main_frame, text="", font=('Arial', 9))
self.progress_label.grid(row=10, column=0, columnspan=3, pady=(0, 10))
# Output text area
output_frame = ttk.LabelFrame(main_frame, text="Download Progress", padding="5")
output_frame.grid(row=10, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
output_frame.grid(row=11, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
output_frame.columnconfigure(0, weight=1)
output_frame.rowconfigure(0, weight=1)
main_frame.rowconfigure(10, weight=1)
main_frame.rowconfigure(11, weight=1)
self.output_text = scrolledtext.ScrolledText(output_frame, height=10, width=80,
font=('Consolas', 9))
@@ -196,7 +203,7 @@ class YouTubeArchiverStandalone:
self.status_var = tk.StringVar(value="Checking system dependencies...")
status_bar = ttk.Label(main_frame, textvariable=self.status_var,
relief=tk.SUNKEN, anchor=tk.W)
status_bar.grid(row=11, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(5, 0))
status_bar.grid(row=12, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(5, 0))
def paste_from_clipboard(self):
"""Paste URL from clipboard."""
@@ -552,13 +559,16 @@ The application will automatically detect yt-dlp once it's installed."""
if not self.validate_inputs():
return
# Clear previous output
# Clear previous output and reset progress
self.clear_output()
self.reset_progress()
# Update UI
self.is_downloading = True
self.download_btn.config(text="Stop Download")
self.progress.config(mode='indeterminate')
self.progress.start()
self.progress_label.config(text="Initializing download...")
self.status_var.set("Downloading...")
# Build command
@@ -573,6 +583,155 @@ The application will automatically detect yt-dlp once it's installed."""
self.download_thread.daemon = True
self.download_thread.start()
def reset_progress(self):
"""Reset progress tracking variables."""
self.total_videos = 0
self.downloaded_videos = 0
self.current_video = ""
self.progress.config(value=0)
self.progress_label.config(text="")
def update_progress(self, downloaded, total, current_title=""):
"""Update the progress bar and label."""
self.downloaded_videos = downloaded
self.total_videos = total
self.current_video = current_title
if total > 0:
percentage = (downloaded / total) * 100
self.progress.config(mode='determinate', value=percentage)
# Truncate long titles
display_title = current_title[:50] + "..." if len(current_title) > 50 else current_title
self.progress_label.config(
text=f"Progress: {downloaded}/{total} videos ({percentage:.1f}%) - {display_title}"
)
else:
self.progress.config(mode='indeterminate')
self.progress_label.config(text="Analyzing channel...")
def parse_download_output(self, line):
"""Parse yt-dlp output to extract progress information."""
line = line.strip()
# Look for playlist info: [download] Downloading video 5 of 23: Title
if "[download] Downloading video" in line:
try:
# Extract: "Downloading video 5 of 23: Title"
parts = line.split(":")
if len(parts) >= 2:
video_part = parts[1].strip() # "Downloading video 5 of 23"
title_part = ":".join(parts[2:]).strip() if len(parts) > 2 else ""
# Extract numbers: "5 of 23"
if " of " in video_part:
numbers = video_part.split(" of ")
if len(numbers) == 2:
try:
current = int(numbers[0].split()[-1]) # Get last word before "of"
total = int(numbers[1].split()[0]) # Get first word after "of"
self.root.after(0, lambda: self.update_progress(current, total, title_part))
except (ValueError, IndexError):
pass
except Exception:
pass
# Look for single video download progress
elif "[download]" in line and "%" in line:
# Extract percentage from lines like: [download] 45.2% of 123.45MiB at 1.23MiB/s ETA 00:30
try:
if "%" in line:
percent_part = line.split("%")[0]
percent_value = float(percent_part.split()[-1])
# If we don't have total videos info, show single video progress
if self.total_videos == 0:
self.root.after(0, lambda: self.update_single_video_progress(percent_value))
except (ValueError, IndexError):
pass
# Look for playlist detection
elif "[youtube] Extracting playlist information" in line or "playlist" in line.lower():
self.root.after(0, lambda: self.update_progress(0, 0, "Analyzing playlist..."))
def update_single_video_progress(self, percentage):
"""Update progress for single video download."""
self.progress.config(mode='determinate', value=percentage)
self.progress_label.config(text=f"Download progress: {percentage:.1f}%")
def show_completion_notification(self, success=True, message=""):
"""Show a popup notification when download completes."""
if success:
title = "Download Complete!"
icon = "info"
msg = f"Channel download completed successfully!\n\n"
if self.total_videos > 0:
msg += f"Downloaded {self.downloaded_videos} out of {self.total_videos} videos.\n"
msg += f"Files saved to:\n{self.output_dir.get()}"
else:
title = "Download Finished"
icon = "warning"
msg = f"Download finished with some issues.\n\n{message}\n\n"
msg += f"Files saved to:\n{self.output_dir.get()}"
# Create custom notification window
notification = tk.Toplevel(self.root)
notification.title(title)
notification.geometry("450x250")
notification.resizable(False, False)
notification.transient(self.root)
notification.grab_set()
# Center the notification
notification.update_idletasks()
x = (notification.winfo_screenwidth() // 2) - (notification.winfo_width() // 2)
y = (notification.winfo_screenheight() // 2) - (notification.winfo_height() // 2)
notification.geometry(f"+{x}+{y}")
# Configure notification content
frame = ttk.Frame(notification, padding="20")
frame.pack(fill="both", expand=True)
# Title
title_label = ttk.Label(frame, text=title, font=('Arial', 14, 'bold'))
title_label.pack(pady=(0, 10))
# Message
message_label = ttk.Label(frame, text=msg, wraplength=400, justify="center")
message_label.pack(pady=(0, 15))
# Buttons
button_frame = ttk.Frame(frame)
button_frame.pack(fill="x", pady=(10, 0))
def open_folder():
"""Open the download folder."""
try:
if platform.system() == "Windows":
os.startfile(self.output_dir.get())
elif platform.system() == "Darwin": # macOS
subprocess.run(["open", self.output_dir.get()])
else: # Linux
subprocess.run(["xdg-open", self.output_dir.get()])
notification.destroy()
except Exception as e:
messagebox.showerror("Error", f"Could not open folder: {e}")
ttk.Button(button_frame, text="Open Folder", command=open_folder).pack(side="left", padx=(0, 10))
ttk.Button(button_frame, text="OK", command=notification.destroy).pack(side="right")
# Auto-close after 30 seconds
notification.after(30000, notification.destroy)
# Play system notification sound
try:
if platform.system() == "Windows":
import winsound
winsound.PlaySound("SystemExclamation", winsound.SND_ALIAS)
except ImportError:
pass # No sound on non-Windows or if winsound not available
def run_download(self, cmd):
"""Run the download process."""
try:
@@ -591,15 +750,20 @@ The application will automatically detect yt-dlp once it's installed."""
for line in iter(self.download_process.stdout.readline, ''):
if not self.is_downloading:
break
# Parse the line for progress information
self.parse_download_output(line)
# Send line to output display
self.output_queue.put(('output', line.rstrip()))
# Wait for process to complete
return_code = self.download_process.wait()
if return_code == 0:
self.output_queue.put(('status', 'Download completed successfully!'))
self.output_queue.put(('success', 'Download completed successfully!'))
else:
self.output_queue.put(('status', f'Download finished with some errors (code {return_code})'))
self.output_queue.put(('warning', f'Download finished with some errors (code {return_code})'))
except Exception as e:
self.output_queue.put(('error', f'Error during download: {str(e)}'))
@@ -619,6 +783,7 @@ The application will automatically detect yt-dlp once it's installed."""
self.is_downloading = False
self.download_btn.config(text="Download Channel")
self.progress.stop()
self.progress.config(mode='determinate')
self.status_var.set("Ready to download!")
def check_queue(self):
@@ -629,9 +794,14 @@ The application will automatically detect yt-dlp once it's installed."""
if msg_type == 'output':
self.log_output(message)
elif msg_type == 'status':
elif msg_type == 'success':
self.log_output(f"\n{message}")
self.status_var.set(message)
self.show_completion_notification(success=True)
elif msg_type == 'warning':
self.log_output(f"\n{message}")
self.status_var.set(message)
self.show_completion_notification(success=False, message=message)
elif msg_type == 'error':
self.log_output(f"\n[ERROR] {message}")
messagebox.showerror("Download Error", message)