Files
SpotiFLAC/getMetadata.py
T
2025-03-18 07:56:51 +07:00

319 lines
11 KiB
Python

from time import sleep
from urllib.parse import urlparse, parse_qs
import requests
import json
import hmac
import time
import hashlib
from typing import Tuple, Callable
_TOTP_SECRET = bytearray([53,53,48,55,49,52,53,56,53,51,52,56,55,52,57,57,53,57,50,50,52,56,54,51,48,51,50,57,51,52,55])
def generate_totp(
secret: bytes = _TOTP_SECRET,
algorithm: Callable[[], object] = hashlib.sha1,
digits: int = 6,
counter_factory: Callable[[], int] = lambda: int(time.time()) // 30,
) -> Tuple[str, int]:
counter = counter_factory()
hmac_result = hmac.new(
secret, counter.to_bytes(8, byteorder="big"), algorithm
).digest()
offset = hmac_result[-1] & 15
truncated_value = (
(hmac_result[offset] & 127) << 24
| (hmac_result[offset + 1] & 255) << 16
| (hmac_result[offset + 2] & 255) << 8
| (hmac_result[offset + 3] & 255)
)
return (
str(truncated_value % (10**digits)).zfill(digits),
counter * 30_000,
)
token_url = 'https://open.spotify.com/get_access_token'
playlist_base_url = 'https://api.spotify.com/v1/playlists/{}'
album_base_url = 'https://api.spotify.com/v1/albums/{}'
track_base_url = 'https://api.spotify.com/v1/tracks/{}'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Accept': 'application/json',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'Referer': 'https://open.spotify.com/',
'Origin': 'https://open.spotify.com'
}
class SpotifyInvalidUrlException(Exception):
pass
class SpotifyWebsiteParserException(Exception):
pass
def parse_uri(uri):
u = urlparse(uri)
if u.netloc == "embed.spotify.com":
if not u.query:
raise SpotifyInvalidUrlException("ERROR: url {} is not supported".format(uri))
qs = parse_qs(u.query)
return parse_uri(qs['uri'][0])
if not u.scheme and not u.netloc:
return {"type": "playlist", "id": u.path}
if u.scheme == "spotify":
parts = uri.split(":")
else:
if u.netloc != "open.spotify.com" and u.netloc != "play.spotify.com":
raise SpotifyInvalidUrlException("ERROR: url {} is not supported".format(uri))
parts = u.path.split("/")
if parts[1] == "embed":
parts = parts[1:]
l = len(parts)
if l == 3 and parts[1] in ["album", "track", "playlist"]:
return {"type": parts[1], "id": parts[2]}
if l == 5 and parts[3] == "playlist":
return {"type": parts[3], "id": parts[4]}
raise SpotifyInvalidUrlException("ERROR: unable to determine Spotify URL type or type is unsupported.")
def get_json_from_api(api_url, access_token):
headers.update({'Authorization': 'Bearer {}'.format(access_token)})
req = requests.get(api_url, headers=headers, timeout=10)
if req.status_code == 429:
seconds = int(req.headers.get("Retry-After", "5")) + 1
print(f"INFO: rate limited! Sleeping for {seconds} seconds")
sleep(seconds)
return None
if req.status_code != 200:
raise SpotifyWebsiteParserException(f"ERROR: {api_url} gave us not a 200. Instead: {req.status_code}")
return req.json()
def get_raw_spotify_data(spotify_url):
url_info = parse_uri(spotify_url)
try:
totp, timestamp = generate_totp()
params = {
"reason": "init",
"productType": "web-player",
"totp": totp,
"totpVer": 5,
"ts": timestamp,
}
req = requests.get(token_url, headers=headers, params=params, timeout=10)
if req.status_code != 200:
return {"error": f"Failed to get access token. Status code: {req.status_code}"}
token = req.json()
except Exception as e:
return {"error": f"Failed to get access token: {str(e)}"}
raw_data = {}
if url_info['type'] == "playlist":
try:
playlist_data = get_json_from_api(
playlist_base_url.format(url_info["id"]),
token["accessToken"]
)
if not playlist_data:
return {"error": "Failed to get playlist data"}
raw_data = playlist_data
tracks = []
tracks_url = f'https://api.spotify.com/v1/playlists/{url_info["id"]}/tracks?limit=100'
while tracks_url:
track_data = get_json_from_api(tracks_url, token["accessToken"])
if not track_data:
break
tracks.extend(track_data['items'])
tracks_url = track_data.get('next')
raw_data['tracks']['items'] = tracks
except Exception as e:
return {"error": f"Failed to get playlist data: {str(e)}"}
elif url_info["type"] == "album":
try:
album_data = get_json_from_api(
album_base_url.format(url_info["id"]),
token["accessToken"]
)
if not album_data:
return {"error": "Failed to get album data"}
album_data['_token'] = token["accessToken"]
raw_data = album_data
tracks = []
tracks_url = f'{album_base_url.format(url_info["id"])}/tracks?limit=50'
while tracks_url:
track_data = get_json_from_api(tracks_url, token["accessToken"])
if not track_data:
break
tracks.extend(track_data['items'])
tracks_url = track_data.get('next')
raw_data['tracks']['items'] = tracks
except Exception as e:
return {"error": f"Failed to get album data: {str(e)}"}
elif url_info["type"] == "track":
try:
track_data = get_json_from_api(
track_base_url.format(url_info["id"]),
token["accessToken"]
)
if not track_data:
return {"error": "Failed to get track data"}
raw_data = track_data
except Exception as e:
return {"error": f"Failed to get track data: {str(e)}"}
return raw_data
def format_track_data(track_data):
artists = []
for artist in track_data['artists']:
artists.append(artist['name'])
image_url = track_data.get('album', {}).get('images', [{}])[0].get('url', '')
return {
"track": {
"artists": ", ".join(artists),
"name": track_data.get('name', ''),
"album_name": track_data.get('album', {}).get('name', ''),
"duration_ms": track_data.get('duration_ms', 0),
"images": image_url,
"release_date": track_data.get('album', {}).get('release_date', ''),
"track_number": track_data.get('track_number', 0),
"external_urls": track_data.get('external_urls', {}).get('spotify', '')
}
}
def format_album_data(album_data):
artists = []
for artist in album_data['artists']:
artists.append(artist['name'])
image_url = album_data.get('images', [{}])[0].get('url', '')
track_list = []
for track in album_data.get('tracks', {}).get('items', []):
track_artists = []
for artist in track.get('artists', []):
track_artists.append(artist['name'])
track_list.append({
"artists": ", ".join(track_artists),
"name": track.get('name', ''),
"album_name": album_data.get('name', ''),
"duration_ms": track.get('duration_ms', 0),
"images": image_url,
"release_date": album_data.get('release_date', ''),
"track_number": track.get('track_number', 0),
"external_urls": track.get('external_urls', {}).get('spotify', '')
})
return {
"album_info": {
"total_tracks": album_data.get('total_tracks', 0),
"name": album_data.get('name', ''),
"release_date": album_data.get('release_date', ''),
"artists": ", ".join(artists),
"images": image_url
},
"track_list": track_list
}
def format_playlist_data(playlist_data):
image_url = playlist_data.get('images', [{}])[0].get('url', '')
track_list = []
for item in playlist_data.get('tracks', {}).get('items', []):
track = item.get('track', {})
artists = []
for artist in track.get('artists', []):
artists.append(artist['name'])
track_image = track.get('album', {}).get('images', [{}])[0].get('url', '')
track_list.append({
"artists": ", ".join(artists),
"name": track.get('name', ''),
"album_name": track.get('album', {}).get('name', ''),
"duration_ms": track.get('duration_ms', 0),
"images": track_image,
"release_date": track.get('album', {}).get('release_date', ''),
"track_number": track.get('track_number', 0),
"external_urls": track.get('external_urls', {}).get('spotify', '')
})
return {
"playlist_info": {
"tracks": {"total": playlist_data.get('tracks', {}).get('total', 0)},
"followers": {"total": playlist_data.get('followers', {}).get('total', 0)},
"owner": {
"display_name": playlist_data.get('owner', {}).get('display_name', ''),
"name": playlist_data.get('name', ''),
"images": image_url
}
},
"track_list": track_list
}
def process_spotify_data(raw_data, data_type):
if not raw_data or "error" in raw_data:
return {"error": "Invalid data provided"}
try:
if data_type == "track":
return format_track_data(raw_data)
elif data_type == "album":
return format_album_data(raw_data)
elif data_type == "playlist":
return format_playlist_data(raw_data)
else:
return {"error": "Invalid data type"}
except Exception as e:
return {"error": f"Error processing data: {str(e)}"}
def get_filtered_data(spotify_url):
raw_data = get_raw_spotify_data(spotify_url)
if raw_data and "error" not in raw_data:
url_info = parse_uri(spotify_url)
filtered_data = process_spotify_data(raw_data, url_info['type'])
return filtered_data
return {"error": "Failed to get raw data"}
if __name__ == '__main__':
playlist = "https://open.spotify.com/playlist/37i9dQZEVXbNG2KDcFcKOF"
album = "https://open.spotify.com/album/7kFyd5oyJdVX2pIi6P4iHE"
song = "https://open.spotify.com/track/4wJ5Qq0jBN4ajy7ouZIV1c"
filtered_playlist = get_filtered_data(playlist)
print(json.dumps(filtered_playlist, indent=2))
filtered_album = get_filtered_data(album)
print(json.dumps(filtered_album, indent=2))
filtered_track = get_filtered_data(song)
print(json.dumps(filtered_track, indent=2))