Noko7 / Spotify-Downloader

Download any playlist from your Spotify account as a .MP3 file using python and youtube.
MIT License
7 stars 4 forks source link

Error downloading video: get_throttling_function_name: could not find match for multiple #4

Closed jadensherriff closed 1 month ago

jadensherriff commented 1 month ago

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

Screenshot 2024-08-01 at 2 09 44 AM
jadensherriff commented 1 month 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 environment variables

load_dotenv(dotenv_path='.env')

Setup Spotify API credentials

client_id = os.getenv("CLIENT_ID") client_secret = os.getenv("CLIENT_SECRET") redirect_uri = os.getenv("REDIRECT_URL")

Create Spotify client

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)

GUI setup

screen = Tk() screen.title('Spotify Downloader') screen.geometry("600x400")

Styling

style = ttk.Style(screen) style.theme_use('clam')

Layout

frame = ttk.Frame(screen, padding="10") frame.pack(fill='both', expand=True)

Path selection

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

Playlist dropdown

selected_playlist = StringVar() playlist_dropdown = ttk.OptionMenu(frame, selected_playlist, "Loading playlists...") playlist_dropdown.pack()

Fetch playlists and update dropdown

get_user_playlists()

Download button

download_button = ttk.Button(frame, text="Download", command=lambda: download_songs(selected_playlist.get())) download_button.pack()

Stop Downloading button

stop_button = ttk.Button(frame, text="Stop Downloading", command=stop_downloading) stop_button.pack()

Status label

status_label = Label(frame, text="") status_label.pack()

Start GUI

screen.mainloop()

Noko7 commented 1 month ago

It's not working for me. What version of yt_dlp did you use?