clean code; fix time format

This commit is contained in:
vitiko98 2020-10-17 12:52:35 -04:00
parent a6ad33ad7d
commit 68caf02c31
8 changed files with 238 additions and 205 deletions

106
main.py
View File

@ -1,35 +1,46 @@
from qo_utils.search import Search
from qo_utils import downloader
from pick import pick
import argparse
import itertools
import re
import os
import sys
import json
import os
import re
import sys
from pick import pick
import qopy
from qo_utils import downloader
from qo_utils.search import Search
def getArgs():
parser = argparse.ArgumentParser(prog='python3 main.py')
parser.add_argument("-a", action="store_true",
help="enable albums-only search")
parser.add_argument("-i", action="store_true",
help="run Qo-Dl-curses on URL input mode")
parser.add_argument("-q", metavar="int", default=6,
help="quality (5, 6, 7, 27) (default: 6)")
parser.add_argument("-l", metavar="int", default=10,
help="limit of search results by type (default: 10)")
parser.add_argument("-d", metavar="PATH", default='Qobuz Downloads',
help="custom directory for downloads")
parser = argparse.ArgumentParser(prog="python3 main.py")
parser.add_argument("-a", action="store_true", help="enable albums-only search")
parser.add_argument(
"-i", action="store_true", help="run Qo-Dl-curses on URL input mode"
)
parser.add_argument(
"-q", metavar="int", default=6, help="quality (5, 6, 7, 27) (default: 6)"
)
parser.add_argument(
"-l",
metavar="int",
default=10,
help="limit of search results by type (default: 10)",
)
parser.add_argument(
"-d",
metavar="PATH",
default="Qobuz Downloads",
help="custom directory for downloads",
)
return parser.parse_args()
def getSession():
print('Logging...')
with open('config.json') as f:
print("Logging...")
with open("config.json") as f:
config = json.load(f)
return qopy.Client(config['email'], config['password'])
return qopy.Client(config["email"], config["password"])
def musicDir(dir):
@ -40,13 +51,16 @@ def musicDir(dir):
def get_id(url):
return re.match(r'https?://(?:w{0,3}|play|open)\.qobuz\.com/(?:(?'
':album|track)/|[a-z]{2}-[a-z]{2}/album/-?\w+(?:-\w+)'
'*-?/|user/library/favorites/)(\w+)', url).group(1)
return re.match(
r"https?://(?:w{0,3}|play|open)\.qobuz\.com/(?:(?"
":album|track)/|[a-z]{2}-[a-z]{2}/album/-?\w+(?:-\w+)"
"*-?/|user/library/favorites/)(\w+)",
url,
).group(1)
def searchSelected(Qz, path, albums, ids, types, quality):
q = ['5', '6', '7', '27']
q = ["5", "6", "7", "27"]
quality = q[quality[1]]
for alb, id_, type_ in zip(albums, ids, types):
for al in alb:
@ -57,7 +71,7 @@ def searchSelected(Qz, path, albums, ids, types, quality):
def fromUrl(Qz, path, link, quality):
if '/track/' in link:
if "/track/" in link:
id = get_id(link)
downloader.iterateIDs(Qz, id, path, quality, False)
else:
@ -71,32 +85,37 @@ def interactive(Qz, path, limit, tracks=True):
try:
while True:
query = input("\nEnter your search: [Ctrl + c to quit]\n- ")
print('Searching...')
print("Searching...")
start = Search(Qz, query, limit)
start.getResults(tracks)
Types.append(start.Types)
IDs.append(start.IDs)
title = ('Select [space] the item(s) you want to download '
'(one or more)\nPress Ctrl + c to quit\n')
Selected = pick(start.Total, title,
multiselect=True, min_selection_count=1)
title = (
"Select [space] the item(s) you want to download "
"(one or more)\nPress Ctrl + c to quit\n"
)
Selected = pick(
start.Total, title, multiselect=True, min_selection_count=1
)
Albums.append(Selected)
y_n = pick(['Yes', 'No'], 'Items were added to queue to '
'be downloaded. Keep searching?')
if y_n[0][0] == 'N':
y_n = pick(
["Yes", "No"],
"Items were added to queue to be downloaded. Keep searching?",
)
if y_n[0][0] == "N":
break
else:
pass
desc = ('Select [intro] the quality (the quality will be automat'
'ically\ndowngraded if the selected is not found)')
Qualits = ['320', 'Lossless', 'Hi-res =< 96kHz', 'Hi-Res > 96 kHz']
desc = (
"Select [intro] the quality (the quality will be automat"
"ically\ndowngraded if the selected is not found)"
)
Qualits = ["320", "Lossless", "Hi-res =< 96kHz", "Hi-Res > 96 kHz"]
quality = pick(Qualits, desc)
searchSelected(Qz, path, Albums, IDs, Types, quality)
except KeyboardInterrupt:
sys.exit('\nBye')
sys.exit("\nBye")
def inputMode(Qz, path, quality):
@ -105,18 +124,15 @@ def inputMode(Qz, path, quality):
link = input("\nAlbum/track URL: [Ctrl + c to quit]\n- ")
fromUrl(Qz, path, link, quality)
except KeyboardInterrupt:
sys.exit('\nBye')
sys.exit("\nBye")
def main():
arguments = getArgs()
directory = musicDir(arguments.d) + '/'
directory = musicDir(arguments.d) + "/"
Qz = getSession()
if not arguments.i:
if arguments.a:
interactive(Qz, directory, arguments.l, False)
else:
interactive(Qz, directory, arguments.l, True)
interactive(Qz, directory, arguments.l, not arguments.a)
else:
inputMode(Qz, directory, arguments.q)

