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