Closed jadensherriff closed 3 months ago
Updated the code and it works now. Also updated it to download past the 100-song limit and skip downloaded songs, changed the library to yt-dlp also:
import os import urllib.parse import urllib.request import requests import re import string from tkinter import Tk, ttk, filedialog, StringVar, Label import yt_dlp as ytdlp from dotenv import load_dotenv import spotipy from spotipy.oauth2 import SpotifyOAuth from mutagen.mp3 import MP3 from mutagen.id3 import ID3, TIT2, TPE1, TALB, TDRC, TRCK, TCON, COMM, TPE2, TCOM, TPOS
load_dotenv(dotenv_path='.env')
client_id = os.getenv("CLIENT_ID") client_secret = os.getenv("CLIENT_SECRET") redirect_uri = os.getenv("REDIRECT_URL")
sp = spotipy.Spotify(auth_manager=SpotifyOAuth( client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, scope="user-library-read playlist-read-private playlist-read-collaborative" ))
playlists = {} is_downloading = True status_label = None path_label = None
def sanitize_filename(filename): validchars = "-.() %s%s" % (string.ascii_letters, string.digits) return ''.join(c for c in filename if c in valid_chars)
def update_playlist_dropdown(): playlist_names = list(playlists.keys()) playlist_menu = playlist_dropdown["menu"] playlist_menu.delete(0, "end") for name in playlist_names: playlist_menu.add_command(label=name, command=lambda value=name: selected_playlist.set(value)) if playlist_names: selected_playlist.set(playlist_names[0])
def get_user_playlists(): global playlists print("Retrieving user playlists...") results = sp.current_user_playlists() for item in results['items']: playlists[item['name']] = item['id'] print("Playlists retrieved successfully.") update_playlist_dropdown()
def stop_downloading(): global is_downloading is_downloading = False if status_label: status_label.config(text="Downloading stopped.")
def get_playlist_tracks(playlist_id): print(f"Retrieving tracks for playlist ID: {playlist_id}") tracks = [] offset = 0 limit = 100 # Spotify API limit per request
while True:
results = sp.playlist_tracks(playlist_id, offset=offset, limit=limit)
if len(results['items']) == 0:
break
for item in results['items']:
track = item['track']
if track is not None: # Check if track exists (some might be None due to availability issues)
album = track['album']
artists = [artist['name'] for artist in track['artists']]
track_info = {
"name": track['name'],
"artists": artists,
"album": album['name'],
"album_artist": album['artists'][0]['name'],
"release_date": album['release_date'],
"track_number": track['track_number'],
"disc_number": track['disc_number'],
"total_tracks": album['total_tracks'],
}
tracks.append(track_info)
offset += limit
if len(results['items']) < limit:
break
print(f"Retrieved {len(tracks)} tracks successfully.")
return tracks
def add_metadata(file_path, track_info): audio = MP3(file_path, ID3=ID3)
# Remove existing ID3 tags
audio.delete()
audio.save()
# Add new ID3 tags
audio = ID3(file_path)
audio.add(TIT2(encoding=3, text=track_info["name"]))
audio.add(TPE1(encoding=3, text=track_info["artists"]))
audio.add(TALB(encoding=3, text=track_info["album"]))
audio.add(TDRC(encoding=3, text=track_info["release_date"]))
audio.add(TRCK(encoding=3, text=f"{track_info['track_number']}/{track_info['total_tracks']}"))
audio.add(TPOS(encoding=3, text=str(track_info["disc_number"])))
audio.add(TPE2(encoding=3, text=track_info["album_artist"]))
audio.save()
def download_songs(selected_playlist): global is_downloading, status_label, path_label is_downloading = True if status_label: status_label.config(text="Downloading...")
user_path = path_label.cget("text") if path_label else ""
download_folder = os.path.join(user_path, sanitize_filename(selected_playlist).replace(" ", "_"))
if not os.path.exists(download_folder):
os.makedirs(download_folder)
playlist_id = playlists[selected_playlist]
tracks = get_playlist_tracks(playlist_id)
total_tracks = len(tracks)
downloaded_tracks = 0
skipped_tracks = 0
for index, track in enumerate(tracks, 1):
if not is_downloading:
print("Downloading stopped by user.")
break
sanitized_track_name = sanitize_filename(f"{track['artists'][0]} - {track['name']}")
final_file = os.path.join(download_folder, f"{sanitized_track_name}.mp3")
# Check if the file already exists
if os.path.exists(final_file):
print(f"Skipping, already downloaded: {final_file}")
skipped_tracks += 1
continue
try:
print(f"Processing {index}/{total_tracks}: {track['name']} by {', '.join(track['artists'])}...")
search_query = urllib.parse.quote(f"{track['name']} {' '.join(track['artists'])} lyrics")
html = urllib.request.urlopen(f"https://www.youtube.com/results?search_query={search_query}")
video_ids = re.findall(r"watch\?v=(\S{11})", html.read().decode())
for video_id in video_ids:
try:
url = f"https://youtube.com/watch?v={video_id}"
# Get video info
with ytdlp.YoutubeDL({'quiet': True}) as ydl:
video_info = ydl.extract_info(url, download=False)
# Check if it's likely a lyric video
title = video_info['title'].lower()
description = video_info.get('description', '').lower()
if ('lyric' in title or 'lyrics' in title or
'lyric' in description or 'lyrics' in description):
ydl_opts = {
'format': 'bestaudio/best',
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192',
}],
'outtmpl': os.path.join(download_folder, 'temp.%(ext)s'),
'noplaylist': True
}
with ytdlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([url])
temp_file = os.path.join(download_folder, 'temp.mp3')
if os.path.exists(temp_file):
os.rename(temp_file, final_file)
add_metadata(final_file, track)
downloaded_tracks += 1
print(f"Downloaded and tagged successfully: {final_file}")
break
else:
print(f"Temporary file not found: {temp_file}")
else:
print(f"Skipping non-lyric video: {title}")
continue
except Exception as e:
print(f"Error downloading video: {e}")
continue
else:
print(f"No suitable lyric video found for {track['name']}")
except Exception as e:
print(f"Error processing track {track['name']}: {e}")
if status_label:
status_label.config(text=f"Downloaded: {downloaded_tracks}, Skipped: {skipped_tracks}, Total: {total_tracks}")
if status_label:
status_label.config(text=f"Completed. Downloaded: {downloaded_tracks}, Skipped: {skipped_tracks}, Total: {total_tracks}")
print(f"Download completed. Downloaded: {downloaded_tracks}, Skipped: {skipped_tracks}, Total: {total_tracks}")
def select_path(): global path_label path = filedialog.askdirectory() if path_label: path_label.config(text=path)
screen = Tk() screen.title('Spotify Downloader') screen.geometry("600x400")
style = ttk.Style(screen) style.theme_use('clam')
frame = ttk.Frame(screen, padding="10") frame.pack(fill='both', expand=True)
path_label = Label(frame, text="Select Download Path:") path_label.pack() select_path_button = ttk.Button(frame, text="Browse", command=select_path) select_path_button.pack()
selected_playlist = StringVar() playlist_dropdown = ttk.OptionMenu(frame, selected_playlist, "Loading playlists...") playlist_dropdown.pack()
get_user_playlists()
download_button = ttk.Button(frame, text="Download", command=lambda: download_songs(selected_playlist.get())) download_button.pack()
stop_button = ttk.Button(frame, text="Stop Downloading", command=stop_downloading) stop_button.pack()
status_label = Label(frame, text="") status_label.pack()
screen.mainloop()
It's not working for me. What version of yt_dlp did you use?
Hey there, I'm getting this response when attempting to download a playlist. Prior to that, it was showing a certification issue, updated the 'Install Certification' code in python, and now I'm running into this. Any ideas?
Thank you