This commit is contained in:
afkarxyz
2025-09-20 08:32:12 +07:00
parent f75385c4e8
commit 9a28e8bd94
3 changed files with 303 additions and 6 deletions
+84 -4
View File
@@ -356,7 +356,7 @@ class TidalStatusChecker(QThread):
def run(self): def run(self):
try: try:
response = requests.get("https://hifi.401658.xyz", timeout=5) response = requests.get("https://tidal.401658.xyz", timeout=5)
is_online = response.status_code == 200 or response.status_code == 429 is_online = response.status_code == 200 or response.status_code == 429
self.status_updated.emit(is_online) self.status_updated.emit(is_online)
except Exception as e: except Exception as e:
@@ -640,7 +640,7 @@ class QobuzRegionComboBox(QComboBox):
class SpotiFLACGUI(QWidget): class SpotiFLACGUI(QWidget):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.current_version = "4.5" self.current_version = "4.6"
self.tracks = [] self.tracks = []
self.all_tracks = [] self.all_tracks = []
self.reset_state() self.reset_state()
@@ -1596,6 +1596,10 @@ class SpotiFLACGUI(QWidget):
self.handle_album_metadata(metadata) self.handle_album_metadata(metadata)
elif url_info["type"] == "playlist": elif url_info["type"] == "playlist":
self.handle_playlist_metadata(metadata) self.handle_playlist_metadata(metadata)
elif url_info["type"] == "artist_discography":
self.handle_discography_metadata(metadata)
elif url_info["type"] == "artist":
self.handle_artist_metadata(metadata)
self.update_button_states() self.update_button_states()
self.tab_widget.setCurrentIndex(0) self.tab_widget.setCurrentIndex(0)
@@ -1695,9 +1699,57 @@ class SpotiFLACGUI(QWidget):
'artists': playlist_data["playlist_info"]["owner"]["display_name"], 'artists': playlist_data["playlist_info"]["owner"]["display_name"],
'cover': playlist_data["playlist_info"]["owner"]["images"], 'cover': playlist_data["playlist_info"]["owner"]["images"],
'followers': playlist_data["playlist_info"]["followers"]["total"], 'followers': playlist_data["playlist_info"]["followers"]["total"],
'total_tracks': playlist_data["playlist_info"]["tracks"]["total"] } 'total_tracks': playlist_data["playlist_info"]["tracks"]["total"]
}
self.update_display_after_fetch(metadata) self.update_display_after_fetch(metadata)
def handle_discography_metadata(self, discography_data):
artist_info = discography_data["artist_info"]
self.album_or_playlist_name = f"{artist_info['name']} - Discography ({artist_info['discography_type'].title()})"
self.tracks = []
for track in discography_data["track_list"]:
track_id = track["external_urls"].split("/")[-1] if track.get("external_urls") else ""
self.tracks.append(Track(
external_urls=track.get("external_urls", ""),
title=track["name"],
artists=track["artists"],
album=track["album_name"],
track_number=track.get("track_number", len(self.tracks) + 1),
duration_ms=track.get("duration_ms", 0),
id=track_id,
isrc=track.get("isrc", ""),
release_date=track.get("release_date", "")
))
self.all_tracks = self.tracks.copy()
self.is_playlist = True
self.is_album = self.is_single_track = False
metadata = {
'title': f"{artist_info['name']} - Discography",
'artists': f"{artist_info['discography_type'].title()}{artist_info['total_albums']} albums",
'cover': artist_info["images"],
'followers': artist_info.get("followers", 0),
'total_tracks': len(self.tracks),
'discography_type': artist_info['discography_type']
}
self.update_display_after_fetch(metadata)
def handle_artist_metadata(self, artist_data):
self.reset_state()
metadata = {
'title': artist_data["artist"]["name"],
'artists': f"Followers: {artist_data['artist']['followers']:,}",
'cover': artist_data["artist"]["images"],
'followers': artist_data["artist"]["followers"],
'genres': artist_data["artist"].get("genres", [])
}
self.update_info_widget_artist_only(metadata)
def update_display_after_fetch(self, metadata): def update_display_after_fetch(self, metadata):
self.track_list.setVisible(not self.is_single_track) self.track_list.setVisible(not self.is_single_track)
@@ -1753,12 +1805,40 @@ class SpotiFLACGUI(QWidget):
self.type_label.setText(f"<b>Album</b> • {total_tracks} tracks") self.type_label.setText(f"<b>Album</b> • {total_tracks} tracks")
elif self.is_playlist: elif self.is_playlist:
total_tracks = metadata.get('total_tracks', 0) total_tracks = metadata.get('total_tracks', 0)
self.type_label.setText(f"<b>Playlist</b> • {total_tracks} tracks") if metadata.get('discography_type'):
discography_type = metadata['discography_type'].title()
self.type_label.setText(f"<b>Discography ({discography_type})</b> • {total_tracks} tracks")
else:
self.type_label.setText(f"<b>Playlist</b> • {total_tracks} tracks")
self.network_manager.get(QNetworkRequest(QUrl(metadata['cover']))) self.network_manager.get(QNetworkRequest(QUrl(metadata['cover'])))
self.info_widget.show() self.info_widget.show()
def update_info_widget_artist_only(self, metadata):
self.title_label.setText(metadata['title'])
self.artists_label.setText(f"<b>Followers</b> {metadata['followers']:,}")
if metadata.get('genres'):
genres_text = ", ".join(metadata['genres'][:3])
if len(metadata['genres']) > 3:
genres_text += f" (+{len(metadata['genres']) - 3} more)"
self.followers_label.setText(f"<b>Genres</b> {genres_text}")
self.followers_label.show()
else:
self.followers_label.hide()
self.release_date_label.hide()
self.type_label.setText("<b>Artist Profile</b> • No tracks available for download")
self.network_manager.get(QNetworkRequest(QUrl(metadata['cover'])))
self.track_list.hide()
self.search_widget.hide()
self.hide_track_buttons()
self.info_widget.show()
def reset_info_widget(self): def reset_info_widget(self):
self.title_label.clear() self.title_label.clear()
self.artists_label.clear() self.artists_label.clear()
+218 -1
View File
@@ -57,6 +57,8 @@ token_url = 'https://open.spotify.com/api/token'
playlist_base_url = 'https://api.spotify.com/v1/playlists/{}' playlist_base_url = 'https://api.spotify.com/v1/playlists/{}'
album_base_url = 'https://api.spotify.com/v1/albums/{}' album_base_url = 'https://api.spotify.com/v1/albums/{}'
track_base_url = 'https://api.spotify.com/v1/tracks/{}' track_base_url = 'https://api.spotify.com/v1/tracks/{}'
artist_base_url = 'https://api.spotify.com/v1/artists/{}'
artist_albums_url = 'https://api.spotify.com/v1/artists/{}/albums'
headers = { headers = {
'User-Agent': get_random_user_agent(), 'User-Agent': get_random_user_agent(),
'Accept': 'application/json', 'Accept': 'application/json',
@@ -101,10 +103,18 @@ def parse_uri(uri):
parts = parts[1:] parts = parts[1:]
l = len(parts) l = len(parts)
if l == 3 and parts[1] in ["album", "track", "playlist"]: if l == 3 and parts[1] in ["album", "track", "playlist", "artist"]:
return {"type": parts[1], "id": parts[2]} return {"type": parts[1], "id": parts[2]}
if l == 5 and parts[3] == "playlist": if l == 5 and parts[3] == "playlist":
return {"type": parts[3], "id": parts[4]} return {"type": parts[3], "id": parts[4]}
if l >= 4 and parts[1] == "artist" and len(parts) >= 4:
if parts[3] == "discography":
discography_type = "all"
if len(parts) >= 5 and parts[4] in ["all", "album", "single", "compilation"]:
discography_type = parts[4]
return {"type": "artist_discography", "id": parts[2], "discography_type": discography_type}
else:
return {"type": "artist", "id": parts[2]}
raise SpotifyInvalidUrlException("ERROR: unable to determine Spotify URL type or type is unsupported.") raise SpotifyInvalidUrlException("ERROR: unable to determine Spotify URL type or type is unsupported.")
@@ -340,6 +350,69 @@ def get_raw_spotify_data(spotify_url, batch: bool = False, delay: float = 1.0):
except Exception as e: except Exception as e:
return {"error": f"Failed to get track data: {str(e)}"} return {"error": f"Failed to get track data: {str(e)}"}
elif url_info["type"] == "artist_discography":
try:
artist_data = get_json_from_api(
artist_base_url.format(url_info["id"]),
access_token
)
if not artist_data:
return {"error": "Failed to get artist data"}
discography_type = url_info.get("discography_type", "all")
if discography_type == "all":
include_groups = "album,single,compilation"
else:
include_groups = discography_type
albums = []
albums_url = f'{artist_albums_url.format(url_info["id"])}?include_groups={include_groups}&limit=50'
if batch:
albums, num_batches = fetch_tracks_in_batches(albums_url, access_token, 50, delay)
raw_data = {
"artist_info": artist_data,
"albums": albums,
"discography_type": discography_type,
"_batch_count": num_batches,
"_batch_enabled": True
}
else:
while albums_url:
album_data = get_json_from_api(albums_url, access_token)
if not album_data:
break
albums.extend(album_data['items'])
albums_url = album_data.get('next')
if albums_url and "&locale=" in albums_url:
albums_url = albums_url.split("&locale=")[0]
raw_data = {
"artist_info": artist_data,
"albums": albums,
"discography_type": discography_type,
"_batch_enabled": False
}
raw_data['_token'] = access_token
except Exception as e:
return {"error": f"Failed to get artist discography data: {str(e)}"}
elif url_info["type"] == "artist":
try:
artist_data = get_json_from_api(
artist_base_url.format(url_info["id"]),
access_token
)
if not artist_data:
return {"error": "Failed to get artist data"}
raw_data = artist_data
except Exception as e:
return {"error": f"Failed to get artist data: {str(e)}"}
return raw_data return raw_data
def format_track_data(track_data): def format_track_data(track_data):
@@ -469,6 +542,134 @@ def format_playlist_data(playlist_data):
"track_list": track_list "track_list": track_list
} }
def format_artist_discography_data(discography_data):
artist_info = discography_data.get('artist_info', {})
albums = discography_data.get('albums', [])
access_token = discography_data.get('_token', '')
artist_image = ''
if artist_info.get('images'):
artist_image = artist_info.get('images', [{}])[0].get('url', '')
formatted_artist_info = {
"name": artist_info.get('name', ''),
"followers": artist_info.get('followers', {}).get('total', 0),
"genres": artist_info.get('genres', []),
"images": artist_image,
"external_urls": artist_info.get('external_urls', {}).get('spotify', ''),
"discography_type": discography_data.get('discography_type', 'all'),
"total_albums": len(albums)
}
if discography_data.get('_batch_enabled', False):
formatted_artist_info["batch"] = f"{discography_data.get('_batch_count', 1)}"
album_list = []
all_tracks = []
for album in albums:
album_image = ''
if album.get('images'):
album_image = album.get('images', [{}])[0].get('url', '')
album_artists = []
for artist in album.get('artists', []):
album_artists.append(artist['name'])
album_info = {
"id": album.get('id', ''),
"name": album.get('name', ''),
"album_type": album.get('album_type', ''),
"release_date": album.get('release_date', ''),
"total_tracks": album.get('total_tracks', 0),
"artists": ", ".join(album_artists),
"images": album_image,
"external_urls": album.get('external_urls', {}).get('spotify', '')
}
album_list.append(album_info)
if access_token and album.get('id'):
try:
album_tracks_data = get_json_from_api(
f'{album_base_url.format(album.get("id"))}/tracks?limit=50',
access_token
)
if album_tracks_data:
tracks = []
tracks_url = f'{album_base_url.format(album.get("id"))}/tracks?limit=50'
while tracks_url:
track_data = get_json_from_api(tracks_url, access_token)
if not track_data:
break
tracks.extend(track_data['items'])
tracks_url = track_data.get('next')
if tracks_url and "&locale=" in tracks_url:
tracks_url = tracks_url.split("&locale=")[0]
for track in tracks:
track_artists = []
for artist in track.get('artists', []):
track_artists.append(artist['name'])
track_id = track.get('id', '')
track_isrc = ''
if track_id:
try:
full_track_data = get_json_from_api(
track_base_url.format(track_id),
access_token
)
if full_track_data:
track_isrc = full_track_data.get('external_ids', {}).get('isrc', '')
except:
pass
formatted_track = {
"artists": ", ".join(track_artists),
"name": track.get('name', ''),
"album_name": album.get('name', ''),
"album_type": album.get('album_type', ''),
"duration_ms": track.get('duration_ms', 0),
"images": album_image,
"release_date": album.get('release_date', ''),
"track_number": track.get('track_number', 0),
"external_urls": track.get('external_urls', {}).get('spotify', ''),
"isrc": track_isrc
}
all_tracks.append(formatted_track)
except Exception as e:
print(f"Error getting tracks for album {album.get('name', '')}: {str(e)}")
continue
return {
"artist_info": formatted_artist_info,
"album_list": album_list,
"track_list": all_tracks
}
def format_artist_data(artist_data):
artist_image = ''
if artist_data.get('images'):
artist_image = artist_data.get('images', [{}])[0].get('url', '')
return {
"artist": {
"name": artist_data.get('name', ''),
"followers": artist_data.get('followers', {}).get('total', 0),
"genres": artist_data.get('genres', []),
"images": artist_image,
"external_urls": artist_data.get('external_urls', {}).get('spotify', ''),
"popularity": artist_data.get('popularity', 0)
}
}
def process_spotify_data(raw_data, data_type): def process_spotify_data(raw_data, data_type):
if not raw_data or "error" in raw_data: if not raw_data or "error" in raw_data:
return {"error": "Invalid data provided"} return {"error": "Invalid data provided"}
@@ -480,6 +681,10 @@ def process_spotify_data(raw_data, data_type):
return format_album_data(raw_data) return format_album_data(raw_data)
elif data_type == "playlist": elif data_type == "playlist":
return format_playlist_data(raw_data) return format_playlist_data(raw_data)
elif data_type == "artist_discography":
return format_artist_discography_data(raw_data)
elif data_type == "artist":
return format_artist_data(raw_data)
else: else:
return {"error": "Invalid data type"} return {"error": "Invalid data type"}
except Exception as e: except Exception as e:
@@ -498,11 +703,23 @@ if __name__ == '__main__':
album = "https://open.spotify.com/album/6J84szYCnMfzEcvIcfWMFL" album = "https://open.spotify.com/album/6J84szYCnMfzEcvIcfWMFL"
song = "https://open.spotify.com/track/7so0lgd0zP2Sbgs2d7a1SZ" song = "https://open.spotify.com/track/7so0lgd0zP2Sbgs2d7a1SZ"
artist_discography_all = "https://open.spotify.com/artist/0du5cEVh5yTK9QJze8zA0C/discography/all"
artist_discography_albums = "https://open.spotify.com/artist/0du5cEVh5yTK9QJze8zA0C/discography/album"
artist_discography_singles = "https://open.spotify.com/artist/0du5cEVh5yTK9QJze8zA0C/discography/single"
artist_discography_compilations = "https://open.spotify.com/artist/0du5cEVh5yTK9QJze8zA0C/discography/compilation"
print("=== Testing Artist Discography (All) ===")
filtered_discography = get_filtered_data(artist_discography_all, batch=True, delay=0.1)
print(json.dumps(filtered_discography, indent=2))
print("\n=== Testing Playlist ===")
filtered_playlist = get_filtered_data(playlist, batch=True, delay=0.1) filtered_playlist = get_filtered_data(playlist, batch=True, delay=0.1)
print(json.dumps(filtered_playlist, indent=2)) print(json.dumps(filtered_playlist, indent=2))
print("\n=== Testing Album ===")
filtered_album = get_filtered_data(album) filtered_album = get_filtered_data(album)
print(json.dumps(filtered_album, indent=2)) print(json.dumps(filtered_album, indent=2))
print("\n=== Testing Track ===")
filtered_track = get_filtered_data(song) filtered_track = get_filtered_data(song)
print(json.dumps(filtered_track, indent=2)) print(json.dumps(filtered_track, indent=2))
+1 -1
View File
@@ -144,7 +144,7 @@ class TidalDownloader:
def get_download_url(self, track_id, quality="LOSSLESS"): def get_download_url(self, track_id, quality="LOSSLESS"):
print("Fetching URL...") print("Fetching URL...")
download_api_url = f"https://hifi.401658.xyz/track/?id={track_id}&quality={quality}" download_api_url = f"https://tidal.401658.xyz/track/?id={track_id}&quality={quality}"
try: try:
response = requests.get(download_api_url, timeout=self.timeout) response = requests.get(download_api_url, timeout=self.timeout)