11 Commits

Author SHA1 Message Date
Tim Rae
299cc3209f Don't specify point version of spotify and tidal APIs
So we don't have to put out a new release to update the dependency
2025-12-31 15:01:30 +01:00
Tim Rae
46771aefc8 Bump version to 1.0.6 2025-12-31 14:59:03 +01:00
Tim Rae
eb745eab6f Update tidalapi version 2025-12-31 14:54:44 +01:00
Alex Camilleri
9ff181ae6e Playlist name in txt output (#140) 2025-10-20 09:36:04 +02:00
Tim Rae
d90c5bac41 Update tidalapi dependency version to 0.8.8 (#136)
Fixes #135 #134
2025-10-16 12:22:15 +02:00
Axel
e62a8a80cb use 127.0.0.1 instead of localhost to meet new spotify URI criteria (#116) 2025-04-09 22:14:15 +02:00
Darkasf
a5afd975f0 Update readme.md (#113) 2025-03-30 06:51:13 +02:00
Johan Lindbergh
03e0396ac0 Add another check to the spotify track sanity filter (#111)
Discovered a crash when syncing a playlist with some random podcast
episode. It crashed because both ['artists'][0]['name'] and
['album']['artists'][0]['name'] was None.

I thought the episode would be caught by the track_filter, but
apparently having the 'type': 'episode' isn't reliable enough :/

Not sure if the check should be in the sanity_filter or any of
the _search_for... methods though.
2025-02-13 22:50:00 +01:00
Tim Rae
a438cda72b Bump version to 1.0.4 2024-12-04 06:09:12 +09:00
Tim Rae
2f1985a42b Check for null playlists in filter 2024-12-04 05:46:30 +09:00
Tim Rae
bcf2bbca0d Add option to do auth manually on headless server (#88) 2024-10-26 11:12:47 +02:00
7 changed files with 26 additions and 14 deletions

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@ config.yml
config.yaml
.cache*
.session.yml
songs not found.txt
# Byte-compiled / optimized / DLL files
__pycache__/

View File

@@ -2,7 +2,8 @@ spotify:
client_id: your_client_id
client_secret: your_client_secret
username: your_spotify_username
redirect_uri: http://localhost:8888/callback
redirect_uri: http://127.0.0.1:8888/callback
open_browser: True # Set to False if using a headless server environment
# uncomment this block if you want to only sync specific playlist IDs

View File

@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
[project]
name = "spotify_to_tidal"
version = "1.0.3"
version = "1.0.6"
requires-python = ">= 3.10"
dependencies = [
"spotipy~=2.24.0",
"tidalapi==0.7.6",
"spotipy~=2.24",
"tidalapi==0.8",
"pyyaml~=6.0",
"tqdm~=4.64",
"sqlalchemy~=2.0",

View File

@@ -19,6 +19,7 @@ Setup
Usage
----
To synchronize all of your Spotify playlists with your Tidal account run the following from the project root directory
Windows ignores python module paths by default, but you can run them using `python3 -m spotify_to_tidal`
```bash
spotify_to_tidal

View File

@@ -15,11 +15,12 @@ SPOTIFY_SCOPES = 'playlist-read-private, user-library-read'
def open_spotify_session(config) -> spotipy.Spotify:
credentials_manager = spotipy.SpotifyOAuth(username=config['username'],
scope=SPOTIFY_SCOPES,
client_id=config['client_id'],
client_secret=config['client_secret'],
redirect_uri=config['redirect_uri'],
requests_timeout=2)
scope=SPOTIFY_SCOPES,
client_id=config['client_id'],
client_secret=config['client_secret'],
redirect_uri=config['redirect_uri'],
requests_timeout=2,
open_browser=config.get('open_browser', True))
try:
credentials_manager.get_access_token(as_dict=False)
except spotipy.SpotifyOauthError:

View File

@@ -184,7 +184,11 @@ async def get_tracks_from_spotify_playlist(spotify_session: spotipy.Spotify, spo
print(f"Loading tracks from Spotify playlist '{spotify_playlist['name']}'")
items = await repeat_on_request_error( _fetch_all_from_spotify_in_chunks, lambda offset: _get_tracks_from_spotify_playlist(offset=offset, playlist_id=spotify_playlist["id"]))
track_filter = lambda item: item.get('type', 'track') == 'track' # type may be 'episode' also
sanity_filter = lambda item: 'album' in item and 'name' in item['album'] and 'artists' in item['album'] and len(item['album']['artists']) > 0
sanity_filter = lambda item: ('album' in item
and 'name' in item['album']
and 'artists' in item['album']
and len(item['album']['artists']) > 0
and item['album']['artists'][0]['name'] is not None)
return list(filter(sanity_filter, filter(track_filter, items)))
def populate_track_match_cache(spotify_tracks_: Sequence[t_spotify.SpotifyTrack], tidal_tracks_: Sequence[tidalapi.Track]):
@@ -277,7 +281,9 @@ async def search_new_tracks_on_tidal(tidal_session: tidalapi.Session, spotify_tr
color = ('\033[91m', '\033[0m')
print(color[0] + "Could not find the track " + song404[-1] + color[1])
file_name = "songs not found.txt"
header = f"==========================\nPlaylist: {playlist_name}\n==========================\n"
with open(file_name, "a", encoding="utf-8") as file:
file.write(header)
for song in song404:
file.write(f"{song}\n")
@@ -387,7 +393,7 @@ async def get_playlists_from_spotify(spotify_session: spotipy.Spotify, config):
playlists.extend([p for p in extra_result['items']])
# filter out playlists that don't belong to us or are on the exclude list
my_playlist_filter = lambda p: p['owner']['id'] == user_id
my_playlist_filter = lambda p: p and p['owner']['id'] == user_id
exclude_filter = lambda p: not p['id'] in exclude_list
return list(filter( exclude_filter, filter( my_playlist_filter, playlists )))

View File

@@ -23,7 +23,8 @@ def test_open_spotify_session(mocker):
"username": "test_user",
"client_id": "test_client_id",
"client_secret": "test_client_secret",
"redirect_uri": "http://localhost/",
"redirect_uri": "http://127.0.0.1/",
"open_browser": True,
}
# Create a mock SpotifyOAuth instance
@@ -39,8 +40,9 @@ def test_open_spotify_session(mocker):
scope=SPOTIFY_SCOPES,
client_id="test_client_id",
client_secret="test_client_secret",
redirect_uri="http://localhost/",
redirect_uri="http://127.0.0.1/",
requests_timeout=2,
open_browser=True,
)
# Assert that the Spotify instance was created
@@ -62,7 +64,7 @@ def test_open_spotify_session_oauth_error(mocker):
"username": "test_user",
"client_id": "test_client_id",
"client_secret": "test_client_secret",
"redirect_uri": "http://localhost/",
"redirect_uri": "http://127.0.0.1/",
}
# Mock sys.exit to prevent the test from exiting