下载Youtube 播放列表的脚本,支持添加多个下载播放列表,视频的总数和已完成数,还有每个视频的下载日志进度以及下载到哪一个视频。
import os
import threading
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext
from tkinter.ttk import Progressbar
from pytube import Playlist, YouTube
from pytube.exceptions import VideoUnavailable
from PIL import Image, ImageTk
class YouTubeDownloader(tk.Tk):
def __init__(self):
super().__init__()
self.title("YouTube 播放列表下载器")
self.geometry("900x700")
self.default_save_path = os.path.expanduser("~/Downloads")
self.create_widgets()
self.tasks = []
self.task_threads = []
def create_widgets(self):
tk.Label(self, text="播放列表或视频 URL:").grid(row=0, column=0, padx=10, pady=10)
self.entry_playlist_url = tk.Entry(self, width=50)
self.entry_playlist_url.grid(row=0, column=1, padx=10, pady=10)
tk.Label(self, text="保存路径:").grid(row=1, column=0, padx=10, pady=10)
self.entry_save_path = tk.Entry(self, width=50)
self.entry_save_path.grid(row=1, column=1, padx=10, pady=10)
self.entry_save_path.insert(0, self.default_save_path)
tk.Button(self, text="浏览...", command=self.browse_directory).grid(row=1, column=2, padx=10, pady=10)
self.start_button = tk.Button(self, text="添加下载任务", command=self.add_task)
self.start_button.grid(row=2, column=0, pady=20, columnspan=3)
self.task_frame = tk.Frame(self)
self.task_frame.grid(row=3, columnspan=3, padx=10, pady=10, sticky="nsew")
self.task_frame.grid_rowconfigure(0, weight=1)
self.task_frame.grid_columnconfigure(0, weight=1)
def browse_directory(self):
directory = filedialog.askdirectory()
if directory:
self.entry_save_path.delete(0, tk.END)
self.entry_save_path.insert(0, directory)
def add_task(self):
playlist_url = self.entry_playlist_url.get()
save_path = self.entry_save_path.get()
if not playlist_url or not save_path:
messagebox.showerror("错误", "请输入播放列表或视频 URL 和保存路径")
return
task = DownloadTask(self.task_frame, playlist_url, save_path, len(self.tasks))
self.tasks.append(task)
self.task_threads.append(threading.Thread(target=task.download))
self.task_threads[-1].start()
class DownloadTask:
def __init__(self, master, playlist_url, save_path, row):
self.master = master
self.playlist_url = playlist_url
self.save_path = save_path
self.is_paused = False
self.is_downloading = True
self.frame = tk.Frame(master)
self.frame.grid(row=row*2, column=0, pady=5, sticky="nsew")
self.label = tk.Label(self.frame, text=f"下载任务: {playlist_url}")
self.label.grid(row=0, column=0, padx=10, pady=5, sticky="w")
image_path = os.path.join(os.path.dirname(__file__), 'images')
self.pause_image = ImageTk.PhotoImage(Image.open(os.path.join(image_path, "pause.png")).resize((32, 32)))
self.resume_image = ImageTk.PhotoImage(Image.open(os.path.join(image_path, "resume.png")).resize((32, 32)))
self.pause_resume_button = tk.Button(self.frame, image=self.pause_image, command=self.toggle_pause_resume)
self.pause_resume_button.grid(row=0, column=1, padx=10, pady=5, sticky="e")
self.progress_label = tk.Label(self.frame, text="进度: 0/0")
self.progress_label.grid(row=0, column=2, padx=10, pady=5, sticky="e")
self.progressbar = Progressbar(self.frame, length=200, mode='determinate')
self.progressbar.grid(row=0, column=3, padx=10, pady=5, sticky="e")
self.log_text = scrolledtext.ScrolledText(self.master, width=100, height=5, state='disabled')
self.log_text.grid(row=row*2+1, column=0, padx=10, pady=5, sticky="nsew")
def toggle_pause_resume(self):
self.is_paused = not self.is_paused
if self.is_paused:
self.pause_resume_button.config(image=self.resume_image)
else:
self.pause_resume_button.config(image=self.pause_image)
def download(self):
try:
playlist = Playlist(self.playlist_url) if 'playlist' in self.playlist_url else [self.playlist_url]
if not os.path.exists(self.save_path):
os.makedirs(self.save_path)
total_videos = len(playlist.video_urls) if hasattr(playlist, 'video_urls') else 1
downloaded_videos = 0
self.update_progress_label(downloaded_videos, total_videos)
for video_url in (playlist.video_urls if hasattr(playlist, 'video_urls') else playlist):
while self.is_paused:
continue
if not self.is_downloading:
break
self.download_video(video_url)
downloaded_videos += 1
self.progressbar['value'] = (downloaded_videos / total_videos) * 100
self.update_progress_label(downloaded_videos, total_videos)
self.update_progress_label(downloaded_videos, total_videos, finished=True)
messagebox.showinfo("完成", f"下载任务完成: {self.playlist_url}")
self.is_downloading = False
except Exception as e:
messagebox.showerror("错误", f"下载任务失败: {e}")
self.log_message(f"下载任务失败: {e}")
def download_video(self, video_url):
try:
yt = YouTube(video_url)
video_title = yt.title.replace("/", "_").replace("\\", "_")
file_path = os.path.join(self.save_path, f"{video_title}.mp4")
if os.path.exists(file_path):
self.log_message(f"视频已存在,跳过下载: {yt.title}")
return
self.log_message(f"正在下载视频: {yt.title}")
stream = yt.streams.filter(progressive=True, file_extension='mp4').order_by('resolution').desc().first()
stream.download(output_path=self.save_path, filename=f"{video_title}.mp4")
self.log_message(f"下载完成: {yt.title}")
except VideoUnavailable:
self.log_message(f"视频无法访问,跳过: {video_url}")
except Exception as e:
self.log_message(f"下载视频 {video_url} 时出错: {e}")
def update_progress_label(self, downloaded, total, finished=False):
status = "完成" if finished else "进度"
self.progress_label.config(text=f"{status}: {downloaded}/{total}")
def log_message(self, message):
self.log_text.config(state='normal')
self.log_text.insert(tk.END, message + '\n')
self.log_text.yview(tk.END)
self.log_text.config(state='disabled')
if __name__ == "__main__":
app = YouTubeDownloader()
app.mainloop()
注意事项:
- 图标路径:确保
pause.png
和resume.png
文件存在于images
文件夹中,并且图标文件大小适当(32×32像素)。
- 依赖安装:确保安装必要的依赖项:bash复制代码
pip install pytube pillow
- 打包:如果需要将应用程序打包为可执行文件,使用
PyInstaller
或其他打包工具时,确保将所有必要的资源文件(如图标)包含在内。