From 8d82aaf752ede80ea4916c6364591074244acf71 Mon Sep 17 00:00:00 2001 From: Raleigh Littles Date: Sat, 16 Nov 2024 21:21:18 -0700 Subject: [PATCH 1/6] first commit... doesn't work, timeout, see https://github.com/ramsayleung/rspotify/issues/502 --- to_spotify/.gitignore | 23 +++++++++++++++++++++++ to_spotify/Cargo.toml | 9 +++++++++ to_spotify/README.md | 10 ++++++++++ to_spotify/src/main.rs | 19 +++++++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 to_spotify/.gitignore create mode 100644 to_spotify/Cargo.toml create mode 100644 to_spotify/README.md create mode 100644 to_spotify/src/main.rs diff --git a/to_spotify/.gitignore b/to_spotify/.gitignore new file mode 100644 index 0000000..044a18f --- /dev/null +++ b/to_spotify/.gitignore @@ -0,0 +1,23 @@ +# Don't commit env file that has API keys +.env +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/to_spotify/Cargo.toml b/to_spotify/Cargo.toml new file mode 100644 index 0000000..a9da154 --- /dev/null +++ b/to_spotify/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "to_spotify" +version = "0.1.0" +edition = "2021" + +[dependencies] +# This must be on a single line for some reason +rspotify = { version = "0.13.3",default-features = false,features = ["env-file", "client-ureq", "ureq-rustls-tls"]} +dotenv = "0.15.0" \ No newline at end of file diff --git a/to_spotify/README.md b/to_spotify/README.md new file mode 100644 index 0000000..66d8ad2 --- /dev/null +++ b/to_spotify/README.md @@ -0,0 +1,10 @@ +# env file + +``` +# Both are 32-character alphanumeric values +# See: https://developer.spotify.com/documentation/web-api/concepts/apps +# for instructions on how to generate! +RSPOTIFY_CLIENT_ID=HdtSF4bZRaBBSTISX1oJq0ewYkspqsdB +RSPOTIFY_CLIENT_SECRET=ehZjXinzzmzxPjQ75E7YkvF57uYJstBU + +``` \ No newline at end of file diff --git a/to_spotify/src/main.rs b/to_spotify/src/main.rs new file mode 100644 index 0000000..9a68a67 --- /dev/null +++ b/to_spotify/src/main.rs @@ -0,0 +1,19 @@ +use rspotify::clients::BaseClient; +use rspotify::model::SearchType; + +fn main() { + println!("Loading credentials from .env file .."); + let creds_from_env_file = rspotify::Credentials::from_env().unwrap(); + println!("Credentials loaded! Creating client obj.. "); + let spotify_cli_obj = rspotify::ClientCredsSpotify::new(creds_from_env_file); + + // Obtaining the access token - must be done before any query is made + spotify_cli_obj.request_token().unwrap(); + + let album_query = "album:arrival artist:abba"; + let result = spotify_cli_obj.search(album_query, SearchType::Album, None, None, Some(10), None); + match result { + Ok(album) => println!("Searched album: {album:?}"), + Err(err) => println!("Search error! {err:?}"), + } +} From 9e118c94e2c3602e9941491ade0c8ac47fa0d153 Mon Sep 17 00:00:00 2001 From: Raleigh Littles Date: Tue, 26 Nov 2024 01:09:30 -0700 Subject: [PATCH 2/6] tested, seems to work --- to_spotify/requirements.txt | 7 ++++ to_spotify/spotify_integration.py | 57 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 to_spotify/requirements.txt create mode 100644 to_spotify/spotify_integration.py diff --git a/to_spotify/requirements.txt b/to_spotify/requirements.txt new file mode 100644 index 0000000..b8e41a7 --- /dev/null +++ b/to_spotify/requirements.txt @@ -0,0 +1,7 @@ +certifi==2024.8.30 +charset-normalizer==3.4.0 +idna==3.10 +redis==5.2.0 +requests==2.32.3 +spotipy==2.24.0 +urllib3==2.2.3 diff --git a/to_spotify/spotify_integration.py b/to_spotify/spotify_integration.py new file mode 100644 index 0000000..e0559c1 --- /dev/null +++ b/to_spotify/spotify_integration.py @@ -0,0 +1,57 @@ +import csv +import spotipy +import argparse +from spotipy.oauth2 import SpotifyClientCredentials + +UNITED_STATES_ISO_3166_1_CODE = "US" +DEFAULT_QUERY_LIMIT = 3 + +if __name__ == '__main__': + + argparse_parser = argparse.ArgumentParser(description="Read CSV files containing songs and search for them in Spotify") + argparse_parser.add_argument("-f", "--csv-file", help="Path to the CSV file containing the songs", required=True) + argparse_parser.add_argument("-t", "--track-column", help="Name of the column containing the track names", required=False, default="Song Title") + argparse_args = argparse_parser.parse_args() + + num_songs_found = 0 + spotify_track_ids = list() + + if not argparse_args.csv_file: + raise FileNotFoundError(f"Error: File {argparse_args.csv_file} not found") + + with open(argparse_args.csv_file, mode='r') as csv_file: + csv_reader = csv.DictReader(csv_file) + + sp_obj = spotipy.Spotify(auth_manager=SpotifyClientCredentials(client_id="", + client_secret="")) + + for csv_row in csv_reader: + song_title = csv_row[argparse_args.track_column] + song_artist = csv_row["Artist"] + + if not song_title or not song_artist: + print(f"Skipping row... missing song title or artist") + continue + + search_results = sp_obj.search(q=f"track:{song_title} artist:{song_artist}", type="track", limit=DEFAULT_QUERY_LIMIT, market=UNITED_STATES_ISO_3166_1_CODE) + + if search_results["tracks"]["total"] == 0: + print(f"Song '{song_title}' by '{song_artist}' not found on Spotify!") + continue + + else: + num_songs_found += 1 + track_id = search_results["tracks"]["items"][0]["id"] + spotify_track_ids.append(track_id) + + + + + + + + + + + + From d5cf3e50f2a1c23830b0ba7e648f87f555516a26 Mon Sep 17 00:00:00 2001 From: Raleigh Littles Date: Tue, 26 Nov 2024 11:10:31 -0700 Subject: [PATCH 3/6] playlist creation works --- to_spotify/spotify_integration.py | 62 +++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/to_spotify/spotify_integration.py b/to_spotify/spotify_integration.py index e0559c1..ebc0968 100644 --- a/to_spotify/spotify_integration.py +++ b/to_spotify/spotify_integration.py @@ -1,30 +1,25 @@ import csv import spotipy import argparse +import socket from spotipy.oauth2 import SpotifyClientCredentials +from spotipy.oauth2 import SpotifyOAuth UNITED_STATES_ISO_3166_1_CODE = "US" DEFAULT_QUERY_LIMIT = 3 -if __name__ == '__main__': - argparse_parser = argparse.ArgumentParser(description="Read CSV files containing songs and search for them in Spotify") - argparse_parser.add_argument("-f", "--csv-file", help="Path to the CSV file containing the songs", required=True) - argparse_parser.add_argument("-t", "--track-column", help="Name of the column containing the track names", required=False, default="Song Title") - argparse_args = argparse_parser.parse_args() +def get_track_ids_from_csv(csv_file) -> list: + spotify_tracks = list() num_songs_found = 0 - spotify_track_ids = list() - if not argparse_args.csv_file: - raise FileNotFoundError(f"Error: File {argparse_args.csv_file} not found") - - with open(argparse_args.csv_file, mode='r') as csv_file: + with open(csv_file, mode='r') as csv_file: csv_reader = csv.DictReader(csv_file) sp_obj = spotipy.Spotify(auth_manager=SpotifyClientCredentials(client_id="", - client_secret="")) - + client_secret="")) + for csv_row in csv_reader: song_title = csv_row[argparse_args.track_column] song_artist = csv_row["Artist"] @@ -33,25 +28,54 @@ print(f"Skipping row... missing song title or artist") continue - search_results = sp_obj.search(q=f"track:{song_title} artist:{song_artist}", type="track", limit=DEFAULT_QUERY_LIMIT, market=UNITED_STATES_ISO_3166_1_CODE) + search_results = sp_obj.search(q=f"track:{song_title} artist:{ + song_artist}", type="track", limit=DEFAULT_QUERY_LIMIT, market=UNITED_STATES_ISO_3166_1_CODE) if search_results["tracks"]["total"] == 0: - print(f"Song '{song_title}' by '{song_artist}' not found on Spotify!") + print(f"Song '{song_title}' by '{ + song_artist}' not found on Spotify!") continue - + else: num_songs_found += 1 track_id = search_results["tracks"]["items"][0]["id"] - spotify_track_ids.append(track_id) - + spotify_tracks.append( + dict(song_title=song_title, song_artist=song_artist, track_id=track_id)) + print(f"[DEBUG] Found {num_songs_found} songs on Spotify") + return spotify_tracks + - +def create_playlist_from_tracks(spotify_tracks: list, playlist_name: str) -> str: - + sp_obj = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id="", + client_secret="", redirect_uri="", scope="playlist-modify-private")) + user_id = sp_obj.current_user()["id"] + playlist_id = sp_obj.user_playlist_create( + user_id, playlist_name, public=False, description=socket.gethostname())["id"] + track_ids = [track["track_id"] for track in spotify_tracks] + sp_obj.playlist_add_items(playlist_id, track_ids) + return playlist_id +if __name__ == '__main__': + + argparse_parser = argparse.ArgumentParser( + description="Read CSV files containing songs and search for them in Spotify") + argparse_parser.add_argument( + "-f", "--csv-file", help="Path to the CSV file containing the songs", required=True) + argparse_parser.add_argument( + "-t", "--track-column", help="Name of the column containing the track names", required=False, default="Song Title") + argparse_args = argparse_parser.parse_args() + + if not argparse_args.csv_file: + raise FileNotFoundError( + f"Error: File {argparse_args.csv_file} not found") + spotify_tracks_and_track_ids = get_track_ids_from_csv( + argparse_args.csv_file) + new_playlist_id = create_playlist_from_tracks( + spotify_tracks_and_track_ids, "Test Playlist") From 3949cb2ea6707a7db42f32da82f2a7368516f907 Mon Sep 17 00:00:00 2001 From: Raleigh Littles Date: Tue, 26 Nov 2024 11:35:17 -0700 Subject: [PATCH 4/6] final commit of spotify integration --- spotify_integration/.gitignore | 186 ++++++++++++++++++ .../Cargo.toml | 0 spotify_integration/README.md | 37 ++++ .../requirements.txt | 0 .../spotify_api_credentials.json | 5 + .../spotify_integration.py | 30 ++- .../src/main.rs | 0 to_spotify/.gitignore | 23 --- to_spotify/README.md | 10 - 9 files changed, 248 insertions(+), 43 deletions(-) create mode 100644 spotify_integration/.gitignore rename {to_spotify => spotify_integration}/Cargo.toml (100%) create mode 100644 spotify_integration/README.md rename {to_spotify => spotify_integration}/requirements.txt (100%) create mode 100644 spotify_integration/spotify_api_credentials.json rename {to_spotify => spotify_integration}/spotify_integration.py (68%) rename {to_spotify => spotify_integration}/src/main.rs (100%) delete mode 100644 to_spotify/.gitignore delete mode 100644 to_spotify/README.md diff --git a/spotify_integration/.gitignore b/spotify_integration/.gitignore new file mode 100644 index 0000000..34539d1 --- /dev/null +++ b/spotify_integration/.gitignore @@ -0,0 +1,186 @@ +# Don't commit env file that has API keys +.env +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/to_spotify/Cargo.toml b/spotify_integration/Cargo.toml similarity index 100% rename from to_spotify/Cargo.toml rename to spotify_integration/Cargo.toml diff --git a/spotify_integration/README.md b/spotify_integration/README.md new file mode 100644 index 0000000..f434c09 --- /dev/null +++ b/spotify_integration/README.md @@ -0,0 +1,37 @@ +# About + +Provides API integration with [Spotify](https://en.wikipedia.org/wiki/Spotify) + +After running the iTunesDB parser application on an iTunesDB file, you end up with a music CSV file that contains info about your songs. This tool lets you then import that CSV file into a Spotify playlist. + +If you want to use this tool with your _own_ CSV files, see `test.csv` for an example of the format. + +## Usage info + +``` +usage: spotify_integration.py [-h] -f CSV_FILE [-t TRACK_COLUMN] -a API_CREDENTIALS_FILE + +Read CSV files containing songs and search for them in Spotify + +options: + -h, --help show this help message and exit + -f CSV_FILE, --csv-file CSV_FILE + Path to the CSV file containing the songs + -t TRACK_COLUMN, --track-column TRACK_COLUMN + Name of the column containing the track names + -a API_CREDENTIALS_FILE, --api-credentials-file API_CREDENTIALS_FILE + Path to JSON file containing API credentials, see + spotify_api_credentials.json for the format +``` + +# API instructions + +See `spotify_api_credentials.json` for example of format +Both are 32-character alphanumeric values +See: https://developer.spotify.com/documentation/web-api/concepts/apps +for instructions on how to generate! + +After running you will be sent to a authorization prompt in your browser and then redirected to a URL + +The application will ask you to paste that URL to retrieve the code + diff --git a/to_spotify/requirements.txt b/spotify_integration/requirements.txt similarity index 100% rename from to_spotify/requirements.txt rename to spotify_integration/requirements.txt diff --git a/spotify_integration/spotify_api_credentials.json b/spotify_integration/spotify_api_credentials.json new file mode 100644 index 0000000..c189604 --- /dev/null +++ b/spotify_integration/spotify_api_credentials.json @@ -0,0 +1,5 @@ +{ + "client_id": "HdtSF4bZRaBBSTISX1oJq0ewYkspqsdB", + "client_secret": "ehZjXinzzmzxPjQ75E7YkvF57uYJstBU", + "redirect_uri": "your_redirect_uri" +} \ No newline at end of file diff --git a/to_spotify/spotify_integration.py b/spotify_integration/spotify_integration.py similarity index 68% rename from to_spotify/spotify_integration.py rename to spotify_integration/spotify_integration.py index ebc0968..ad60288 100644 --- a/to_spotify/spotify_integration.py +++ b/spotify_integration/spotify_integration.py @@ -2,14 +2,15 @@ import spotipy import argparse import socket +import json from spotipy.oauth2 import SpotifyClientCredentials from spotipy.oauth2 import SpotifyOAuth UNITED_STATES_ISO_3166_1_CODE = "US" DEFAULT_QUERY_LIMIT = 3 +DEFAULT_SPOTIFY_API_SCOPE="playlist-modify-private" - -def get_track_ids_from_csv(csv_file) -> list: +def get_track_ids_from_csv(csv_file, spotify_api_obj) -> list: spotify_tracks = list() num_songs_found = 0 @@ -17,8 +18,7 @@ def get_track_ids_from_csv(csv_file) -> list: with open(csv_file, mode='r') as csv_file: csv_reader = csv.DictReader(csv_file) - sp_obj = spotipy.Spotify(auth_manager=SpotifyClientCredentials(client_id="", - client_secret="")) + sp_obj = spotipy.Spotify(auth_manager=SpotifyClientCredentials(client_id=spotify_api_obj["client_id"],client_secret=spotify_api_obj["client_secret"])) for csv_row in csv_reader: song_title = csv_row[argparse_args.track_column] @@ -41,14 +41,15 @@ def get_track_ids_from_csv(csv_file) -> list: track_id = search_results["tracks"]["items"][0]["id"] spotify_tracks.append( dict(song_title=song_title, song_artist=song_artist, track_id=track_id)) + print(f"[DEBUG] Found {num_songs_found} songs on Spotify") return spotify_tracks -def create_playlist_from_tracks(spotify_tracks: list, playlist_name: str) -> str: +def create_playlist_from_tracks(spotify_tracks: list, playlist_name: str, playlist_description: str, spotify_api_obj) -> str: - sp_obj = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id="", - client_secret="", redirect_uri="", scope="playlist-modify-private")) + sp_obj = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=spotify_api_obj["client_id"], + client_secret=spotify_api_obj["client_secret"], redirect_uri=spotify_api_obj["redirect_uri"], scope=DEFAULT_SPOTIFY_API_SCOPE)) user_id = sp_obj.current_user()["id"] playlist_id = sp_obj.user_playlist_create( @@ -68,14 +69,23 @@ def create_playlist_from_tracks(spotify_tracks: list, playlist_name: str) -> str "-f", "--csv-file", help="Path to the CSV file containing the songs", required=True) argparse_parser.add_argument( "-t", "--track-column", help="Name of the column containing the track names", required=False, default="Song Title") + argparse_parser.add_argument("-a", "--api-credentials-file", help="Path to JSON file containing API credentials, see spotify_api_credentials.json for the format", required=True) argparse_args = argparse_parser.parse_args() if not argparse_args.csv_file: raise FileNotFoundError( - f"Error: File {argparse_args.csv_file} not found") + f"Error: CSV file '{argparse_args.csv_file}' not found") + + if not argparse_args.api_credentials_file: + raise FileNotFoundError( + f"Error: API credentials file '{argparse_args.api_credentials_file}' not found! Go to https://developer.spotify.com/documentation/web-api to create one") + + api_obj = json.load(open(argparse_args.api_credentials_file)) spotify_tracks_and_track_ids = get_track_ids_from_csv( - argparse_args.csv_file) + argparse_args.csv_file, api_obj) new_playlist_id = create_playlist_from_tracks( - spotify_tracks_and_track_ids, "Test Playlist") + spotify_tracks_and_track_ids, "Test Playlist", socket.gethostname(), api_obj) + + print(f"Created new playlist with ID {new_playlist_id}") diff --git a/to_spotify/src/main.rs b/spotify_integration/src/main.rs similarity index 100% rename from to_spotify/src/main.rs rename to spotify_integration/src/main.rs diff --git a/to_spotify/.gitignore b/to_spotify/.gitignore deleted file mode 100644 index 044a18f..0000000 --- a/to_spotify/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# Don't commit env file that has API keys -.env -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file diff --git a/to_spotify/README.md b/to_spotify/README.md deleted file mode 100644 index 66d8ad2..0000000 --- a/to_spotify/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# env file - -``` -# Both are 32-character alphanumeric values -# See: https://developer.spotify.com/documentation/web-api/concepts/apps -# for instructions on how to generate! -RSPOTIFY_CLIENT_ID=HdtSF4bZRaBBSTISX1oJq0ewYkspqsdB -RSPOTIFY_CLIENT_SECRET=ehZjXinzzmzxPjQ75E7YkvF57uYJstBU - -``` \ No newline at end of file From 1c676dd97961fb96c43f2e7138603f95006a2b7b Mon Sep 17 00:00:00 2001 From: Raleigh Littles Date: Tue, 26 Nov 2024 12:03:25 -0700 Subject: [PATCH 5/6] encountering API issues with playlist not sure why --- spotify_integration/.gitignore | 1 + spotify_integration/spotify_integration.py | 28 +++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/spotify_integration/.gitignore b/spotify_integration/.gitignore index 34539d1..9ff2fcf 100644 --- a/spotify_integration/.gitignore +++ b/spotify_integration/.gitignore @@ -1,5 +1,6 @@ # Don't commit env file that has API keys .env +*.json # Generated by Cargo # will have compiled files and executables debug/ diff --git a/spotify_integration/spotify_integration.py b/spotify_integration/spotify_integration.py index ad60288..8d1a0ed 100644 --- a/spotify_integration/spotify_integration.py +++ b/spotify_integration/spotify_integration.py @@ -3,12 +3,14 @@ import argparse import socket import json +import datetime + from spotipy.oauth2 import SpotifyClientCredentials from spotipy.oauth2 import SpotifyOAuth UNITED_STATES_ISO_3166_1_CODE = "US" DEFAULT_QUERY_LIMIT = 3 -DEFAULT_SPOTIFY_API_SCOPE="playlist-modify-private" +DEFAULT_SPOTIFY_API_SCOPE = "playlist-modify-private" def get_track_ids_from_csv(csv_file, spotify_api_obj) -> list: @@ -18,7 +20,8 @@ def get_track_ids_from_csv(csv_file, spotify_api_obj) -> list: with open(csv_file, mode='r') as csv_file: csv_reader = csv.DictReader(csv_file) - sp_obj = spotipy.Spotify(auth_manager=SpotifyClientCredentials(client_id=spotify_api_obj["client_id"],client_secret=spotify_api_obj["client_secret"])) + sp_obj = spotipy.Spotify(auth_manager=SpotifyClientCredentials( + client_id=spotify_api_obj["client_id"], client_secret=spotify_api_obj["client_secret"])) for csv_row in csv_reader: song_title = csv_row[argparse_args.track_column] @@ -41,19 +44,18 @@ def get_track_ids_from_csv(csv_file, spotify_api_obj) -> list: track_id = search_results["tracks"]["items"][0]["id"] spotify_tracks.append( dict(song_title=song_title, song_artist=song_artist, track_id=track_id)) - + print(f"[DEBUG] Found {num_songs_found} songs on Spotify") return spotify_tracks def create_playlist_from_tracks(spotify_tracks: list, playlist_name: str, playlist_description: str, spotify_api_obj) -> str: - sp_obj = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=spotify_api_obj["client_id"], - client_secret=spotify_api_obj["client_secret"], redirect_uri=spotify_api_obj["redirect_uri"], scope=DEFAULT_SPOTIFY_API_SCOPE)) + sp_obj = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=spotify_api_obj["client_id"], client_secret=spotify_api_obj["client_secret"], redirect_uri=spotify_api_obj["redirect_uri"], scope=DEFAULT_SPOTIFY_API_SCOPE)) user_id = sp_obj.current_user()["id"] playlist_id = sp_obj.user_playlist_create( - user_id, playlist_name, public=False, description=socket.gethostname())["id"] + user_id, playlist_name, public=False, description=playlist_description)["id"] track_ids = [track["track_id"] for track in spotify_tracks] sp_obj.playlist_add_items(playlist_id, track_ids) @@ -69,23 +71,27 @@ def create_playlist_from_tracks(spotify_tracks: list, playlist_name: str, playli "-f", "--csv-file", help="Path to the CSV file containing the songs", required=True) argparse_parser.add_argument( "-t", "--track-column", help="Name of the column containing the track names", required=False, default="Song Title") - argparse_parser.add_argument("-a", "--api-credentials-file", help="Path to JSON file containing API credentials, see spotify_api_credentials.json for the format", required=True) + argparse_parser.add_argument("-a", "--api-credentials-file", + help="Path to JSON file containing API credentials, see spotify_api_credentials.json for the format", required=True) argparse_args = argparse_parser.parse_args() if not argparse_args.csv_file: raise FileNotFoundError( f"Error: CSV file '{argparse_args.csv_file}' not found") - + if not argparse_args.api_credentials_file: raise FileNotFoundError( f"Error: API credentials file '{argparse_args.api_credentials_file}' not found! Go to https://developer.spotify.com/documentation/web-api to create one") - + api_obj = json.load(open(argparse_args.api_credentials_file)) spotify_tracks_and_track_ids = get_track_ids_from_csv( argparse_args.csv_file, api_obj) + playlist_description = f"Playlist created on { + datetime.datetime.now().isoformat()} \n Created by {socket.gethostname()}" + new_playlist_id = create_playlist_from_tracks( - spotify_tracks_and_track_ids, "Test Playlist", socket.gethostname(), api_obj) - + spotify_tracks_and_track_ids, "Test Playlist", playlist_description, api_obj) + print(f"Created new playlist with ID {new_playlist_id}") From c5245badb79559f514326598e6b820526651e6c8 Mon Sep 17 00:00:00 2001 From: Raleigh Littles Date: Tue, 26 Nov 2024 12:42:09 -0700 Subject: [PATCH 6/6] spotify integration works tested --- spotify_integration/README.md | 12 ++++++++++-- spotify_integration/spotify_integration.py | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/spotify_integration/README.md b/spotify_integration/README.md index f434c09..048bfdc 100644 --- a/spotify_integration/README.md +++ b/spotify_integration/README.md @@ -6,7 +6,13 @@ After running the iTunesDB parser application on an iTunesDB file, you end up wi If you want to use this tool with your _own_ CSV files, see `test.csv` for an example of the format. -## Usage info +## Code usage + +Install dependencies + +```bash +$ pip install requirements.txt +``` ``` usage: spotify_integration.py [-h] -f CSV_FILE [-t TRACK_COLUMN] -a API_CREDENTIALS_FILE @@ -27,7 +33,9 @@ options: # API instructions See `spotify_api_credentials.json` for example of format -Both are 32-character alphanumeric values + +Both client_id and client_secret are 32-character alphanumeric values + See: https://developer.spotify.com/documentation/web-api/concepts/apps for instructions on how to generate! diff --git a/spotify_integration/spotify_integration.py b/spotify_integration/spotify_integration.py index 8d1a0ed..4063419 100644 --- a/spotify_integration/spotify_integration.py +++ b/spotify_integration/spotify_integration.py @@ -88,8 +88,8 @@ def create_playlist_from_tracks(spotify_tracks: list, playlist_name: str, playli spotify_tracks_and_track_ids = get_track_ids_from_csv( argparse_args.csv_file, api_obj) - playlist_description = f"Playlist created on { - datetime.datetime.now().isoformat()} \n Created by {socket.gethostname()}" + # Cannot have newline in playlist description + playlist_description = f"Playlist created on {datetime.datetime.now().isoformat()} Created by {socket.gethostname()}" new_playlist_id = create_playlist_from_tracks( spotify_tracks_and_track_ids, "Test Playlist", playlist_description, api_obj)