diff --git a/ElectricShuffle.code-workspace b/ElectricShuffle.code-workspace deleted file mode 100644 index 362d7c2..0000000 --- a/ElectricShuffle.code-workspace +++ /dev/null @@ -1,7 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ] -} \ No newline at end of file diff --git a/ElectricShuffle.py b/ElectricShuffle.py index c963e82..32f5b1d 100644 --- a/ElectricShuffle.py +++ b/ElectricShuffle.py @@ -41,15 +41,18 @@ out_file.write(json.dumps(session_dict)) ### Now that session is handled, get playlists -playlists = session.user.playlists() + +playlists_no_e = session.user.playlists() +for playlist in playlists_no_e: + if("- Electrified" in playlist.name): + playlists_no_e.remove(playlist) print("Please select the playlist you wish to shuffle by intering it's number below") -for idx, playlist in enumerate(playlists): +for idx, playlist in enumerate(playlists_no_e): print(f" [{idx+1}] {playlist.name}") - # Get playlists on user account playlist_num = int(input("Playlist Number: ")) - 1 -selected_playlist = playlists[playlist_num] +selected_playlist = playlists_no_e[playlist_num] print(f"Electric shuffle {selected_playlist.name}?") should_continue = bool(input("Continue (Yes/No): ")) @@ -57,7 +60,6 @@ if not should_continue: exit(1) - ### Do the thing tracklist = selected_playlist.tracks() track_ids = [] @@ -67,9 +69,12 @@ ### Signify that this is a copy for the user shuffled_name = f"{selected_playlist.name} - Electrified" + +# Get all playlists including - Electrified +playlists = session.user.playlists() for playlist in playlists: ### Delete an existing version of this playlist if we are re-rolling - if playlist.name is shuffled_name: + if playlist.name == shuffled_name: playlist.delete() ## Create (or re create) the electric playlist @@ -78,3 +83,4 @@ ## Add the beautiful music after a quick shuffle random.shuffle(track_ids) new_playlist.add(track_ids) +print("Shuffling complete") \ No newline at end of file diff --git a/ElectricShuffleAll.py b/ElectricShuffleAll.py new file mode 100644 index 0000000..7d802b0 --- /dev/null +++ b/ElectricShuffleAll.py @@ -0,0 +1,107 @@ +#!/bin/env python3 + +import datetime +import json +import os +from os import path +import random +from select import select +import tidalapi + + +def token_refresh(session, refresh_token): + """ + Retrieves a new access token using the specified parameters, updating the current access token + + :param refresh_token: The refresh token retrieved when using the OAuth login. + :return: True if we believe the token was successfully refreshed, otherwise False + """ + url = 'https://auth.tidal.com/v1/oauth2/token' + params = { + 'grant_type': 'refresh_token', + 'refresh_token': refresh_token, + 'client_id': session.config.client_id, + 'client_secret': session.config.client_secret + } + + request = session.request_session.post(url, params) + json = request.json() + if not request.ok: + log.warning("The refresh token has expired, a new login is required.") + return json + session.access_token = json['access_token'] + session.expiry_time = datetime.datetime.now() + datetime.timedelta(seconds=json['expires_in']) + session.token_type = json['token_type'] + return json + +dir = path.dirname(path.realpath(__file__)) +credential_file = path.join(dir, '.credentials.json') + +need_oauth = True +print("Creating Session") +session = tidalapi.Session() + +# Check if the credentials are still valid +if os.path.exists(credential_file): + with open(credential_file, "r") as open_file: + token_json = json.load(open_file) + + token_refresh(session, token_json["refresh_token"]) + + # Check tokens with tidal + session.load_oauth_session( + token_json["token_type"], + token_json["access_token"], + token_json["refresh_token"], + datetime.datetime.fromisoformat(token_json["expiry_time"]) + ) + + new_token_json = token_refresh(session, token_json["refresh_token"]) + session_dict = { + "token_type": session.token_type, + "access_token": session.access_token, + "refresh_token": session.refresh_token, + "expiry_time": session.expiry_time.isoformat() + } + with open(credential_file, "w") as out_file: + out_file.write(json.dumps(session_dict)) + +if not session.check_login(): + # Have user go log in and when this completes, we will auto continue + session.login_oauth_simple() + + session_dict = { + "token_type": session.token_type, + "access_token": session.access_token, + "refresh_token": session.refresh_token, + "expiry_time": session.expiry_time.isoformat() + } + with open(credential_file, "w") as out_file: + out_file.write(json.dumps(session_dict)) + +### Now that session is handled, get playlists +print("Getting Playlists") +playlists = session.user.playlists() + +for playlist in playlists: + ### Signify that this is a copy for the user + shuffled_name = f"{playlist.name} - Electrified" + + if "- Electrified" in playlist.name: + playlist.delete() + else: + print("Shuffling playlist " + playlist.name) + + ### Do the thing + tracklist = playlist.tracks() + track_ids = [] + + for track in tracklist: + track_ids.append(track.id) + + ## Create (or re create) the electric playlist + new_playlist = session.user.create_playlist(shuffled_name, playlist.description) + + ## Add the beautiful music after a quick shuffle + random.shuffle(track_ids) + new_playlist.add(track_ids) diff --git a/README.md b/README.md index c209022..018547a 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,29 @@ This is a (terrible) app to shuffle your Tidal playlists since there are not (cu I'm not responsible for bricked devices, dead SD cards, thermonuclear war, or you getting fired because your entire playlist got deleted and you are now too sad to work. This _shouldn't happen_ but the app is very lightly tested. -### Running The Program +### Setting up Environment 1. Clone this repo to your computer 1. Create a venv to install packages by running `python3 -m venv .venv` 1. Start using the virtual env by running `source .venv/bin/activate` 1. Install required packages by running `pip3 install -r requirements.txt` + +### Running ElectricShuffle 1. Run the program `./ElectricShuffle.py` 1. Log in by clicking the link (or copying it to your browser). Once logged in return to the terminal. 1. Pick a playlist to shuffle 1. Confirm that is the playlist you want 1. Go enjoy some tunes! Notice your new playlist which is a copy of your current one but with an " - Electrified" suffix. This is the shuffled one. +### Running ElectricShuffleAll +This script requires no human intervention beyond the initial login. Run it one time manually to login, then automate to your heart's content. + +**Note:** On linux VMs there is an issue where the tidalapi package sends requests too fast for the tidal servers. Adding time.sleep(1) to the tidalapi playlist.py \__init__ resolves the issue. + +1. Run the program `./ElectricShuffleAll.py` +1. Log in by clicking the link (or copying it to your browser). Once logged in return to the terminal. (Once per environment) +1. Shuffles all playlists + ### Contributing Contributions are welcome, this is just a little hobby script I wrote to make my life easier. Feel free to change it and send a pull request. diff --git a/requirements.txt b/requirements.txt index 1dc71f4..f315a07 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,32 +1,2 @@ -attr==0.3.2 -brotli==1.0.9 -brotlicffi==1.0.9.2 -ConfigParser==5.3.0 -cryptography==38.0.1 -Cython==0.29.32 -dl==0.1.0 -docutils==0.19 -HTMLParser==0.0.2 -ipython==8.5.0 -ipywidgets==8.0.2 -Jinja2==3.1.2 -jnius==1.1.0 -lockfile==0.12.2 -mock==4.0.3 -numpy==1.23.2 -Pillow==9.2.0 -pluggy==1.0.0 -protobuf==4.21.5 -pyOpenSSL==22.0.0 -pytest==7.1.3 -railroad==0.5.0 -secretstorage==3.3.3 -simplejson==3.17.6 -Sphinx==5.1.1 -toml==0.10.2 -tornado==6.2 -trove_classifiers==2022.8.31 -truststore==0.5.0 -unicodedata2==14.0.0 -urllib3_secure_extra==0.1.0 -xmlrpclib==1.0.1 +tidalapi==0.7.0 +