View File

@ -1,20 +1,22 @@
import os
import requests
from qo_utils import metadata
from pathvalidate import sanitize_filename
from tqdm import tqdm
from qo_utils import metadata
def req_tqdm(url, fname, track_name):
r = requests.get(url, allow_redirects=True, stream=True)
total = int(r.headers.get('content-length', 0))
with open(fname, 'wb') as file, tqdm(
total = int(r.headers.get("content-length", 0))
with open(fname, "wb") as file, tqdm(
total=total,
unit='iB',
unit="iB",
unit_scale=True,
unit_divisor=1024,
desc=track_name,
bar_format='{n_fmt}/{total_fmt} /// {desc}',
bar_format="{n_fmt}/{total_fmt} /// {desc}",
) as bar:
for data in r.iter_content(chunk_size=1024):
size = file.write(data)
@ -25,25 +27,25 @@ def mkDir(dirn):
try:
os.mkdir(dirn)
except FileExistsError:
print('Warning: folder already exists. Overwriting...')
print("Warning: folder already exists. Overwriting...")
def getDesc(u, mt):
return '{} [{}/{}]'.format(mt['title'], u['bit_depth'], u['sampling_rate'])
return "{} [{}/{}]".format(mt["title"], u["bit_depth"], u["sampling_rate"])
def getCover(i, dirn):
req_tqdm(i, dirn + '/cover.jpg', 'Downloading cover art')
req_tqdm(i, dirn + "/cover.jpg", "Downloading cover art")
# Download and tag a file
def downloadItem(dirn, count, parse, meta, album, url, is_track, mp3):
if mp3:
fname = '{}/{:02}.mp3'.format(dirn, count)
func = metadata.tag_mp3
else:
fname = '{}/{:02}.flac'.format(dirn, count)
func = metadata.tag_flac
fname = (
"{}/{:02}.mp3".format(dirn, count)
if mp3
else "{}/{:02}.flac".format(dirn, count)
)
func = metadata.tag_mp3 if mp3 else metadata.tag_flac
desc = getDesc(parse, meta)
req_tqdm(url, fname, desc)
func(fname, dirn, meta, album, is_track)
@ -55,46 +57,43 @@ def iterateIDs(client, id, path, quality, album=False):
if album:
meta = client.get_album_meta(id)
print('\nDownloading: {}\n'.format(meta['title']))
dirT = (meta['artist']['name'],
meta['title'],
meta['release_date_original'].split('-')[0])
sanitized_title = sanitize_filename('{} - {} [{}]'.format(*dirT))
print("\nDownloading: {}\n".format(meta["title"]))
dirT = (
meta["artist"]["name"],
meta["title"],
meta["release_date_original"].split("-")[0],
)
sanitized_title = sanitize_filename("{} - {} [{}]".format(*dirT))
dirn = path + sanitized_title
mkDir(dirn)
getCover(meta['image']['large'], dirn)
for i in meta['tracks']['items']:
parse = client.get_track_url(i['id'], quality)
url = parse['url']
if 'sample' not in parse:
if int(quality) == 5:
downloadItem(dirn, count, parse, i, meta, url, False, True)
getCover(meta["image"]["large"], dirn)
for i in meta["tracks"]["items"]:
parse = client.get_track_url(i["id"], quality)
url = parse["url"]
if "sample" not in parse:
is_mp3 = True if int(quality) == 5 else False
downloadItem(dirn, count, parse, i, meta, url, False, is_mp3)
else:
downloadItem(dirn, count, parse, i, meta, url, False, False)
else:
print('Demo. Skipping')
print("Demo. Skipping")
count = count + 1
else:
parse = client.get_track_url(id, quality)
url = parse['url']
url = parse["url"]
if 'sample' not in parse:
if "sample" not in parse:
meta = client.get_track_meta(id)
print('\nDownloading: {}\n'.format(meta['title']))
dirT = (meta['album']['artist']['name'],
meta['title'],
meta['album']['release_date_original'].split('-')[0])
sanitized_title = sanitize_filename('{} - {} [{}]'.format(*dirT))
print("\nDownloading: {}\n".format(meta["title"]))
dirT = (
meta["album"]["artist"]["name"],
meta["title"],
meta["album"]["release_date_original"].split("-")[0],
)
sanitized_title = sanitize_filename("{} - {} [{}]".format(*dirT))
dirn = path + sanitized_title
mkDir(dirn)
getCover(meta['album']['image']['large'], dirn)
if int(quality) == 5:
downloadItem(dirn, count, parse, meta, meta, url, True, True)
getCover(meta["album"]["image"]["large"], dirn)
is_mp3 = True if int(quality) == 5 else False
downloadItem(dirn, count, parse, meta, meta, url, True, is_mp3)
else:
downloadItem(dirn, count, parse, meta, meta, url, True, False)
else:
print('Demo. Skipping')
print('\nCompleted\n')
print("Demo. Skipping")
print("\nCompleted\n")

View File

@ -1,73 +1,74 @@
import os
from mutagen.flac import FLAC
from mutagen.mp3 import EasyMP3
from pathvalidate import sanitize_filename
import os
def tag_flac(file, path, d, album, istrack=True):
audio = FLAC(file)
audio['TITLE'] = d['title'] # TRACK TITLE
audio['TRACKNUMBER'] = str(d['track_number']) # TRACK NUMBER
audio["TITLE"] = d["title"] # TRACK TITLE
audio["TRACKNUMBER"] = str(d["track_number"]) # TRACK NUMBER
try:
audio['COMPOSER'] = d['composer']['name'] # COMPOSER
audio["COMPOSER"] = d["composer"]["name"] # COMPOSER
except KeyError:
pass
try:
audio['ARTIST'] = d['performer']['name'] # TRACK ARTIST
audio["ARTIST"] = d["performer"]["name"] # TRACK ARTIST
except KeyError:
if istrack:
audio['ARTIST'] = d['album']['artist']['name'] # TRACK ARTIST
audio["ARTIST"] = d["album"]["artist"]["name"] # TRACK ARTIST
else:
audio['ARTIST'] = album['artist']['name']
audio["ARTIST"] = album["artist"]["name"]
if istrack:
audio['GENRE'] = ', '.join(d['album']['genres_list']) # GENRE
audio['ALBUMARTIST'] = d['album']['artist']['name'] # ALBUM ARTIST
audio['TRACKTOTAL'] = str(d['album']['tracks_count']) # TRACK TOTAL
audio['ALBUM'] = d['album']['title'] # ALBUM TITLE
audio['YEAR'] = d['album']['release_date_original'].split('-')[0]
audio["GENRE"] = ", ".join(d["album"]["genres_list"]) # GENRE
audio["ALBUMARTIST"] = d["album"]["artist"]["name"] # ALBUM ARTIST
audio["TRACKTOTAL"] = str(d["album"]["tracks_count"]) # TRACK TOTAL
audio["ALBUM"] = d["album"]["title"] # ALBUM TITLE
audio["YEAR"] = d["album"]["release_date_original"].split("-")[0]
else:
audio['GENRE'] = ', '.join(album['genres_list']) # GENRE
audio['ALBUMARTIST'] = album['artist']['name'] # ALBUM ARTIST
audio['TRACKTOTAL'] = str(album['tracks_count']) # TRACK TOTAL
audio['ALBUM'] = album['title'] # ALBUM TITLE
audio['YEAR'] = album['release_date_original'].split('-')[0] # YEAR
audio["GENRE"] = ", ".join(album["genres_list"]) # GENRE
audio["ALBUMARTIST"] = album["artist"]["name"] # ALBUM ARTIST
audio["TRACKTOTAL"] = str(album["tracks_count"]) # TRACK TOTAL
audio["ALBUM"] = album["title"] # ALBUM TITLE
audio["YEAR"] = album["release_date_original"].split("-")[0] # YEAR
audio.save()
title = sanitize_filename(d['title'])
os.rename(file, '{}/{:02}. {}.flac'.format(path, d['track_number'], title))
title = sanitize_filename(d["title"])
os.rename(file, "{}/{:02}. {}.flac".format(path, d["track_number"], title))
def tag_mp3(file, path, d, album, istrack=True):
audio = EasyMP3(file)
audio['title'] = d['title']
audio['tracknumber'] = str(d['track_number'])
audio["title"] = d["title"]
audio["tracknumber"] = str(d["track_number"])
try:
audio['composer'] = d['composer']['name']
audio["composer"] = d["composer"]["name"]
except KeyError:
pass
try:
audio['artist'] = d['performer']['name'] # TRACK ARTIST
audio["artist"] = d["performer"]["name"] # TRACK ARTIST
except KeyError:
if istrack:
audio['artist'] = d['album']['artist']['name'] # TRACK ARTIST
audio["artist"] = d["album"]["artist"]["name"] # TRACK ARTIST
else:
audio['artist'] = album['artist']['name']
audio["artist"] = album["artist"]["name"]
if istrack:
audio['genre'] = ', '.join(d['album']['genres_list']) # GENRE
audio['albumartist'] = d['album']['artist']['name'] # ALBUM ARTIST
audio['album'] = d['album']['title'] # ALBUM TITLE
audio['date'] = d['album']['release_date_original'].split('-')[0]
audio["genre"] = ", ".join(d["album"]["genres_list"]) # GENRE
audio["albumartist"] = d["album"]["artist"]["name"] # ALBUM ARTIST
audio["album"] = d["album"]["title"] # ALBUM TITLE
audio["date"] = d["album"]["release_date_original"].split("-")[0]
else:
audio['GENRE'] = ', '.join(album['genres_list']) # GENRE
audio['albumartist'] = album['artist']['name'] # ALBUM ARTIST
audio['album'] = album['title'] # ALBUM TITLE
audio['date'] = album['release_date_original'].split('-')[0] # YEAR
audio["GENRE"] = ", ".join(album["genres_list"]) # GENRE
audio["albumartist"] = album["artist"]["name"] # ALBUM ARTIST
audio["album"] = album["title"] # ALBUM TITLE
audio["date"] = album["release_date_original"].split("-")[0] # YEAR
audio.save()
title = sanitize_filename(d['title'])
os.rename(file, '{}/{:02}. {}.mp3'.format(path, d['track_number'], title))
title = sanitize_filename(d["title"])
os.rename(file, "{}/{:02}. {}.mp3".format(path, d["track_number"], title))

View File

@ -6,33 +6,35 @@ class Search:
self.Total = []
self.IDs = []
self.Types = []
self.Tracks = Qz.search_tracks(query, limit)['tracks']['items']
self.Albums = Qz.search_albums(query, limit)['albums']['items']
self.Tracks = Qz.search_tracks(query, limit)["tracks"]["items"]
self.Albums = Qz.search_albums(query, limit)["albums"]["items"]
def seconds(self, duration):
return time.strftime("%M:%S", time.gmtime(duration))
def isHRes(self, item):
if item:
return 'HI-RES'
else:
return 'Lossless'
return time.strftime("%H:%M:%S", time.gmtime(duration))
def appendInfo(self, i, bool):
self.IDs.append(i['id'])
self.IDs.append(i["id"])
self.Types.append(bool)
def itResults(self, iterable):
for i in iterable:
try:
items = (i['artist']['name'], i['title'],
self.seconds(i['duration']), self.isHRes(i['hires']))
self.Total.append('[RELEASE] {} - {} - {} [{}]'.format(*items))
items = (
i["artist"]["name"],
i["title"],
self.seconds(i["duration"]),
"HI-RES" if i["hires"] else "Lossless",
)
self.Total.append("[RELEASE] {} - {} - {} [{}]".format(*items))
self.appendInfo(i, True)
except KeyError:
items = (i['performer']['name'], i['title'],
self.seconds(i['duration']), self.isHRes(i['hires']))
self.Total.append('[TRACK] {} - {} - {} [{}]'.format(*items))
items = (
i["performer"]["name"],
i["title"],
self.seconds(i["duration"]),
"HI-RES" if i["hires"] else "Lossless",
)
self.Total.append("[TRACK] {} - {} - {} [{}]".format(*items))
self.appendInfo(i, False)
def getResults(self, tracks=False):

View File

@ -1,10 +1,11 @@
import requests
import base64
import re
from collections import OrderedDict
class Spoofer:
import requests
class Spoofer:
def __init__(self):
self.seed_timezone_regex = r'[a-z]\.initialSeed\("(?P<seed>[\w=]+)",window\.utimezone\.(?P<timezone>[a-z]+)\)'
# note: {timezones} should be replaced with every capitalized timezone joined by a |
@ -12,8 +13,10 @@ class Spoofer:
self.appId_regex = r'{app_id:"(?P<app_id>\d{9})",app_secret:"\w{32}",base_port:"80",base_url:"https://www\.qobuz\.com",base_method:"/api\.json/0\.2/"},n\.base_url="https://play\.qobuz\.com"'
login_page_request = requests.get("https://play.qobuz.com/login")
login_page = login_page_request.text
bundle_url_match = re.search(r'<script src="(/resources/\d+\.\d+\.\d+-[a-z]\d{3}/bundle\.js)"></script>',
login_page)
bundle_url_match = re.search(
r'<script src="(/resources/\d+\.\d+\.\d+-[a-z]\d{3}/bundle\.js)"></script>',
login_page,
)
bundle_url = bundle_url_match.group(1)
bundle_req = requests.get("https://play.qobuz.com" + bundle_url)
self.bundle = bundle_req.text
@ -34,7 +37,9 @@ class Spoofer:
"""
keypairs = list(secrets.items())
secrets.move_to_end(keypairs[1][0], last=False)
info_extras_regex = self.info_extras_regex.format(timezones="|".join([timezone.capitalize() for timezone in secrets]))
info_extras_regex = self.info_extras_regex.format(
timezones="|".join([timezone.capitalize() for timezone in secrets])
)
info_extras_matches = re.finditer(info_extras_regex, self.bundle)
for match in info_extras_matches:
timezone, info, extras = match.group("timezone", "info", "extras")

View File

@ -1,11 +1,14 @@
class AuthenticationError(Exception):
pass
class IneligibleError(Exception):
pass
class InvalidAppIdError(Exception):
pass
class InvalidAppSecretError(Exception):
pass

View File

@ -2,111 +2,118 @@
# of qopy, originally written by Sorrow446. All credits to the
# original author.
import time
import hashlib
import requests
from qo_utils import spoofbuz
import time
from qopy.exceptions import AuthenticationError, IneligibleError, InvalidAppSecretError, InvalidAppIdError
import requests
from qo_utils import spoofbuz
from qopy.exceptions import (
AuthenticationError,
IneligibleError,
InvalidAppIdError,
InvalidAppSecretError,
)
class Client:
def __init__(self, email, pwd):
print('Getting tokens...')
print("Getting tokens...")
self.spoofer = spoofbuz.Spoofer()
self.id = self.spoofer.getAppId()
self.session = requests.Session()
self.session.headers.update({
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0',
"X-App-Id": self.id})
self.base = 'https://www.qobuz.com/api.json/0.2/'
self.session.headers.update(
{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0",
"X-App-Id": self.id,
}
)
self.base = "https://www.qobuz.com/api.json/0.2/"
self.auth(email, pwd)
self.cfg_setup()
def api_call(self, epoint, **kwargs):
if epoint == "user/login?":
params = {
"email": kwargs['email'],
"password": kwargs['pwd'],
"app_id": self.id}
"email": kwargs["email"],
"password": kwargs["pwd"],
"app_id": self.id,
}
elif epoint == "track/get?":
params={
"track_id": kwargs['id']}
params = {"track_id": kwargs["id"]}
elif epoint == "album/get?":
params={
"album_id": kwargs['id']}
params = {"album_id": kwargs["id"]}
elif epoint == "track/search?":
params={
"query": kwargs['query'],
"limit": kwargs['limit']}
params = {"query": kwargs["query"], "limit": kwargs["limit"]}
elif epoint == "album/search?":
params={
"query": kwargs['query'],
"limit": kwargs['limit']}
params = {"query": kwargs["query"], "limit": kwargs["limit"]}
elif epoint == "userLibrary/getAlbumsList?":
unix = time.time()
r_sig = "userLibrarygetAlbumsList" + str(unix) + kwargs['sec']
r_sig_hashed = hashlib.md5(r_sig.encode('utf-8')).hexdigest()
r_sig = "userLibrarygetAlbumsList" + str(unix) + kwargs["sec"]
r_sig_hashed = hashlib.md5(r_sig.encode("utf-8")).hexdigest()
params = {
"app_id": self.id,
"user_auth_token": self.uat,
"request_ts": unix,
"request_sig": r_sig_hashed}
"request_sig": r_sig_hashed,
}
elif epoint == "track/getFileUrl?":
unix = time.time()
track_id = kwargs['id']
fmt_id = kwargs['fmt_id']
r_sig = "trackgetFileUrlformat_id{}intentstreamtrack_id{}{}{}".format(fmt_id, track_id, unix, self.sec)
r_sig_hashed = hashlib.md5(r_sig.encode('utf-8')).hexdigest()
track_id = kwargs["id"]
fmt_id = kwargs["fmt_id"]
r_sig = "trackgetFileUrlformat_id{}intentstreamtrack_id{}{}{}".format(
fmt_id, track_id, unix, self.sec
)
r_sig_hashed = hashlib.md5(r_sig.encode("utf-8")).hexdigest()
params = {
"request_ts": unix,
"request_sig": r_sig_hashed,
"track_id": track_id,
"format_id": fmt_id,
"intent": 'stream'}
"intent": "stream",
}
r = self.session.get(self.base + epoint, params=params)
# Do ref header.
if epoint == "user/login?":
if r.status_code == 401:
raise AuthenticationError('Invalid credentials.')
raise AuthenticationError("Invalid credentials.")
elif r.status_code == 400:
raise InvalidAppIdError('Invalid app id.')
raise InvalidAppIdError("Invalid app id.")
else:
print('Logged: OK')
print("Logged: OK")
elif epoint in ["track/getFileUrl?", "userLibrary/getAlbumsList?"]:
if r.status_code == 400:
raise InvalidAppSecretError('Invalid app secret.')
raise InvalidAppSecretError("Invalid app secret.")
r.raise_for_status()
return r.json()
def auth(self, email, pwd):
usr_info = self.api_call('user/login?', email=email, pwd=pwd)
if not usr_info['user']['credential']['parameters']:
usr_info = self.api_call("user/login?", email=email, pwd=pwd)
if not usr_info["user"]["credential"]["parameters"]:
raise IneligibleError("Free accounts are not eligible to download tracks.")
self.uat = usr_info['user_auth_token']
self.session.headers.update({
"X-User-Auth-Token": self.uat})
self.label = usr_info['user']['credential']['parameters']['short_label']
print('Membership: {}'.format(self.label))
self.uat = usr_info["user_auth_token"]
self.session.headers.update({"X-User-Auth-Token": self.uat})
self.label = usr_info["user"]["credential"]["parameters"]["short_label"]
print("Membership: {}".format(self.label))
def get_album_meta(self, id):
return self.api_call('album/get?', id=id)
return self.api_call("album/get?", id=id)
def get_track_meta(self, id):
return self.api_call('track/get?', id=id)
return self.api_call("track/get?", id=id)
def get_track_url(self, id, fmt_id):
return self.api_call('track/getFileUrl?', id=id, fmt_id=fmt_id)
return self.api_call("track/getFileUrl?", id=id, fmt_id=fmt_id)
def search_albums(self, query, limit):
return self.api_call('album/search?', query=query, limit=limit)
return self.api_call("album/search?", query=query, limit=limit)
def search_tracks(self, query, limit):
return self.api_call('track/search?', query=query, limit=limit)
return self.api_call("track/search?", query=query, limit=limit)
def test_secret(self, sec):
try:
r = self.api_call('userLibrary/getAlbumsList?', sec=sec)
r = self.api_call("userLibrary/getAlbumsList?", sec=sec)
return True
except InvalidAppSecretError:
return False