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() |