Show completion percentage and pop up when download is done
This commit is contained in:
@@ -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)
|
Reference in New Issue
Block a user