-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtidal_client.py
More file actions
221 lines (188 loc) 路 7.77 KB
/
Copy pathtidal_client.py
File metadata and controls
221 lines (188 loc) 路 7.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
"""
Tidal API client for Music Teleportation.
"""
import time
from typing import Dict, List, Optional, Tuple
import tidalapi
from tidalapi import Session, Quality
class TidalClient:
"""Client voor Tidal API-interacties."""
def __init__(self, session_data: Optional[Dict] = None):
"""
Initialiseer Tidal client.
Args:
session_data: Optional dictionary with session data from previous authentication
"""
self.session = tidalapi.Session()
if session_data:
# Restore existing session
self.session.load_oauth_session(**session_data)
def login_oauth(self):
"""
Start OAuth login flow.
Returns:
Tuple of (login_object, future) from tidalapi
"""
login, future = self.session.login_oauth()
return login, future
def check_auth(self, future):
"""
Check if authentication is complete.
Args:
future: The future object from login_oauth
Returns:
bool: True if authenticated
"""
try:
# Check if future is done
if future.done():
return True
return False
except:
return self.session.check_login()
def get_session_data(self) -> Dict:
"""Get session data for storage."""
return {
'token_type': self.session.token_type,
'access_token': self.session.access_token,
'refresh_token': self.session.refresh_token,
'expiry_time': self.session.expiry_time
}
def search_track(self, title: str, artists: List[str], album: str = "", isrc: str = None) -> List[Dict]:
"""
Zoek naar een track op Tidal.
Args:
title: Track title
artists: List of artist names
album: Album name (optional)
isrc: ISRC code (optional, most reliable)
Returns:
List of candidate tracks with metadata
"""
candidates = []
# Try multiple search strategies for better matching
# Strategy 1: ISRC search (most reliable)
if isrc:
try:
# Search by ISRC code
results = self.session.search(isrc, models=[tidalapi.Track])
tracks = results.get('tracks', [])
# Check if any result has matching ISRC
for track in tracks[:5]:
try:
# Some tracks might have ISRC info
if hasattr(track, 'isrc') and track.isrc == isrc:
candidates.append({
'id': track.id,
'title': track.name,
'artist': track.artist.name if track.artist else '',
'album': track.album.name if track.album else '',
'duration_ms': track.duration * 1000 if track.duration else 0,
})
# ISRC match found, return immediately
if len(candidates) > 0:
return candidates
except Exception:
pass
except Exception as e:
print(f"ISRC search failed for {isrc}: {e}")
# Strategy 2: Precise search with title and primary artist
if artists and len(artists) > 0:
try:
# Search with just title and first artist for precision
query = f"{title} {artists[0]}"
results = self.session.search(query, models=[tidalapi.Track])
tracks = results.get('tracks', [])
for track in tracks[:15]:
candidates.append({
'id': track.id,
'title': track.name,
'artist': track.artist.name if track.artist else '',
'album': track.album.name if track.album else '',
'duration_ms': track.duration * 1000 if track.duration else 0,
})
# If we found good candidates, return them
if len(candidates) >= 5:
return candidates
except Exception as e:
print(f"Precise search failed: {e}")
# Strategy 3: Broader search with all artists and album
try:
query = f"{title} {' '.join(artists)}"
if album:
query += f" {album}"
results = self.session.search(query, models=[tidalapi.Track])
tracks = results.get('tracks', [])
for track in tracks[:20]: # Get more candidates for better fuzzy matching
# Avoid duplicates
if not any(c['id'] == track.id for c in candidates):
candidates.append({
'id': track.id,
'title': track.name,
'artist': track.artist.name if track.artist else '',
'album': track.album.name if track.album else '',
'duration_ms': track.duration * 1000 if track.duration else 0,
})
except Exception as e:
print(f"Tidal search error: {e}")
return candidates
def add_tracks_to_favorites(self, track_ids: List[int]) -> None:
"""Voeg tracks toe aan gebruikers favorieten."""
user = self.session.user
for track_id in track_ids:
try:
user.favorites.add_track(track_id)
time.sleep(0.1) # Rate limiting
except Exception as e:
print(f"Error adding track {track_id}: {e}")
def create_playlist(self, name: str, description: str = "") -> str:
"""
Maak een nieuwe playlist aan.
Returns:
Playlist ID
"""
user = self.session.user
playlist = user.create_playlist(name, description)
return playlist.id
def add_tracks_to_playlist(self, playlist_id: str, track_ids: List[int]) -> None:
"""Voeg tracks toe aan een playlist."""
try:
playlist = self.session.playlist(playlist_id)
playlist.add(track_ids)
except Exception as e:
print(f"Error adding tracks to playlist: {e}")
def search_artist(self, artist_name: str) -> Optional[int]:
"""Zoek een artiest en retourneer het ID."""
try:
results = self.session.search(artist_name, models=[tidalapi.Artist])
artists = results.get('artists', [])
if artists:
return artists[0].id
except Exception as e:
print(f"Error searching artist: {e}")
return None
def add_artist_to_favorites(self, artist_id: int) -> None:
"""Voeg een artiest toe aan favorieten."""
user = self.session.user
try:
user.favorites.add_artist(artist_id)
except Exception as e:
print(f"Error adding artist: {e}")
def search_album(self, album_name: str, artist_names: List[str]) -> Optional[int]:
"""Zoek een album en retourneer het ID."""
query = f"{album_name} {' '.join(artist_names)}"
try:
results = self.session.search(query, models=[tidalapi.Album])
albums = results.get('albums', [])
if albums:
return albums[0].id
except Exception as e:
print(f"Error searching album: {e}")
return None
def add_album_to_favorites(self, album_id: int) -> None:
"""Voeg een album toe aan favorieten."""
user = self.session.user
try:
user.favorites.add_album(album_id)
except Exception as e:
print(f"Error adding album: {e}")