最后活跃于 1754707381

Това приложение ви позволява лесно да сваляте музикални плейлисти от Spotify и YouTube

urocibg 修订了这个 Gist 1754707381. 转到此修订

1 file changed, 400 insertions

playlist_downloader.py(文件已创建)

@@ -0,0 +1,400 @@
1 + import os
2 + import sys
3 + import tkinter as tk
4 + from tkinter import filedialog, messagebox, ttk
5 + import webbrowser
6 + import subprocess
7 + import threading
8 + from pathlib import Path
9 +
10 + class PlaylistDownloader:
11 + def __init__(self):
12 + self.root = tk.Tk()
13 +
14 + # Инициализиране на променливите ПРЕДИ setup_ui()
15 + self.progress_var = tk.DoubleVar()
16 + self.status_var = tk.StringVar(value="Готов")
17 + self.playlist_file_path = tk.StringVar()
18 + self.spotify_link = tk.StringVar()
19 + self.folder_path = tk.StringVar()
20 + self.youtube_video_link = tk.StringVar()
21 + self.audio_quality = tk.StringVar(value="best")
22 + self.video_quality = tk.StringVar(value="best")
23 +
24 + self.setup_ui()
25 +
26 + def setup_ui(self):
27 + self.root.title("🎵 Spotify & YouTube Playlist Downloader v2.0")
28 + self.root.geometry("850x500")
29 + self.root.resizable(False, False)
30 +
31 + # Центриране на прозореца
32 + self.root.eval('tk::PlaceWindow . center')
33 +
34 + # Цветова схема
35 + bg_color = "#f0f0f0"
36 + button_color = "#4a90e2"
37 + success_color = "#5cb85c"
38 + info_color = "#5bc0de"
39 + warning_color = "#f0ad4e"
40 +
41 + self.root.configure(bg=bg_color)
42 +
43 + # Основна рамка с padding
44 + main_frame = tk.Frame(self.root, bg=bg_color, padx=20, pady=20)
45 + main_frame.pack(fill=tk.BOTH, expand=True)
46 +
47 + # Заглавие
48 + title_label = tk.Label(main_frame, text="🎵 Playlist Downloader",
49 + font=("Arial", 16, "bold"), bg=bg_color, fg="#333")
50 + title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
51 +
52 + row = 1
53 +
54 + # Файл с песни
55 + tk.Label(main_frame, text="📄 Текстов файл с имената на песните:",
56 + bg=bg_color, font=("Arial", 10)).grid(
57 + row=row, column=0, padx=10, pady=5, sticky="w")
58 + tk.Entry(main_frame, textvariable=self.playlist_file_path, width=60,
59 + state='readonly', font=("Arial", 9)).grid(row=row, column=1, padx=5, pady=5)
60 + tk.Button(main_frame, text="Избери", command=self.select_playlist_file,
61 + bg=button_color, fg="white", font=("Arial", 9),
62 + relief=tk.FLAT, padx=10).grid(row=row, column=2, padx=5, pady=5)
63 + row += 1
64 +
65 + # Spotify линк
66 + tk.Label(main_frame, text="🎧 Spotify плейлист линк:",
67 + bg=bg_color, font=("Arial", 10)).grid(
68 + row=row, column=0, padx=10, pady=5, sticky="w")
69 + spotify_entry = tk.Entry(main_frame, textvariable=self.spotify_link, width=60,
70 + font=("Arial", 9))
71 + spotify_entry.grid(row=row, column=1, padx=5, pady=5)
72 + tk.Button(main_frame, text="Постави",
73 + command=lambda: self.paste_from_clipboard(self.spotify_link),
74 + bg=button_color, fg="white", font=("Arial", 9),
75 + relief=tk.FLAT, padx=10).grid(row=row, column=2, padx=5, pady=5)
76 + row += 1
77 +
78 + # YouTube видео линк
79 + tk.Label(main_frame, text="📹 YouTube видео линк:",
80 + bg=bg_color, font=("Arial", 10)).grid(
81 + row=row, column=0, padx=10, pady=5, sticky="w")
82 + youtube_entry = tk.Entry(main_frame, textvariable=self.youtube_video_link, width=60,
83 + font=("Arial", 9))
84 + youtube_entry.grid(row=row, column=1, padx=5, pady=5)
85 + tk.Button(main_frame, text="Постави",
86 + command=lambda: self.paste_from_clipboard(self.youtube_video_link),
87 + bg=button_color, fg="white", font=("Arial", 9),
88 + relief=tk.FLAT, padx=10).grid(row=row, column=2, padx=5, pady=5)
89 + row += 1
90 +
91 + # Папка за запазване
92 + tk.Label(main_frame, text="📁 Папка за запазване:",
93 + bg=bg_color, font=("Arial", 10)).grid(
94 + row=row, column=0, padx=10, pady=5, sticky="w")
95 + tk.Entry(main_frame, textvariable=self.folder_path, width=60,
96 + font=("Arial", 9)).grid(row=row, column=1, padx=5, pady=5)
97 + tk.Button(main_frame, text="Избери", command=self.select_folder,
98 + bg=button_color, fg="white", font=("Arial", 9),
99 + relief=tk.FLAT, padx=10).grid(row=row, column=2, padx=5, pady=5)
100 + row += 1
101 +
102 + # Качество на аудио
103 + tk.Label(main_frame, text="🎵 Качество на аудиото:",
104 + bg=bg_color, font=("Arial", 10)).grid(
105 + row=row, column=0, padx=10, pady=5, sticky="w")
106 + audio_quality_combo = ttk.Combobox(main_frame, textvariable=self.audio_quality,
107 + values=["best", "320k", "256k", "128k"],
108 + state="readonly", width=57, font=("Arial", 9))
109 + audio_quality_combo.grid(row=row, column=1, padx=5, pady=5, columnspan=2, sticky="w")
110 + row += 1
111 +
112 + # Качество на видео
113 + tk.Label(main_frame, text="📹 Качество на видеото:",
114 + bg=bg_color, font=("Arial", 10)).grid(
115 + row=row, column=0, padx=10, pady=5, sticky="w")
116 + video_quality_combo = ttk.Combobox(main_frame, textvariable=self.video_quality,
117 + values=["best", "1080p", "720p", "480p"],
118 + state="readonly", width=57, font=("Arial", 9))
119 + video_quality_combo.grid(row=row, column=1, padx=5, pady=5, columnspan=2, sticky="w")
120 + row += 1
121 +
122 + # Прогрес бар
123 + tk.Label(main_frame, text="Прогрес:", bg=bg_color, font=("Arial", 10)).grid(
124 + row=row, column=0, padx=10, pady=(20, 5), sticky="w")
125 + self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var,
126 + length=500, mode='determinate')
127 + self.progress_bar.grid(row=row, column=1, columnspan=2, padx=5, pady=(20, 5), sticky="ew")
128 + row += 1
129 +
130 + # Статус
131 + self.status_label = tk.Label(main_frame, textvariable=self.status_var,
132 + bg=bg_color, font=("Arial", 9), fg="#666")
133 + self.status_label.grid(row=row, column=0, columnspan=3, pady=5)
134 + row += 1
135 +
136 + # Бутони за сваляне
137 + button_frame = tk.Frame(main_frame, bg=bg_color)
138 + button_frame.grid(row=row, column=0, columnspan=3, pady=20)
139 +
140 + tk.Button(button_frame, text="🎧 Свали от Spotify",
141 + command=self.download_spotify_playlist,
142 + bg=success_color, fg="white", font=("Arial", 10, "bold"),
143 + relief=tk.FLAT, padx=15, pady=8, width=22).pack(side=tk.LEFT, padx=5)
144 +
145 + tk.Button(button_frame, text="🎵 Свали MP3 от YouTube",
146 + command=self.download_from_youtube,
147 + bg=info_color, fg="white", font=("Arial", 10, "bold"),
148 + relief=tk.FLAT, padx=15, pady=8, width=22).pack(side=tk.LEFT, padx=5)
149 +
150 + tk.Button(button_frame, text="📹 Свали YouTube видео",
151 + command=self.download_youtube_video,
152 + bg=warning_color, fg="white", font=("Arial", 10, "bold"),
153 + relief=tk.FLAT, padx=15, pady=8, width=22).pack(side=tk.LEFT, padx=5)
154 + row += 1
155 +
156 + # Линк към сайт
157 + link_button = tk.Button(main_frame, text="🌐 Посетете сайта на приложението",
158 + command=self.open_link, bg=bg_color, fg=button_color,
159 + font=("Arial", 9, "underline"), relief=tk.FLAT,
160 + cursor="hand2")
161 + link_button.grid(row=row, column=0, columnspan=3, pady=10)
162 +
163 + def select_playlist_file(self):
164 + filename = filedialog.askopenfilename(
165 + title="Изберете текстов файл",
166 + filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
167 + )
168 + if filename:
169 + self.playlist_file_path.set(filename)
170 +
171 + def select_folder(self):
172 + folder = filedialog.askdirectory(title="Изберете папка за запазване")
173 + if folder:
174 + self.folder_path.set(folder)
175 +
176 + def paste_from_clipboard(self, var):
177 + try:
178 + clipboard_content = self.root.clipboard_get()
179 + var.set(clipboard_content)
180 + except tk.TkError:
181 + messagebox.showwarning("Предупреждение", "Няма данни в клипборда")
182 +
183 + def validate_url(self, url, platform):
184 + if platform == "spotify":
185 + return "spotify.com" in url and "playlist" in url
186 + elif platform == "youtube":
187 + return any(domain in url for domain in ["youtube.com", "youtu.be"])
188 + return False
189 +
190 + def check_dependencies(self):
191 + """Проверява дали са инсталирани необходимите инструменти"""
192 + missing = []
193 +
194 + # Проверка за spotdl
195 + try:
196 + result = subprocess.run(['spotdl', '--version'],
197 + capture_output=True, text=True, timeout=5)
198 + if result.returncode != 0:
199 + missing.append("spotdl")
200 + except (subprocess.TimeoutExpired, FileNotFoundError):
201 + missing.append("spotdl")
202 +
203 + # Проверка за yt-dlp
204 + try:
205 + result = subprocess.run(['yt-dlp', '--version'],
206 + capture_output=True, text=True, timeout=5)
207 + if result.returncode != 0:
208 + missing.append("yt-dlp")
209 + except (subprocess.TimeoutExpired, FileNotFoundError):
210 + missing.append("yt-dlp")
211 +
212 + if missing:
213 + messagebox.showerror(
214 + "Грешка",
215 + f"Липсват необходимите инструменти: {', '.join(missing)}\n\n"
216 + f"Моля инсталирайте ги с:\n"
217 + f"pip install spotdl yt-dlp"
218 + )
219 + return False
220 + return True
221 +
222 + def run_download(self, func, *args):
223 + """Стартира сваляне в отделен thread"""
224 + if not self.check_dependencies():
225 + return
226 +
227 + thread = threading.Thread(target=func, args=args)
228 + thread.daemon = True
229 + thread.start()
230 +
231 + def update_progress(self, value, status):
232 + """Обновява прогрес бара и статуса"""
233 + self.progress_var.set(value)
234 + self.status_var.set(status)
235 + self.root.update_idletasks()
236 +
237 + def download_spotify_playlist(self):
238 + self.run_download(self._download_spotify_playlist)
239 +
240 + def _download_spotify_playlist(self):
241 + playlist_link = self.spotify_link.get().strip()
242 + output_folder = self.folder_path.get().strip()
243 +
244 + if not playlist_link:
245 + messagebox.showerror("Грешка", "Моля, въведете линк към Spotify плейлист.")
246 + return
247 +
248 + if not self.validate_url(playlist_link, "spotify"):
249 + messagebox.showerror("Грешка", "Невалиден Spotify плейлист линк.")
250 + return
251 +
252 + if not output_folder:
253 + messagebox.showerror("Грешка", "Моля, изберете папка за запазване.")
254 + return
255 +
256 + try:
257 + self.update_progress(10, "Започва сваляне от Spotify...")
258 +
259 + # Създаване на папката ако не съществува
260 + Path(output_folder).mkdir(parents=True, exist_ok=True)
261 +
262 + # Команда за spotdl
263 + quality_param = f"--audio-bitrate {self.audio_quality.get()}" if self.audio_quality.get() != "best" else ""
264 + cmd = f'spotdl "{playlist_link}" --output "{output_folder}" {quality_param}'
265 +
266 + self.update_progress(30, "Сваляне в процес...")
267 +
268 + process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
269 + stderr=subprocess.STDOUT, text=True, cwd=output_folder)
270 +
271 + while True:
272 + output = process.stdout.readline()
273 + if output == '' and process.poll() is not None:
274 + break
275 + if output:
276 + self.update_progress(50, f"Сваляне: {output.strip()[:50]}...")
277 +
278 + if process.returncode == 0:
279 + self.update_progress(100, "Плейлистът е успешно свален!")
280 + messagebox.showinfo("Успех", "Плейлистът беше успешно свален.")
281 + else:
282 + raise Exception("Грешка при изпълнение на spotdl")
283 +
284 + except Exception as e:
285 + self.update_progress(0, "Грешка при свалянето")
286 + messagebox.showerror("Грешка", f"Грешка при свалянето: {str(e)}")
287 +
288 + def download_from_youtube(self):
289 + self.run_download(self._download_from_youtube)
290 +
291 + def _download_from_youtube(self):
292 + playlist_file = self.playlist_file_path.get().strip()
293 + output_folder = self.folder_path.get().strip()
294 +
295 + if not playlist_file:
296 + messagebox.showerror("Грешка", "Моля, изберете текстов файл с имената на песните.")
297 + return
298 + if not output_folder:
299 + messagebox.showerror("Грешка", "Моля, изберете папка за запазване.")
300 + return
301 +
302 + try:
303 + self.update_progress(5, "Четене на файла с песни...")
304 +
305 + with open(playlist_file, 'r', encoding='utf-8') as file:
306 + songs = [line.strip() for line in file.readlines() if line.strip()]
307 +
308 + if not songs:
309 + messagebox.showerror("Грешка", "Файлът е празен или не съдържа валидни песни.")
310 + return
311 +
312 + Path(output_folder).mkdir(parents=True, exist_ok=True)
313 + total_songs = len(songs)
314 +
315 + for i, song in enumerate(songs):
316 + progress = (i / total_songs) * 90 + 10
317 + self.update_progress(progress, f"Сваляне: {song[:40]}...")
318 +
319 + # Качество на аудиото
320 + quality_param = ""
321 + if self.audio_quality.get() != "best":
322 + quality_param = f"--audio-quality {self.audio_quality.get()}"
323 +
324 + cmd = f'yt-dlp --extract-audio --audio-format mp3 {quality_param} -o "%(title)s.%(ext)s" "ytsearch:{song}"'
325 +
326 + result = subprocess.run(cmd, shell=True, cwd=output_folder,
327 + capture_output=True, text=True)
328 +
329 + if result.returncode != 0:
330 + print(f"Грешка при {song}: {result.stderr}")
331 +
332 + self.update_progress(100, f"Свалени {total_songs} песни!")
333 + messagebox.showinfo("Успех", f"Всички {total_songs} песни бяха успешно свалени.")
334 +
335 + except Exception as e:
336 + self.update_progress(0, "Грешка при свалянето")
337 + messagebox.showerror("Грешка", f"Грешка при свалянето: {str(e)}")
338 +
339 + def download_youtube_video(self):
340 + self.run_download(self._download_youtube_video)
341 +
342 + def _download_youtube_video(self):
343 + video_link = self.youtube_video_link.get().strip()
344 + output_folder = self.folder_path.get().strip()
345 +
346 + if not video_link:
347 + messagebox.showerror("Грешка", "Моля, въведете линк към YouTube видеото.")
348 + return
349 +
350 + if not self.validate_url(video_link, "youtube"):
351 + messagebox.showerror("Грешка", "Невалиден YouTube линк.")
352 + return
353 +
354 + if not output_folder:
355 + messagebox.showerror("Грешка", "Моля, изберете папка за запазване.")
356 + return
357 +
358 + try:
359 + self.update_progress(10, "Започва сваляне на видеото...")
360 +
361 + Path(output_folder).mkdir(parents=True, exist_ok=True)
362 +
363 + # Качество на видеото
364 + format_param = "best"
365 + if self.video_quality.get() != "best":
366 + format_param = f"best[height<={self.video_quality.get()[:-1]}]"
367 +
368 + cmd = f'yt-dlp -f "{format_param}" -o "%(title)s.%(ext)s" "{video_link}"'
369 +
370 + self.update_progress(30, "Сваляне в процес...")
371 +
372 + process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
373 + stderr=subprocess.STDOUT, text=True, cwd=output_folder)
374 +
375 + while True:
376 + output = process.stdout.readline()
377 + if output == '' and process.poll() is not None:
378 + break
379 + if output:
380 + self.update_progress(50, f"Сваляне: {output.strip()[:50]}...")
381 +
382 + if process.returncode == 0:
383 + self.update_progress(100, "Видеото е успешно свалено!")
384 + messagebox.showinfo("Успех", "Видеото беше успешно свалено.")
385 + else:
386 + raise Exception("Грешка при изпълнение на yt-dlp")
387 +
388 + except Exception as e:
389 + self.update_progress(0, "Грешка при свалянето")
390 + messagebox.showerror("Грешка", f"Грешка при свалянето: {str(e)}")
391 +
392 + def open_link(self):
393 + webbrowser.open("https://urocibg.eu/%f0%9f%8e%b5-spotify-%d0%b8-youtube-playlist-downloader/")
394 +
395 + def run(self):
396 + self.root.mainloop()
397 +
398 + if __name__ == "__main__":
399 + app = PlaylistDownloader()
400 + app.run()
上一页 下一页