From 03f6375679e80fdd071a0e06719629773fe82a9d Mon Sep 17 00:00:00 2001 From: Haim Daniel Date: Sat, 14 Sep 2024 15:08:42 +0300 Subject: [PATCH 1/6] Update pip install instructions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5f74908..03def06 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ We will be installing dependencies using `pip`, the official Python package mana Copy and paste (and run) the following line in your terminal session to install all necessary packages. ```bash -pip3 install spotipy && pip3 install youtube_dl && pip3 install youtube_search && pip3 install yt_dlp && pip3 install ffprobe && pip3 install ffmpeg +pip install spotipy youtube_dl youtube_search yt_dlp ffprobe ffmpeg ``` ### 3. Setting up Spotify @@ -53,7 +53,7 @@ Alternatively, you can find the URI as follows: 4. Click "Show Code" 5. The URI is the code between "https://open.spotify.com/embed/playlist/" and the first "?" -For example in this code snippet: +For example, in this code snippet: ```html From 93cb66d5bde510e47276e91c5d9741b831823165 Mon Sep 17 00:00:00 2001 From: Haim Daniel Date: Sat, 14 Sep 2024 15:29:19 +0300 Subject: [PATCH 2/6] Update pip install instructions + add setup.py --- README.md | 6 +++--- setup.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 setup.py diff --git a/README.md b/README.md index 03def06..3ec4d23 100644 --- a/README.md +++ b/README.md @@ -22,17 +22,17 @@ Open a terminal session and navigate to this folder, using `cd`. cd spotify-to-mp3-python/ ``` -### 2. Installing dependencies +### 2. Install dependencies We will be installing dependencies using `pip`, the official Python package manager. If you do not have `pip`, I'd recommend checking this [thread](https://stackoverflow.com/questions/6587507/how-to-install-pip-with-python-3/) to install it. Copy and paste (and run) the following line in your terminal session to install all necessary packages. ```bash -pip install spotipy youtube_dl youtube_search yt_dlp ffprobe ffmpeg +pip install setuptools -e . ``` -### 3. Setting up Spotify +### 3. Set up Spotify Unfortunately, I could not find a workaround for this step - it seems like we're forced to go through the Spotify API to fetch information about playlists. But, it doesn't take long at all. diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..dd90eb7 --- /dev/null +++ b/setup.py @@ -0,0 +1,23 @@ +from setuptools import setup, find_packages + +setup( + name='spotify_to_mp3', + version='1.0.0', + description='A tool to download Spotify playlists as MP3 files', + author='Haim Daniel', + packages=find_packages(), + install_requires=[ + 'spotipy', + 'configparser', + 'youtube_dl', + 'youtube_search', + 'yt_dlp', + 'ffprobe', + 'ffmpeg', + ], + entry_points={ + 'console_scripts': [ + 'spotify_to_mp3=spotify_to_mp3:main', + ], + }, +) \ No newline at end of file From 6949472bd95a20907079479ba1798232befdbc5e Mon Sep 17 00:00:00 2001 From: Haim Daniel Date: Sat, 14 Sep 2024 15:36:30 +0300 Subject: [PATCH 3/6] Add console script + change instructions --- README.md | 2 +- setup.py | 2 +- spotify_to_mp3/__init__.py | 0 .../spotify_to_mp3.py | 130 ++++++++++++------ 4 files changed, 90 insertions(+), 44 deletions(-) create mode 100644 spotify_to_mp3/__init__.py rename spotify_to_mp3.py => spotify_to_mp3/spotify_to_mp3.py (73%) diff --git a/README.md b/README.md index 3ec4d23..f4a048f 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ The code is 11cPCycyvvpL0MDLO648vE Running this script is straightforward. Simply run in your terminal session: ```bash -python3 spotify_to_mp3.py +spotify_to_mp3 ``` If you run into an error saying something like "ffprobe or avprobe not found", check out this [solution](https://stackoverflow.com/questions/30770155/ffprobe-or-avprobe-not-found-please-install-one). diff --git a/setup.py b/setup.py index dd90eb7..de8b68d 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ ], entry_points={ 'console_scripts': [ - 'spotify_to_mp3=spotify_to_mp3:main', + 'spotify_to_mp3=spotify_to_mp3.spotify_to_mp3:main', ], }, ) \ No newline at end of file diff --git a/spotify_to_mp3/__init__.py b/spotify_to_mp3/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/spotify_to_mp3.py b/spotify_to_mp3/spotify_to_mp3.py similarity index 73% rename from spotify_to_mp3.py rename to spotify_to_mp3/spotify_to_mp3.py index 2026153..839ccbe 100644 --- a/spotify_to_mp3.py +++ b/spotify_to_mp3/spotify_to_mp3.py @@ -11,9 +11,11 @@ from mutagen.mp3 import MP3 from mutagen.id3 import ID3, APIC, error + # **************PLEASE READ THE README.md FOR USE INSTRUCTIONS**************n def write_tracks(text_file: str, tracks: dict): - # This includins the name, artist, and spotify URL. Each is delimited by a comma. + # This includes the name, artist, and spotify URL. + # A comma delimits each field and a newline delimiting each track. with open(text_file, 'w+', encoding='utf-8') as file_out: while True: for item in tracks['items']: @@ -26,15 +28,33 @@ def write_tracks(text_file: str, tracks: dict): track_name = track['name'] track_artist = track['artists'][0]['name'] album_art_url = track['album']['images'][0]['url'] - csv_line = track_name + "," + track_artist + "," + track_url + "," + album_art_url + "\n" + csv_line = ( + track_name + + "," + + track_artist + + "," + + track_url + + "," + + album_art_url + + "\n" + ) try: file_out.write(csv_line) - except UnicodeEncodeError: # Most likely caused by non-English song names - print("Track named {} failed due to an encoding error. This is \ - most likely due to this song having a non-English name.".format(track_name)) + except ( + UnicodeEncodeError + ): # Most likely caused by non-English song names + print( + "Track named {} failed due to an encoding error. This is \ + most likely due to this song having a non-English name.".format( + track_name + ) + ) except KeyError: - print(u'Skipping track {0} by {1} (local only?)'.format( - track['name'], track['artists'][0]['name'])) + print( + u'Skipping track {0} by {1} (local only?)'.format( + track['name'], track['artists'][0]['name'] + ) + ) # 1 page = 50 results, check if there are more pages if tracks['next']: tracks = spotify.next(tracks) @@ -50,35 +70,45 @@ def write_playlist(username: str, playlist_id: str): tracks = results['tracks'] write_tracks(text_file, tracks) - imgURLs = []; + imgURLs = [] for item in tracks['items']: - imgURLs.append(item['track']['album']['images'][0]['url']); + imgURLs.append(item['track']['album']['images'][0]['url']) return playlist_name, imgURLs + def find_and_download_songs(reference_file: str): - TOTAL_ATTEMPTS = 10 + total_attempts = 10 with open(reference_file, "r", encoding='utf-8') as file: for line in file: temp = line.split(",") name, artist, album_art_url = temp[0], temp[1], temp[3] text_to_search = artist + " - " + name best_url = None - attempts_left = TOTAL_ATTEMPTS + attempts_left = total_attempts while attempts_left > 0: try: - results_list = YoutubeSearch(text_to_search, max_results=1).to_dict() - best_url = "https://www.youtube.com{}".format(results_list[0]['url_suffix']) + results_list = YoutubeSearch( + text_to_search, max_results=1 + ).to_dict() + best_url = "https://www.youtube.com{}".format( + results_list[0]['url_suffix'] + ) break except IndexError: attempts_left -= 1 - print("No valid URLs found for {}, trying again ({} attempts left).".format( - text_to_search, attempts_left)) + print( + "No valid URLs found for {}, trying again ({} attempts left).".format( + text_to_search, attempts_left + ) + ) if best_url is None: - print("No valid URLs found for {}, skipping track.".format(text_to_search)) + print( + "No valid URLs found for {}, skipping track.".format(text_to_search) + ) continue print("Initiating download for Image {}.".format(album_art_url)) - f = open('{}.jpg'.format(name),'wb') + f = open('{}.jpg'.format(name), 'wb') f.write(urllib.request.urlopen(album_art_url).read()) f.close() @@ -86,15 +116,18 @@ def find_and_download_songs(reference_file: str): print("Initiating download for {}.".format(text_to_search)) ydl_opts = { 'format': 'bestaudio/best', - 'outtmpl':'%(title)s', #name the file the ID of the video + 'outtmpl': '%(title)s', # name the file the ID of the video 'embedthumbnail': True, - 'postprocessors': [{ - 'key': 'FFmpegExtractAudio', - 'preferredcodec': 'mp3', - 'preferredquality': '192', - }, { - 'key': 'FFmpegMetadata', - }] + 'postprocessors': [ + { + 'key': 'FFmpegExtractAudio', + 'preferredcodec': 'mp3', + 'preferredquality': '192', + }, + { + 'key': 'FFmpegMetadata', + }, + ], } with yt_dlp.YoutubeDL(ydl_opts) as ydl: info_dict = ydl.extract_info([best_url][0], download=True) @@ -116,20 +149,19 @@ def find_and_download_songs(reference_file: str): mime="image/jpeg", # can be image/jpeg or image/png type=3, # 3 is for the cover image desc='Cover', - data=open("{}.jpg".format(name), mode='rb').read() + data=open("{}.jpg".format(name), mode='rb').read(), ) ) audio.save() os.remove("{}.jpg".format(name)) - - -# Multiprocessed implementation of find_and_download_songs -# This method is responsible for manging and distributing the multi-core workload def multicore_find_and_download_songs(reference_file: str, cpu_count: int): - # Extract songs from the reference file + """Extract songs from the reference file. + Multiprocessed implementation of find_and_download_songs + This method is responsible for managing and distributing the multicore workload + """ lines = [] with open(reference_file, "r", encoding='utf-8') as file: for line in file: @@ -167,7 +199,9 @@ def multicore_find_and_download_songs(reference_file: str, cpu_count: int): processes = [] segment_index = 0 for segment in file_segments: - p = multiprocessing.Process(target = multicore_handler, args=(segment, segment_index)) + p = multiprocessing.Process( + target=multicore_handler, args=(segment, segment_index) + ) processes.append(p) segment_index = segment_index + 1 @@ -179,9 +213,13 @@ def multicore_find_and_download_songs(reference_file: str, cpu_count: int): for p in processes: p.join() -# Just a wrapper around the original find_and_download_songs method to ensure future compatibility -# Preserves the same functionality just allows for several shorter lists to be used and cleaned up + def multicore_handler(reference_list: list, segment_index: int): + """A wrapper around the original find_and_download_songs method + + Ensures future compatibility, and reserves the same functionality. + Just allows for several shorter lists to be used and cleaned up + """ # Create reference filename based off of the process id (segment_index) reference_filename = "{}.txt".format(segment_index) @@ -194,7 +232,7 @@ def multicore_handler(reference_list: list, segment_index: int): find_and_download_songs(reference_filename) # Clean up the extra list that was generated - if(os.path.exists(reference_filename)): + if os.path.exists(reference_filename): os.remove(reference_filename) @@ -207,29 +245,31 @@ def enable_multicore(autoenable=False, maxcores=None, buffercores=1): native_cpu_count = multiprocessing.cpu_count() - buffercores if autoenable: if maxcores: - if(maxcores <= native_cpu_count): + if maxcores <= native_cpu_count: return maxcores else: print("Too many cores requested, single core operation fallback") return 1 return multiprocessing.cpu_count() - 1 multicore_query = input("Enable multiprocessing (Y or N): ") - if multicore_query not in ["Y","y","Yes","YES","YEs",'yes']: + if multicore_query not in ["Y", "y", "Yes", "YES", "YEs", 'yes']: return 1 core_count_query = int(input("Max core count (0 for allcores): ")) - if(core_count_query == 0): + if core_count_query == 0: return native_cpu_count - if(core_count_query <= native_cpu_count): + if core_count_query <= native_cpu_count: return core_count_query else: print("Too many cores requested, single core operation fallback") return 1 -if __name__ == "__main__": + +def main(): # Parameters print("Please read README.md for use instructions.") if os.path.isfile('config.ini'): import configparser + config = configparser.ConfigParser() config.read("config.ini") client_id = config["Settings"]["client_id"] @@ -243,7 +283,9 @@ def enable_multicore(autoenable=False, maxcores=None, buffercores=1): if playlist_uri.find("https://open.spotify.com/playlist/") != -1: playlist_uri = playlist_uri.replace("https://open.spotify.com/playlist/", "") multicore_support = enable_multicore(autoenable=False, maxcores=None, buffercores=1) - auth_manager = oauth2.SpotifyClientCredentials(client_id=client_id, client_secret=client_secret) + auth_manager = oauth2.SpotifyClientCredentials( + client_id=client_id, client_secret=client_secret + ) spotify = spotipy.Spotify(auth_manager=auth_manager) playlist_name, albumArtUrls = write_playlist(username, playlist_uri) reference_file = "{}.txt".format(playlist_name) @@ -258,4 +300,8 @@ def enable_multicore(autoenable=False, maxcores=None, buffercores=1): else: find_and_download_songs(reference_file) os.remove(f'{reference_file}') - print("Operation complete.") \ No newline at end of file + print("Operation complete.") + + +if __name__ == "__main__": + main() From 76e19b8b8606959263eab9324c0cf1210eecba2e Mon Sep 17 00:00:00 2001 From: Haim Daniel Date: Sat, 14 Sep 2024 18:26:12 +0300 Subject: [PATCH 4/6] Multiple refactor + fixes --- README.md | 35 ++++++++-- spotify_to_mp3/spotify_to_mp3.py | 112 +++++++++++++------------------ 2 files changed, 74 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index f4a048f..33efcb5 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,35 @@ Copy and paste (and run) the following line in your terminal session to install pip install setuptools -e . ``` +### 2.1 Install ffmpeg +You will be needing `ffmpeg` to convert the downloaded audio files to MP3. +You can download it: + +*Ubuntu and Debian: +`sudo apt-get install ffmpeg` + +*macOS: `brew install ffmpeg +* Windows: `choco install ffmpeg` + + + + ### 3. Set up Spotify Unfortunately, I could not find a workaround for this step - it seems like we're forced to go through the Spotify API to fetch information about playlists. But, it doesn't take long at all. -Go to the Spotify [dashboard](https://developer.spotify.com/dashboard/). Log in. Once at the Dashboard, click the green button labeled "Create App". Don't worry - you're not signing up for anything or commiting to something from Spotify. Here, **it really doesn't matter what you put** for "App name" and "App description". For me, I just put "Testing" for both. Make sure to check both agreement boxes and click "Create". +Go to the Spotify [dashboard](https://developer.spotify.com/dashboard/). Log in. Once at the Dashboard, click the green button labeled "Create App". Don't worry - you're not signing up for anything or commiting to something from Spotify. + +Here **it really doesn't matter what you put** for "App name" and "App description." +I just put "Testing" for both. + +The next section is "Redirect URIs", which is a bit more important. +You can put anything here, but I'd recommend putting `http://localhost:8888/callback`. + +This is because the script uses the `spotipy` library, which uses the `localhost` URL to authenticate with Spotify. + +Make sure to check "I understand and agree with Spotify's Developer Terms of Service and Design Guidelines" and click "Create". + You should see this: @@ -75,12 +99,11 @@ If all goes well, you should see your playlist beginning to download in a folder ## Modifications -If you don't like inputting your Client ID, Client Secret, Username, and URI every time, you can edit lines 96-99 in `spotify_to_mp3.py` to set the respective variables into a string containing your credentials instead of prompting with `input()`. For example, line 98 would become +If you don't like inputting your Client ID and Client Secret every time, you can edit `config.ini` to set the respective variables. -```python -username = "YourUserName" -``` ## Debugging -This script was made in the better part of an afternoon and so it's not, by far, bug-free. Personally, I've run into no problems using this script on any of my playlists, however, your mileage may vary. The most promenant bug I've run into involves the `youtube-search` package not consistantly turning up results, and most of the time, the best solution is to simply try running the script again and giving it more chances to get the search right. +This script was made in the better part of an afternoon, and so it's not by far bug-free. + +Personally, I've run into no problems using this script on any of my playlists, however, your mileage may vary. The most promenant bug I've run into involves the `youtube-search` package not consistantly turning up results, and most of the time, the best solution is to simply try running the script again and giving it more chances to get the search right. diff --git a/spotify_to_mp3/spotify_to_mp3.py b/spotify_to_mp3/spotify_to_mp3.py index 839ccbe..56b7130 100644 --- a/spotify_to_mp3/spotify_to_mp3.py +++ b/spotify_to_mp3/spotify_to_mp3.py @@ -1,19 +1,21 @@ # Downloads a Spotify playlist into a folder of MP3 tracks # Jason Chen, 21 June 2020 +import multiprocessing import os +import pathlib +import urllib.request + import spotipy -import spotipy.oauth2 as oauth2 import yt_dlp -from youtube_search import YoutubeSearch -import multiprocessing -import urllib.request -from mutagen.mp3 import MP3 from mutagen.id3 import ID3, APIC, error +from mutagen.mp3 import MP3 +from spotipy.oauth2 import SpotifyClientCredentials +from youtube_search import YoutubeSearch -# **************PLEASE READ THE README.md FOR USE INSTRUCTIONS**************n -def write_tracks(text_file: str, tracks: dict): +# **************PLEASE READ THE README.md FOR USE INSTRUCTIONS************** +def write_tracks(client, text_file: str, tracks: dict): # This includes the name, artist, and spotify URL. # A comma delimits each field and a newline delimiting each track. with open(text_file, 'w+', encoding='utf-8') as file_out: @@ -26,7 +28,7 @@ def write_tracks(text_file: str, tracks: dict): try: track_url = track['external_urls']['spotify'] track_name = track['name'] - track_artist = track['artists'][0]['name'] + track_artist = track['artists'][0]['name'] or 'Unknown Artist' album_art_url = track['album']['images'][0]['url'] csv_line = ( track_name @@ -57,23 +59,29 @@ def write_tracks(text_file: str, tracks: dict): ) # 1 page = 50 results, check if there are more pages if tracks['next']: - tracks = spotify.next(tracks) + tracks = client.next(tracks) else: break -def write_playlist(username: str, playlist_id: str): - results = spotify.user_playlist(username, playlist_id, fields='tracks,next,name') +def get_playlist_id(playlist_uri: str) -> str: + assert playlist_uri.startswith("https://open.spotify.com/playlist/") + return playlist_uri.split('https://open.spotify.com/playlist/')[1].split('?')[0] + + +def write_playlist(client, playlist_uri: str): + playlist_id = get_playlist_id(playlist_uri) + results = client.playlist(playlist_id, fields='tracks,next,name') playlist_name = results['name'] text_file = u'{0}.txt'.format(playlist_name, ok='-_()[]{}') print(u'Writing {0} tracks to {1}.'.format(results['tracks']['total'], text_file)) tracks = results['tracks'] - write_tracks(text_file, tracks) + write_tracks(client, text_file, tracks) - imgURLs = [] + img_urls = [] for item in tracks['items']: - imgURLs.append(item['track']['album']['images'][0]['url']) - return playlist_name, imgURLs + img_urls.append(item['track']['album']['images'][0]['url']) + return playlist_name, img_urls def find_and_download_songs(reference_file: str): @@ -156,7 +164,7 @@ def find_and_download_songs(reference_file: str): os.remove("{}.jpg".format(name)) -def multicore_find_and_download_songs(reference_file: str, cpu_count: int): +def multicore_find_and_download_songs(reference_file: str, n_cores: int): """Extract songs from the reference file. Multiprocessed implementation of find_and_download_songs @@ -169,17 +177,17 @@ def multicore_find_and_download_songs(reference_file: str, cpu_count: int): # Process allocation of songs per cpu number_of_songs = len(lines) - songs_per_cpu = number_of_songs // cpu_count + songs_per_cpu = number_of_songs // n_cores - # Calculates number of songs that dont evenly fit into the cpu list + # Calculates number of songs that don't evenly fit into the cpu list # i.e. 4 cores and 5 songs, one core will have to process 1 extra song - extra_songs = number_of_songs - (cpu_count * songs_per_cpu) + extra_songs = number_of_songs - (n_cores * songs_per_cpu) # Create a list of number of songs which by index allocates it to a cpu # 4 core cpu and 5 songs [2, 1, 1, 1] where each item is the number of songs # Core 0^ 1^ 2^ 3^ cpu_count_list = [] - for cpu in range(cpu_count): + for cpu in range(n_cores): songs = songs_per_cpu if cpu < extra_songs: songs = songs + 1 @@ -236,69 +244,39 @@ def multicore_handler(reference_list: list, segment_index: int): os.remove(reference_filename) -# This is prompt to handle the multicore queries -# An effort has been made to create an easily automated interface -# Autoeneable: bool allows for no prompts and defaults to max core usage -# Maxcores: int allows for automation of set number of cores to be used -# Buffercores: int allows for an allocation of unused cores (default 1) -def enable_multicore(autoenable=False, maxcores=None, buffercores=1): - native_cpu_count = multiprocessing.cpu_count() - buffercores - if autoenable: - if maxcores: - if maxcores <= native_cpu_count: - return maxcores - else: - print("Too many cores requested, single core operation fallback") - return 1 - return multiprocessing.cpu_count() - 1 - multicore_query = input("Enable multiprocessing (Y or N): ") - if multicore_query not in ["Y", "y", "Yes", "YES", "YEs", 'yes']: - return 1 - core_count_query = int(input("Max core count (0 for allcores): ")) - if core_count_query == 0: - return native_cpu_count - if core_count_query <= native_cpu_count: - return core_count_query - else: - print("Too many cores requested, single core operation fallback") - return 1 - - -def main(): - # Parameters - print("Please read README.md for use instructions.") - if os.path.isfile('config.ini'): +def get_config(): + cur_dir = pathlib.Path(os.path.dirname(os.path.realpath(__file__))) + config_file = cur_dir.parent / "config.ini" + if config_file.exists(): import configparser config = configparser.ConfigParser() - config.read("config.ini") + config.read(config_file) client_id = config["Settings"]["client_id"] client_secret = config["Settings"]["client_secret"] - username = config["Settings"]["username"] else: client_id = input("Client ID: ") client_secret = input("Client secret: ") - username = input("Spotify username: ") + return client_id, client_secret + + +def main(): + print("Please read README.md for use instructions.") + client_id, client_secret = get_config() playlist_uri = input("Playlist URI/Link: ") - if playlist_uri.find("https://open.spotify.com/playlist/") != -1: - playlist_uri = playlist_uri.replace("https://open.spotify.com/playlist/", "") - multicore_support = enable_multicore(autoenable=False, maxcores=None, buffercores=1) - auth_manager = oauth2.SpotifyClientCredentials( + auth_manager = SpotifyClientCredentials( client_id=client_id, client_secret=client_secret ) - spotify = spotipy.Spotify(auth_manager=auth_manager) - playlist_name, albumArtUrls = write_playlist(username, playlist_uri) - reference_file = "{}.txt".format(playlist_name) + sp = spotipy.Spotify(auth_manager=auth_manager) + playlist_name, albumArtUrls = write_playlist(sp, playlist_uri) + reference_file = f"{playlist_name}.txt" # Create the playlist folder if not os.path.exists(playlist_name): os.makedirs(playlist_name) os.rename(reference_file, playlist_name + "/" + reference_file) os.chdir(playlist_name) - # Enable multicore support - if multicore_support > 1: - multicore_find_and_download_songs(reference_file, multicore_support) - else: - find_and_download_songs(reference_file) + n_cores = multiprocessing.cpu_count() + multicore_find_and_download_songs(reference_file, n_cores) os.remove(f'{reference_file}') print("Operation complete.") From e0363563b27fc7f27abab27e07d07aac853cdc02 Mon Sep 17 00:00:00 2001 From: Haim Daniel Date: Sat, 14 Sep 2024 18:27:55 +0300 Subject: [PATCH 5/6] cosmetic: cleanups --- spotify_to_mp3/spotify_to_mp3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spotify_to_mp3/spotify_to_mp3.py b/spotify_to_mp3/spotify_to_mp3.py index 56b7130..f7a2deb 100644 --- a/spotify_to_mp3/spotify_to_mp3.py +++ b/spotify_to_mp3/spotify_to_mp3.py @@ -263,12 +263,12 @@ def get_config(): def main(): print("Please read README.md for use instructions.") client_id, client_secret = get_config() - playlist_uri = input("Playlist URI/Link: ") + playlist_uri = input("Playlist URI: ") auth_manager = SpotifyClientCredentials( client_id=client_id, client_secret=client_secret ) sp = spotipy.Spotify(auth_manager=auth_manager) - playlist_name, albumArtUrls = write_playlist(sp, playlist_uri) + playlist_name, album_art_urls = write_playlist(sp, playlist_uri) reference_file = f"{playlist_name}.txt" # Create the playlist folder if not os.path.exists(playlist_name): From 75dfdc3ae2a0e0ddec6a6a072e4230face5e61ed Mon Sep 17 00:00:00 2001 From: Haim Daniel Date: Sat, 14 Sep 2024 18:38:33 +0300 Subject: [PATCH 6/6] Fix README.md --- README.md | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 33efcb5..a0a2557 100644 --- a/README.md +++ b/README.md @@ -24,22 +24,23 @@ cd spotify-to-mp3-python/ ### 2. Install dependencies -We will be installing dependencies using `pip`, the official Python package manager. If you do not have `pip`, I'd recommend checking this [thread](https://stackoverflow.com/questions/6587507/how-to-install-pip-with-python-3/) to install it. +We will be installing dependencies using `uv`, the super fast Python package manager. If you do not have `uv`, I'd recommend checking this [thread](https://open.spotify.com/playlist/2FekNrO1rasjSaIndayN7O?si=66ef762c945e4f0a) to install it. Copy and paste (and run) the following line in your terminal session to install all necessary packages. ```bash -pip install setuptools -e . +uv venv venv && source .venv/bin/activate +uv pip install setuptools -e . ``` ### 2.1 Install ffmpeg You will be needing `ffmpeg` to convert the downloaded audio files to MP3. You can download it: -*Ubuntu and Debian: -`sudo apt-get install ffmpeg` +* Ubuntu and Debian: `sudo apt-get install ffmpeg` + +* macOS: `brew install ffmpeg` -*macOS: `brew install ffmpeg * Windows: `choco install ffmpeg` @@ -55,9 +56,8 @@ Here **it really doesn't matter what you put** for "App name" and "App descripti I just put "Testing" for both. The next section is "Redirect URIs", which is a bit more important. -You can put anything here, but I'd recommend putting `http://localhost:8888/callback`. - -This is because the script uses the `spotipy` library, which uses the `localhost` URL to authenticate with Spotify. +You can put anything here, but I'd recommend putting `http://localhost:8888/callback`. +The script won't be using this URL, but it's necessary to put something here. Make sure to check "I understand and agree with Spotify's Developer Terms of Service and Design Guidelines" and click "Create". @@ -68,22 +68,9 @@ You should see this: You will see the "Client ID" field on the left (it's redacted here). Copy and save your Client ID somewhere - you'll need it later. Click "Show client secret" under Client ID and it should show you another long list of characters. Also copy and save your Client Secret. -Next, we need your playlist URI. To do this, simply open Spotify, right-click on the playlist you want to download, hover over "Share", and click "Copy Spotify URI". It should look something like this: `spotify:playlist:37i9dQZEVXbJiZcmkrIHGU`. When inputting this into the script, make sure to *only input the characters after "spotify:playlist:"*. So for this example, input `37i9dQZEVXbJiZcmkrIHGU`. Save your URI somewhere handy. - -Alternatively, you can find the URI as follows: -1. Right-click on the playlist you want to download -2. Click "Share" -3. Click "Embed Playlist" -4. Click "Show Code" -5. The URI is the code between "https://open.spotify.com/embed/playlist/" and the first "?" - -For example, in this code snippet: - -```html - -``` +Next, we need your playlist URI. To do this, simply open Spotify, right-click on the playlist you want to download, hover over "Share", and click "Copy link to playlist". -The code is 11cPCycyvvpL0MDLO648vE +It should look something like this: `https://open.spotify.com/playlist/2FekNrO1rasjSaIndayN7O?si=6666762ba5421f0a`. ### 4. Running