playlist_downloader.py
· 18 KiB · Python
Eredeti
import os
import sys
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import webbrowser
import subprocess
import threading
from pathlib import Path
class PlaylistDownloader:
def __init__(self):
self.root = tk.Tk()
# Инициализиране на променливите ПРЕДИ setup_ui()
self.progress_var = tk.DoubleVar()
self.status_var = tk.StringVar(value="Готов")
self.playlist_file_path = tk.StringVar()
self.spotify_link = tk.StringVar()
self.folder_path = tk.StringVar()
self.youtube_video_link = tk.StringVar()
self.audio_quality = tk.StringVar(value="best")
self.video_quality = tk.StringVar(value="best")
self.setup_ui()
def setup_ui(self):
self.root.title("🎵 Spotify & YouTube Playlist Downloader v2.0")
self.root.geometry("850x500")
self.root.resizable(False, False)
# Центриране на прозореца
self.root.eval('tk::PlaceWindow . center')
# Цветова схема
bg_color = "#f0f0f0"
button_color = "#4a90e2"
success_color = "#5cb85c"
info_color = "#5bc0de"
warning_color = "#f0ad4e"
self.root.configure(bg=bg_color)
# Основна рамка с padding
main_frame = tk.Frame(self.root, bg=bg_color, padx=20, pady=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# Заглавие
title_label = tk.Label(main_frame, text="🎵 Playlist Downloader",
font=("Arial", 16, "bold"), bg=bg_color, fg="#333")
title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
row = 1
# Файл с песни
tk.Label(main_frame, text="📄 Текстов файл с имената на песните:",
bg=bg_color, font=("Arial", 10)).grid(
row=row, column=0, padx=10, pady=5, sticky="w")
tk.Entry(main_frame, textvariable=self.playlist_file_path, width=60,
state='readonly', font=("Arial", 9)).grid(row=row, column=1, padx=5, pady=5)
tk.Button(main_frame, text="Избери", command=self.select_playlist_file,
bg=button_color, fg="white", font=("Arial", 9),
relief=tk.FLAT, padx=10).grid(row=row, column=2, padx=5, pady=5)
row += 1
# Spotify линк
tk.Label(main_frame, text="🎧 Spotify плейлист линк:",
bg=bg_color, font=("Arial", 10)).grid(
row=row, column=0, padx=10, pady=5, sticky="w")
spotify_entry = tk.Entry(main_frame, textvariable=self.spotify_link, width=60,
font=("Arial", 9))
spotify_entry.grid(row=row, column=1, padx=5, pady=5)
tk.Button(main_frame, text="Постави",
command=lambda: self.paste_from_clipboard(self.spotify_link),
bg=button_color, fg="white", font=("Arial", 9),
relief=tk.FLAT, padx=10).grid(row=row, column=2, padx=5, pady=5)
row += 1
# YouTube видео линк
tk.Label(main_frame, text="📹 YouTube видео линк:",
bg=bg_color, font=("Arial", 10)).grid(
row=row, column=0, padx=10, pady=5, sticky="w")
youtube_entry = tk.Entry(main_frame, textvariable=self.youtube_video_link, width=60,
font=("Arial", 9))
youtube_entry.grid(row=row, column=1, padx=5, pady=5)
tk.Button(main_frame, text="Постави",
command=lambda: self.paste_from_clipboard(self.youtube_video_link),
bg=button_color, fg="white", font=("Arial", 9),
relief=tk.FLAT, padx=10).grid(row=row, column=2, padx=5, pady=5)
row += 1
# Папка за запазване
tk.Label(main_frame, text="📁 Папка за запазване:",
bg=bg_color, font=("Arial", 10)).grid(
row=row, column=0, padx=10, pady=5, sticky="w")
tk.Entry(main_frame, textvariable=self.folder_path, width=60,
font=("Arial", 9)).grid(row=row, column=1, padx=5, pady=5)
tk.Button(main_frame, text="Избери", command=self.select_folder,
bg=button_color, fg="white", font=("Arial", 9),
relief=tk.FLAT, padx=10).grid(row=row, column=2, padx=5, pady=5)
row += 1
# Качество на аудио
tk.Label(main_frame, text="🎵 Качество на аудиото:",
bg=bg_color, font=("Arial", 10)).grid(
row=row, column=0, padx=10, pady=5, sticky="w")
audio_quality_combo = ttk.Combobox(main_frame, textvariable=self.audio_quality,
values=["best", "320k", "256k", "128k"],
state="readonly", width=57, font=("Arial", 9))
audio_quality_combo.grid(row=row, column=1, padx=5, pady=5, columnspan=2, sticky="w")
row += 1
# Качество на видео
tk.Label(main_frame, text="📹 Качество на видеото:",
bg=bg_color, font=("Arial", 10)).grid(
row=row, column=0, padx=10, pady=5, sticky="w")
video_quality_combo = ttk.Combobox(main_frame, textvariable=self.video_quality,
values=["best", "1080p", "720p", "480p"],
state="readonly", width=57, font=("Arial", 9))
video_quality_combo.grid(row=row, column=1, padx=5, pady=5, columnspan=2, sticky="w")
row += 1
# Прогрес бар
tk.Label(main_frame, text="Прогрес:", bg=bg_color, font=("Arial", 10)).grid(
row=row, column=0, padx=10, pady=(20, 5), sticky="w")
self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var,
length=500, mode='determinate')
self.progress_bar.grid(row=row, column=1, columnspan=2, padx=5, pady=(20, 5), sticky="ew")
row += 1
# Статус
self.status_label = tk.Label(main_frame, textvariable=self.status_var,
bg=bg_color, font=("Arial", 9), fg="#666")
self.status_label.grid(row=row, column=0, columnspan=3, pady=5)
row += 1
# Бутони за сваляне
button_frame = tk.Frame(main_frame, bg=bg_color)
button_frame.grid(row=row, column=0, columnspan=3, pady=20)
tk.Button(button_frame, text="🎧 Свали от Spotify",
command=self.download_spotify_playlist,
bg=success_color, fg="white", font=("Arial", 10, "bold"),
relief=tk.FLAT, padx=15, pady=8, width=22).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="🎵 Свали MP3 от YouTube",
command=self.download_from_youtube,
bg=info_color, fg="white", font=("Arial", 10, "bold"),
relief=tk.FLAT, padx=15, pady=8, width=22).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="📹 Свали YouTube видео",
command=self.download_youtube_video,
bg=warning_color, fg="white", font=("Arial", 10, "bold"),
relief=tk.FLAT, padx=15, pady=8, width=22).pack(side=tk.LEFT, padx=5)
row += 1
# Линк към сайт
link_button = tk.Button(main_frame, text="🌐 Посетете сайта на приложението",
command=self.open_link, bg=bg_color, fg=button_color,
font=("Arial", 9, "underline"), relief=tk.FLAT,
cursor="hand2")
link_button.grid(row=row, column=0, columnspan=3, pady=10)
def select_playlist_file(self):
filename = filedialog.askopenfilename(
title="Изберете текстов файл",
filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
)
if filename:
self.playlist_file_path.set(filename)
def select_folder(self):
folder = filedialog.askdirectory(title="Изберете папка за запазване")
if folder:
self.folder_path.set(folder)
def paste_from_clipboard(self, var):
try:
clipboard_content = self.root.clipboard_get()
var.set(clipboard_content)
except tk.TkError:
messagebox.showwarning("Предупреждение", "Няма данни в клипборда")
def validate_url(self, url, platform):
if platform == "spotify":
return "spotify.com" in url and "playlist" in url
elif platform == "youtube":
return any(domain in url for domain in ["youtube.com", "youtu.be"])
return False
def check_dependencies(self):
"""Проверява дали са инсталирани необходимите инструменти"""
missing = []
# Проверка за spotdl
try:
result = subprocess.run(['spotdl', '--version'],
capture_output=True, text=True, timeout=5)
if result.returncode != 0:
missing.append("spotdl")
except (subprocess.TimeoutExpired, FileNotFoundError):
missing.append("spotdl")
# Проверка за yt-dlp
try:
result = subprocess.run(['yt-dlp', '--version'],
capture_output=True, text=True, timeout=5)
if result.returncode != 0:
missing.append("yt-dlp")
except (subprocess.TimeoutExpired, FileNotFoundError):
missing.append("yt-dlp")
if missing:
messagebox.showerror(
"Грешка",
f"Липсват необходимите инструменти: {', '.join(missing)}\n\n"
f"Моля инсталирайте ги с:\n"
f"pip install spotdl yt-dlp"
)
return False
return True
def run_download(self, func, *args):
"""Стартира сваляне в отделен thread"""
if not self.check_dependencies():
return
thread = threading.Thread(target=func, args=args)
thread.daemon = True
thread.start()
def update_progress(self, value, status):
"""Обновява прогрес бара и статуса"""
self.progress_var.set(value)
self.status_var.set(status)
self.root.update_idletasks()
def download_spotify_playlist(self):
self.run_download(self._download_spotify_playlist)
def _download_spotify_playlist(self):
playlist_link = self.spotify_link.get().strip()
output_folder = self.folder_path.get().strip()
if not playlist_link:
messagebox.showerror("Грешка", "Моля, въведете линк към Spotify плейлист.")
return
if not self.validate_url(playlist_link, "spotify"):
messagebox.showerror("Грешка", "Невалиден Spotify плейлист линк.")
return
if not output_folder:
messagebox.showerror("Грешка", "Моля, изберете папка за запазване.")
return
try:
self.update_progress(10, "Започва сваляне от Spotify...")
# Създаване на папката ако не съществува
Path(output_folder).mkdir(parents=True, exist_ok=True)
# Команда за spotdl
quality_param = f"--audio-bitrate {self.audio_quality.get()}" if self.audio_quality.get() != "best" else ""
cmd = f'spotdl "{playlist_link}" --output "{output_folder}" {quality_param}'
self.update_progress(30, "Сваляне в процес...")
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, text=True, cwd=output_folder)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
self.update_progress(50, f"Сваляне: {output.strip()[:50]}...")
if process.returncode == 0:
self.update_progress(100, "Плейлистът е успешно свален!")
messagebox.showinfo("Успех", "Плейлистът беше успешно свален.")
else:
raise Exception("Грешка при изпълнение на spotdl")
except Exception as e:
self.update_progress(0, "Грешка при свалянето")
messagebox.showerror("Грешка", f"Грешка при свалянето: {str(e)}")
def download_from_youtube(self):
self.run_download(self._download_from_youtube)
def _download_from_youtube(self):
playlist_file = self.playlist_file_path.get().strip()
output_folder = self.folder_path.get().strip()
if not playlist_file:
messagebox.showerror("Грешка", "Моля, изберете текстов файл с имената на песните.")
return
if not output_folder:
messagebox.showerror("Грешка", "Моля, изберете папка за запазване.")
return
try:
self.update_progress(5, "Четене на файла с песни...")
with open(playlist_file, 'r', encoding='utf-8') as file:
songs = [line.strip() for line in file.readlines() if line.strip()]
if not songs:
messagebox.showerror("Грешка", "Файлът е празен или не съдържа валидни песни.")
return
Path(output_folder).mkdir(parents=True, exist_ok=True)
total_songs = len(songs)
for i, song in enumerate(songs):
progress = (i / total_songs) * 90 + 10
self.update_progress(progress, f"Сваляне: {song[:40]}...")
# Качество на аудиото
quality_param = ""
if self.audio_quality.get() != "best":
quality_param = f"--audio-quality {self.audio_quality.get()}"
cmd = f'yt-dlp --extract-audio --audio-format mp3 {quality_param} -o "%(title)s.%(ext)s" "ytsearch:{song}"'
result = subprocess.run(cmd, shell=True, cwd=output_folder,
capture_output=True, text=True)
if result.returncode != 0:
print(f"Грешка при {song}: {result.stderr}")
self.update_progress(100, f"Свалени {total_songs} песни!")
messagebox.showinfo("Успех", f"Всички {total_songs} песни бяха успешно свалени.")
except Exception as e:
self.update_progress(0, "Грешка при свалянето")
messagebox.showerror("Грешка", f"Грешка при свалянето: {str(e)}")
def download_youtube_video(self):
self.run_download(self._download_youtube_video)
def _download_youtube_video(self):
video_link = self.youtube_video_link.get().strip()
output_folder = self.folder_path.get().strip()
if not video_link:
messagebox.showerror("Грешка", "Моля, въведете линк към YouTube видеото.")
return
if not self.validate_url(video_link, "youtube"):
messagebox.showerror("Грешка", "Невалиден YouTube линк.")
return
if not output_folder:
messagebox.showerror("Грешка", "Моля, изберете папка за запазване.")
return
try:
self.update_progress(10, "Започва сваляне на видеото...")
Path(output_folder).mkdir(parents=True, exist_ok=True)
# Качество на видеото
format_param = "best"
if self.video_quality.get() != "best":
format_param = f"best[height<={self.video_quality.get()[:-1]}]"
cmd = f'yt-dlp -f "{format_param}" -o "%(title)s.%(ext)s" "{video_link}"'
self.update_progress(30, "Сваляне в процес...")
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, text=True, cwd=output_folder)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
self.update_progress(50, f"Сваляне: {output.strip()[:50]}...")
if process.returncode == 0:
self.update_progress(100, "Видеото е успешно свалено!")
messagebox.showinfo("Успех", "Видеото беше успешно свалено.")
else:
raise Exception("Грешка при изпълнение на yt-dlp")
except Exception as e:
self.update_progress(0, "Грешка при свалянето")
messagebox.showerror("Грешка", f"Грешка при свалянето: {str(e)}")
def open_link(self):
webbrowser.open("https://urocibg.eu/%f0%9f%8e%b5-spotify-%d0%b8-youtube-playlist-downloader/")
def run(self):
self.root.mainloop()
if __name__ == "__main__":
app = PlaylistDownloader()
app.run()
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() |