Update v2.2
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
<b>SpotiFLAC</b> allows you to download Spotify tracks in true FLAC format through services like Tidal, Amazon Music and Deezer with the help of Lucida.
|
<b>SpotiFLAC</b> allows you to download Spotify tracks in true FLAC format through services like Tidal, Amazon Music and Deezer with the help of Lucida.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### [Download](https://github.com/afkarxyz/SpotiFLAC/releases/download/v2.1/SpotiFLAC.exe)
|
### [Download](https://github.com/afkarxyz/SpotiFLAC/releases/download/v2.2/SpotiFLAC.exe)
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|||||||
+870
-524
File diff suppressed because it is too large
Load Diff
+319
@@ -0,0 +1,319 @@
|
|||||||
|
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))
|
||||||
+119
-10
@@ -2,6 +2,8 @@ import requests
|
|||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import re
|
||||||
|
import base64
|
||||||
|
|
||||||
class TrackDownloader:
|
class TrackDownloader:
|
||||||
def __init__(self, use_fallback=False):
|
def __init__(self, use_fallback=False):
|
||||||
@@ -13,7 +15,6 @@ class TrackDownloader:
|
|||||||
self.filename_format = 'title_artist'
|
self.filename_format = 'title_artist'
|
||||||
self.use_fallback = use_fallback
|
self.use_fallback = use_fallback
|
||||||
self.base_domain = "lucida.su" if use_fallback else "lucida.to"
|
self.base_domain = "lucida.su" if use_fallback else "lucida.to"
|
||||||
self.api_base = "https://apislucida.vercel.app"
|
|
||||||
|
|
||||||
def set_progress_callback(self, callback):
|
def set_progress_callback(self, callback):
|
||||||
self.progress_callback = callback
|
self.progress_callback = callback
|
||||||
@@ -32,15 +33,121 @@ class TrackDownloader:
|
|||||||
if use_fallback is None:
|
if use_fallback is None:
|
||||||
use_fallback = self.use_fallback
|
use_fallback = self.use_fallback
|
||||||
|
|
||||||
fallback = "su" if use_fallback else "to"
|
domain_type = "su" if use_fallback else "to"
|
||||||
api_url = f"{self.api_base}/{fallback}/{track_id}/{service}"
|
|
||||||
|
spotify_url = f"https://open.spotify.com/track/{track_id}"
|
||||||
|
|
||||||
|
result = self.convert_spotify_link(spotify_url, service, domain_type)
|
||||||
|
|
||||||
|
if "error" in result:
|
||||||
|
raise Exception(f"Failed to get track info: {result['error']}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def convert_spotify_link(self, spotify_url, target_service="amazon", domain_type="to"):
|
||||||
|
track_id_match = re.search(r'track/([a-zA-Z0-9]+)', spotify_url)
|
||||||
|
if not track_id_match:
|
||||||
|
return {"error": "Invalid Spotify URL"}
|
||||||
|
|
||||||
|
domain = "lucida.to" if domain_type == "to" else "lucida.su"
|
||||||
|
base_url = f"https://{domain}"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||||
|
"Accept-Language": "id-ID,id;q=0.9",
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"Host": domain,
|
||||||
|
"Pragma": "no-cache",
|
||||||
|
"Upgrade-Insecure-Requests": "1",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.get(api_url)
|
headers["Referer"] = f"{base_url}/?url={spotify_url}&country=auto"
|
||||||
response.raise_for_status()
|
|
||||||
return response.json()
|
request_params = {
|
||||||
except requests.exceptions.RequestException as e:
|
"url": spotify_url,
|
||||||
raise Exception(f"Failed to get track info: {str(e)}")
|
"country": "auto",
|
||||||
|
"to": target_service
|
||||||
|
}
|
||||||
|
|
||||||
|
session = requests.Session()
|
||||||
|
session.verify = False
|
||||||
|
|
||||||
|
response = session.get(
|
||||||
|
base_url,
|
||||||
|
params=request_params,
|
||||||
|
headers=headers,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
html_content = response.text
|
||||||
|
|
||||||
|
token_match = re.search(r'token:"([^"]+)"', html_content)
|
||||||
|
token_expiry_match = re.search(r'tokenExpiry:(\d+)', html_content)
|
||||||
|
|
||||||
|
token = token_match.group(1) if token_match else None
|
||||||
|
token_expiry = int(token_expiry_match.group(1)) if token_expiry_match else None
|
||||||
|
|
||||||
|
url = None
|
||||||
|
url_patterns = [
|
||||||
|
r'"url":"([^"]+)"',
|
||||||
|
r'href="(https?://[^"]*' + re.escape(target_service) + r'[^"]*track[^"]*)"',
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in url_patterns:
|
||||||
|
url_match = re.search(pattern, html_content)
|
||||||
|
if url_match:
|
||||||
|
url = url_match.group(1).replace('\\/', '/')
|
||||||
|
break
|
||||||
|
|
||||||
|
if not url:
|
||||||
|
redirect_patterns = [
|
||||||
|
r'url=([^&"]+)',
|
||||||
|
r'href="([^"]+)"',
|
||||||
|
r'window\.location\.href\s*=\s*[\'"]([^\'"]+)[\'"]',
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in redirect_patterns:
|
||||||
|
matches = re.finditer(pattern, html_content)
|
||||||
|
for match in matches:
|
||||||
|
potential_url = match.group(1)
|
||||||
|
if potential_url.startswith('http') and target_service.lower() in potential_url.lower():
|
||||||
|
url = potential_url.replace('\\/', '/')
|
||||||
|
break
|
||||||
|
|
||||||
|
if not url:
|
||||||
|
service_urls = re.finditer(r'(https?://[^"\s]+' + re.escape(target_service) + r'[^"\s]+)', html_content)
|
||||||
|
for match in service_urls:
|
||||||
|
url = match.group(1).replace('\\/', '/')
|
||||||
|
break
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"service": target_service,
|
||||||
|
"url": url,
|
||||||
|
"token": {
|
||||||
|
"primary": None,
|
||||||
|
"expiry": None
|
||||||
|
},
|
||||||
|
"title": "Title",
|
||||||
|
"artists": "Artist"
|
||||||
|
}
|
||||||
|
|
||||||
|
if token:
|
||||||
|
try:
|
||||||
|
decoded_once = base64.b64decode(token).decode('latin1')
|
||||||
|
decoded_token = base64.b64decode(decoded_once).decode('latin1')
|
||||||
|
result["token"]["primary"] = decoded_token
|
||||||
|
except Exception:
|
||||||
|
result["token"]["primary"] = token
|
||||||
|
|
||||||
|
result["token"]["expiry"] = token_expiry
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
return {"error": str(error)}
|
||||||
|
|
||||||
def sanitize_filename(self, filename):
|
def sanitize_filename(self, filename):
|
||||||
invalid_chars = '<>:"/\\|?*'
|
invalid_chars = '<>:"/\\|?*'
|
||||||
@@ -177,7 +284,9 @@ class TrackDownloader:
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
downloader = TrackDownloader()
|
use_fallback = False
|
||||||
|
downloader = TrackDownloader(use_fallback)
|
||||||
|
|
||||||
output_dir = "."
|
output_dir = "."
|
||||||
track_id = "2plbrEY59IikOBgBGLjaoe"
|
track_id = "2plbrEY59IikOBgBGLjaoe"
|
||||||
service = "amazon"
|
service = "amazon"
|
||||||
@@ -192,7 +301,7 @@ async def main():
|
|||||||
try:
|
try:
|
||||||
print(f"Getting track info for ID: {track_id} from {service}")
|
print(f"Getting track info for ID: {track_id} from {service}")
|
||||||
metadata = await downloader.get_track_info(track_id, service)
|
metadata = await downloader.get_track_info(track_id, service)
|
||||||
print(f"Track info received: {metadata['title']} by {metadata['artists']}")
|
print(f"Track info received, starting download process")
|
||||||
|
|
||||||
downloaded_file = downloader.download(metadata, output_dir)
|
downloaded_file = downloader.download(metadata, output_dir)
|
||||||
print(f"\nFile downloaded successfully: {downloaded_file}")
|
print(f"\nFile downloaded successfully: {downloaded_file}")
|
||||||
|
|||||||
Reference in New Issue
Block a user