This commit is contained in:
lars 2022-11-06 18:10:00 +01:00
parent a59244e82d
commit c37fa68937
4 changed files with 446 additions and 480 deletions

View File

@ -17,7 +17,8 @@ NOT_A_GENRE = ".", "..", "misc_scripts", "Music", "script", ".git", ".idea"
MUSIC_DIR = os.path.expanduser('~/Music') MUSIC_DIR = os.path.expanduser('~/Music')
TOR = False TOR = False
logging.basicConfig(level=logging.INFO) logger = logging.getLogger()
logger.level = logging.DEBUG
def get_existing_genre(): def get_existing_genre():
@ -102,4 +103,4 @@ def cli(start_at: int = 0):
if __name__ == "__main__": if __name__ == "__main__":
cli(start_at=2) cli(start_at=0)

View File

@ -4,63 +4,53 @@ import logging
import json import json
def get_temp_dir(): class Database:
import tempfile def __init__(self, path_to_db: str, db_structure: str, logger: logging.Logger, reset_anyways: bool = False):
self.logger = logger
self.path_to_db = path_to_db
temp_folder = "music-downloader" self.connection = sqlite3.connect(self.path_to_db)
temp_dir = os.path.join(tempfile.gettempdir(), temp_folder) self.cursor = self.connection.cursor()
if not os.path.exists(temp_dir):
os.mkdir(temp_dir)
return temp_dir
# init database
self.init_db(database_structure=db_structure, reset_anyways=reset_anyways)
# DATABASE_STRUCTURE_FILE = "database_structure.sql" def init_db(self, database_structure: str, reset_anyways: bool = False):
DATABASE_STRUCTURE_FILE = "src/metadata/database_structure.sql"
TEMP_DIR = get_temp_dir()
DATABASE_FILE = "metadata.db"
db_path = os.path.join(TEMP_DIR, DATABASE_FILE)
connection = sqlite3.connect(db_path)
# connection.row_factory = sqlite3.Row
cursor = connection.cursor()
def init_db(cursor, connection, reset_anyways: bool = False):
# check if db exists # check if db exists
exists = True exists = True
try: try:
query = 'SELECT * FROM track;' query = 'SELECT * FROM track;'
cursor.execute(query) self.cursor.execute(query)
_ = cursor.fetchall() _ = self.cursor.fetchall()
except sqlite3.OperationalError: except sqlite3.OperationalError:
exists = False exists = False
if not exists: if not exists:
logging.info("Database does not exist yet.") self.logger.info("Database does not exist yet.")
if reset_anyways or not exists: if reset_anyways or not exists:
# reset the database if reset_anyways is true or if an error has been thrown previously. # reset the database if reset_anyways is true or if an error has been thrown previously.
logging.info("Creating/Reseting Database.") self.logger.info("Creating/Reseting Database.")
# read the file # read the file
with open(DATABASE_STRUCTURE_FILE, "r") as database_structure_file: with open(database_structure, "r") as database_structure_file:
query = database_structure_file.read() query = database_structure_file.read()
cursor.executescript(query) self.cursor.executescript(query)
connection.commit() self.connection.commit()
def add_artist( def add_artist(
self,
musicbrainz_artistid: str, musicbrainz_artistid: str,
artist: str = None artist: str = None
): ):
query = "INSERT OR REPLACE INTO artist (id, name) VALUES (?, ?);" query = "INSERT OR REPLACE INTO artist (id, name) VALUES (?, ?);"
values = musicbrainz_artistid, artist values = musicbrainz_artistid, artist
cursor.execute(query, values) self.cursor.execute(query, values)
connection.commit() self.connection.commit()
def add_release_group( def add_release_group(
self,
musicbrainz_releasegroupid: str, musicbrainz_releasegroupid: str,
artist_ids: list, artist_ids: list,
albumartist: str = None, albumartist: str = None,
@ -75,17 +65,17 @@ def add_release_group(
adjacency_list.append((artist_id, musicbrainz_releasegroupid)) adjacency_list.append((artist_id, musicbrainz_releasegroupid))
adjacency_values = tuple(adjacency_list) adjacency_values = tuple(adjacency_list)
adjacency_query = "INSERT OR REPLACE INTO artist_release_group (artist_id, release_group_id) VALUES (?, ?);" adjacency_query = "INSERT OR REPLACE INTO artist_release_group (artist_id, release_group_id) VALUES (?, ?);"
cursor.executemany(adjacency_query, adjacency_values) self.cursor.executemany(adjacency_query, adjacency_values)
connection.commit() self.connection.commit()
# add release group # add release group
query = "INSERT OR REPLACE INTO release_group (id, albumartist, albumsort, musicbrainz_albumtype, compilation, album_artist_id) VALUES (?, ?, ?, ?, ?, ?);" query = "INSERT OR REPLACE INTO release_group (id, albumartist, albumsort, musicbrainz_albumtype, compilation, album_artist_id) VALUES (?, ?, ?, ?, ?, ?);"
values = musicbrainz_releasegroupid, albumartist, albumsort, musicbrainz_albumtype, compilation, album_artist_id values = musicbrainz_releasegroupid, albumartist, albumsort, musicbrainz_albumtype, compilation, album_artist_id
cursor.execute(query, values) self.cursor.execute(query, values)
connection.commit() self.connection.commit()
def add_release( def add_release(
self,
musicbrainz_albumid: str, musicbrainz_albumid: str,
release_group_id: str, release_group_id: str,
title: str = None, title: str = None,
@ -100,11 +90,11 @@ def add_release(
query = "INSERT OR REPLACE INTO release_ (id, release_group_id, title, copyright, album_status, language, year, date, country, barcode) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" query = "INSERT OR REPLACE INTO release_ (id, release_group_id, title, copyright, album_status, language, year, date, country, barcode) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"
values = musicbrainz_albumid, release_group_id, title, copyright_, album_status, language, year, date, country, barcode values = musicbrainz_albumid, release_group_id, title, copyright_, album_status, language, year, date, country, barcode
cursor.execute(query, values) self.cursor.execute(query, values)
connection.commit() self.connection.commit()
def add_track( def add_track(
self,
musicbrainz_releasetrackid: str, musicbrainz_releasetrackid: str,
musicbrainz_albumid: str, musicbrainz_albumid: str,
feature_aritsts: list, feature_aritsts: list,
@ -117,17 +107,17 @@ def add_track(
adjacency_list.append((artist_id, musicbrainz_releasetrackid)) adjacency_list.append((artist_id, musicbrainz_releasetrackid))
adjacency_values = tuple(adjacency_list) adjacency_values = tuple(adjacency_list)
adjacency_query = "INSERT OR REPLACE INTO artist_track (artist_id, track_id) VALUES (?, ?);" adjacency_query = "INSERT OR REPLACE INTO artist_track (artist_id, track_id) VALUES (?, ?);"
cursor.executemany(adjacency_query, adjacency_values) self.cursor.executemany(adjacency_query, adjacency_values)
connection.commit() self.connection.commit()
# add track # add track
query = "INSERT OR REPLACE INTO track (id, release_id, track, isrc) VALUES (?, ?, ?, ?);" query = "INSERT OR REPLACE INTO track (id, release_id, track, isrc) VALUES (?, ?, ?, ?);"
values = musicbrainz_releasetrackid, musicbrainz_albumid, track, isrc values = musicbrainz_releasetrackid, musicbrainz_albumid, track, isrc
cursor.execute(query, values) self.cursor.execute(query, values)
connection.commit() self.connection.commit()
@staticmethod
def get_custom_track_querry(custom_where: list) -> str: def get_custom_track_query(custom_where: list) -> str:
where_args = [ where_args = [
"track.release_id == release_.id", "track.release_id == release_.id",
"release_group.id == release_.release_group_id", "release_group.id == release_.release_group_id",
@ -179,53 +169,44 @@ GROUP BY track.id;
""" """
return query return query
def get_custom_track(self, custom_where: list):
query = Database.get_custom_track_query(custom_where=custom_where)
return [json.loads(i[0]) for i in self.cursor.execute(query)]
def get_custom_track(custom_where: list): def get_track_metadata(self, musicbrainz_releasetrackid: str):
query = get_custom_track_querry(custom_where=custom_where)
return [json.loads(i[0]) for i in cursor.execute(query)]
def get_track_metadata(musicbrainz_releasetrackid: str):
# this would be vulnerable if musicbrainz_releasetrackid would be user input # this would be vulnerable if musicbrainz_releasetrackid would be user input
resulting_tracks = get_custom_track([f'track.id == "{musicbrainz_releasetrackid}"']) resulting_tracks = self.get_custom_track([f'track.id == "{musicbrainz_releasetrackid}"'])
if len(resulting_tracks) != 1: if len(resulting_tracks) != 1:
return -1 return -1
return resulting_tracks[0] return resulting_tracks[0]
def get_tracks_to_download(self):
return self.get_custom_track(['track.downloaded == 0'])
def get_tracks_to_download(): def get_tracks_without_src(self):
return get_custom_track(['track.downloaded == 0']) return self.get_custom_track(["(track.url IS NULL OR track.src IS NULL)"])
def get_tracks_without_isrc(self):
return self.get_custom_track(["track.isrc IS NULL"])
def get_tracks_without_src(): def get_tracks_without_filepath(self):
return get_custom_track(["(track.url IS NULL OR track.src IS NULL)"]) return self.get_custom_track(["(track.file IS NULL OR track.path IS NULL OR track.genre IS NULL)"])
def update_download_status(self, track_id: str):
def get_tracks_without_isrc():
return get_custom_track(["track.isrc IS NULL"])
def get_tracks_without_filepath():
return get_custom_track(["(track.file IS NULL OR track.path IS NULL OR track.genre IS NULL)"])
def update_download_status(track_id: str):
pass pass
def set_download_data(self, track_id: str, url: str, src: str):
def set_download_data(track_id: str, url: str, src: str):
query = f""" query = f"""
UPDATE track UPDATE track
SET url = ?, SET url = ?,
src = ? src = ?
WHERE '{track_id}' == id; WHERE '{track_id}' == id;
""" """
cursor.execute(query, (url, src)) self.cursor.execute(query, (url, src))
connection.commit() self.connection.commit()
def set_filepath(self, track_id: str, file: str, path: str, genre: str):
def set_filepath(track_id: str, file: str, path: str, genre: str):
query = f""" query = f"""
UPDATE track UPDATE track
SET file = ?, SET file = ?,
@ -233,14 +214,27 @@ SET file = ?,
genre = ? genre = ?
WHERE '{track_id}' == id; WHERE '{track_id}' == id;
""" """
cursor.execute(query, (file, path, genre)) self.cursor.execute(query, (file, path, genre))
connection.commit() self.connection.commit()
init_db(cursor=cursor, connection=connection, reset_anyways=False)
if __name__ == "__main__": if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG) import tempfile
for track in get_tracks_without_isrc(): temp_folder = "music-downloader"
print(track['track'], [artist['name'] for artist in track['artists']]) temp_dir = os.path.join(tempfile.gettempdir(), temp_folder)
if not os.path.exists(temp_dir):
os.mkdir(temp_dir)
temp_dir = get_temp_dir()
DATABASE_FILE = "metadata.db"
DATABASE_STRUCTURE_FILE = "database_structure.sql"
db_path = os.path.join(TEMP_DIR, DATABASE_FILE)
logging.basicConfig()
logger = logging.getLogger("database")
logger.setLevel(logging.DEBUG)
database = Database(os.path.join(temp_dir, "metadata.db"), os.path.join(temp_dir, "database_structure.sql"), logger,
reset_anyways=True)

View File

@ -1,55 +0,0 @@
DROP TABLE IF EXISTS artist;
CREATE TABLE artist (
id TEXT PRIMARY KEY NOT NULL,
name TEXT
);
DROP TABLE IF EXISTS artist_release_group;
CREATE TABLE artist_release_group (
artist_id TEXT NOT NULL,
release_group_id TEXT NOT NULL
);
DROP TABLE IF EXISTS artist_track;
CREATE TABLE artist_track (
artist_id TEXT NOT NULL,
track_id TEXT NOT NULL
);
DROP TABLE IF EXISTS release_group;
CREATE TABLE release_group (
id TEXT PRIMARY KEY NOT NULL,
albumartist TEXT,
albumsort INT,
musicbrainz_albumtype TEXT,
compilation TEXT,
album_artist_id TEXT
);
DROP TABLE IF EXISTS release_;
CREATE TABLE release_ (
id TEXT PRIMARY KEY NOT NULL,
release_group_id TEXT NOT NULL,
title TEXT,
copyright TEXT,
album_status TEXT,
language TEXT,
year TEXT,
date TEXT,
country TEXT,
barcode TEXT
);
DROP TABLE IF EXISTS track;
CREATE TABLE track (
id TEXT PRIMARY KEY NOT NULL,
downloaded BOOLEAN NOT NULL DEFAULT 0,
release_id TEXT NOT NULL,
track TEXT,
isrc TEXT,
genre TEXT,
path TEXT,
file TEXT,
url TEXT,
src TEXT
);

View File

@ -1,10 +1,8 @@
from typing import List from typing import List
import musicbrainzngs import musicbrainzngs
import pandas as pd
import logging import logging
from metadata.object_handeling import get_elem_from_obj, parse_music_brainz_date from object_handeling import get_elem_from_obj, parse_music_brainz_date
from metadata import database
# I don't know if it would be feesable to set up my own mb instance # I don't know if it would be feesable to set up my own mb instance
# https://github.com/metabrainz/musicbrainz-docker # https://github.com/metabrainz/musicbrainz-docker
@ -17,13 +15,23 @@ musicbrainzngs.set_useragent("metadata receiver", "0.1", "https://github.com/HeI
# IMPORTANT DOCUMENTATION WHICH CONTAINS FOR EXAMPLE THE INCLUDES # IMPORTANT DOCUMENTATION WHICH CONTAINS FOR EXAMPLE THE INCLUDES
# https://python-musicbrainzngs.readthedocs.io/en/v0.7.1/api/#getting-data # https://python-musicbrainzngs.readthedocs.io/en/v0.7.1/api/#getting-data
class MetadataDownloader:
def __init__(self, database, logger: logging.Logger):
self.database = database
self.logger = logger
class Artist: class Artist:
def __init__( def __init__(
self, self,
database,
logger,
musicbrainz_artistid: str, musicbrainz_artistid: str,
release_groups: List = [], release_groups: List = [],
new_release_groups: bool = True new_release_groups: bool = True
): ):
self.database = database
self.logger = logger
""" """
release_groups: list release_groups: list
""" """
@ -48,7 +56,9 @@ class Artist:
release_groups.sort(key=lambda x: x['first-release-date']) release_groups.sort(key=lambda x: x['first-release-date'])
for i, release_group in enumerate(release_groups): for i, release_group in enumerate(release_groups):
self.release_groups.append(ReleaseGroup( self.release_groups.append(MetadataDownloader.ReleaseGroup(
self.database,
self.logger,
musicbrainz_releasegroupid=release_group['id'], musicbrainz_releasegroupid=release_group['id'],
artists=[self], artists=[self],
albumsort=i + 1 albumsort=i + 1
@ -59,21 +69,24 @@ class Artist:
return f"id: {self.musicbrainz_artistid}\nname: {self.artist}\n{newline.join([str(release_group) for release_group in self.release_groups])}" return f"id: {self.musicbrainz_artistid}\nname: {self.artist}\n{newline.join([str(release_group) for release_group in self.release_groups])}"
def save(self): def save(self):
logging.info(f"artist: {self}") self.logger.info(f"artist: {self}")
database.add_artist( self.database.add_artist(
musicbrainz_artistid=self.musicbrainz_artistid, musicbrainz_artistid=self.musicbrainz_artistid,
artist=self.artist artist=self.artist
) )
class ReleaseGroup: class ReleaseGroup:
def __init__( def __init__(
self, self,
database,
logger,
musicbrainz_releasegroupid: str, musicbrainz_releasegroupid: str,
artists: List[Artist] = [], artists = [],
albumsort: int = None, albumsort: int = None,
only_download_distinct_releases: bool = True only_download_distinct_releases: bool = True
): ):
self.database = database
self.logger = logger
""" """
split_artists: list -> if len > 1: album_artist=VariousArtists split_artists: list -> if len > 1: album_artist=VariousArtists
releases: list releases: list
@ -95,7 +108,8 @@ class ReleaseGroup:
continue continue
self.append_artist(artist_id) self.append_artist(artist_id)
self.albumartist = "Various Artists" if len(self.artists) > 1 else self.artists[0].artist self.albumartist = "Various Artists" if len(self.artists) > 1 else self.artists[0].artist
self.album_artist_id = None if self.albumartist == "Various Artists" else self.artists[0].musicbrainz_artistid self.album_artist_id = None if self.albumartist == "Various Artists" else self.artists[
0].musicbrainz_artistid
self.albumsort = albumsort self.albumsort = albumsort
self.musicbrainz_albumtype = get_elem_from_obj(release_group_data, ['primary-type']) self.musicbrainz_albumtype = get_elem_from_obj(release_group_data, ['primary-type'])
@ -113,8 +127,8 @@ class ReleaseGroup:
return f"{newline.join([str(release_group) for release_group in self.releases])}" return f"{newline.join([str(release_group) for release_group in self.releases])}"
def save(self): def save(self):
logging.info(f"caching release_group {self}") self.logger.info(f"caching release_group {self}")
database.add_release_group( self.database.add_release_group(
musicbrainz_releasegroupid=self.musicbrainz_releasegroupid, musicbrainz_releasegroupid=self.musicbrainz_releasegroupid,
artist_ids=[artist.musicbrainz_artistid for artist in self.artists], artist_ids=[artist.musicbrainz_artistid for artist in self.artists],
albumartist=self.albumartist, albumartist=self.albumartist,
@ -124,7 +138,7 @@ class ReleaseGroup:
album_artist_id=self.album_artist_id album_artist_id=self.album_artist_id
) )
def append_artist(self, artist_id: str) -> Artist: def append_artist(self, artist_id: str):
for existing_artist in self.artists: for existing_artist in self.artists:
if artist_id == existing_artist.musicbrainz_artistid: if artist_id == existing_artist.musicbrainz_artistid:
return existing_artist return existing_artist
@ -136,7 +150,7 @@ class ReleaseGroup:
musicbrainz_albumid = get_elem_from_obj(release_data, ['id']) musicbrainz_albumid = get_elem_from_obj(release_data, ['id'])
if musicbrainz_albumid is None: if musicbrainz_albumid is None:
return return
self.releases.append(Release(musicbrainz_albumid, release_group=self)) self.releases.append(MetadataDownloader.Release(self.database, self.logger, musicbrainz_albumid, release_group=self))
def append_distinct_releases(self, release_datas: List[dict]): def append_distinct_releases(self, release_datas: List[dict]):
titles = {} titles = {}
@ -154,13 +168,16 @@ class ReleaseGroup:
for release_data in release_datas: for release_data in release_datas:
self.append_release(release_data) self.append_release(release_data)
class Release: class Release:
def __init__( def __init__(
self, self,
database,
logger,
musicbrainz_albumid: str, musicbrainz_albumid: str,
release_group: ReleaseGroup = None release_group = None
): ):
self.database = database
self.logger = logger
""" """
release_group: ReleaseGroup release_group: ReleaseGroup
tracks: list tracks: list
@ -191,8 +208,8 @@ class Release:
return f"{self.title} ©{self.copyright} {self.album_status}" return f"{self.title} ©{self.copyright} {self.album_status}"
def save(self): def save(self):
logging.info(f"caching release {self}") self.logger.info(f"caching release {self}")
database.add_release( self.database.add_release(
musicbrainz_albumid=self.musicbrainz_albumid, musicbrainz_albumid=self.musicbrainz_albumid,
release_group_id=self.release_group.musicbrainz_releasegroupid, release_group_id=self.release_group.musicbrainz_releasegroupid,
title=self.title, title=self.title,
@ -211,15 +228,18 @@ class Release:
if musicbrainz_releasetrackid is None: if musicbrainz_releasetrackid is None:
continue continue
self.tracklist.append(Track(musicbrainz_releasetrackid, self)) self.tracklist.append(MetadataDownloader.Track(self.database, self.logger, musicbrainz_releasetrackid, self))
class Track: class Track:
def __init__( def __init__(
self, self,
database,
logger,
musicbrainz_releasetrackid: str, musicbrainz_releasetrackid: str,
release: Release = None release = None
): ):
self.database = database
self.logger = logger
""" """
release: Release release: Release
feature_artists: list feature_artists: list
@ -229,7 +249,9 @@ class Track:
self.release = release self.release = release
self.artists = [] self.artists = []
result = musicbrainzngs.get_recording_by_id(self.musicbrainz_releasetrackid, includes=["artists", "releases", "recording-rels", "isrcs", "work-level-rels"]) result = musicbrainzngs.get_recording_by_id(self.musicbrainz_releasetrackid,
includes=["artists", "releases", "recording-rels", "isrcs",
"work-level-rels"])
recording_data = result['recording'] recording_data = result['recording']
for artist_data in get_elem_from_obj(recording_data, ['artist-credit'], return_if_none=[]): for artist_data in get_elem_from_obj(recording_data, ['artist-credit'], return_if_none=[]):
self.append_artist(get_elem_from_obj(artist_data, ['artist', 'id'])) self.append_artist(get_elem_from_obj(artist_data, ['artist', 'id']))
@ -243,9 +265,9 @@ class Track:
return f"{self.title}: {self.isrc}" return f"{self.title}: {self.isrc}"
def save(self): def save(self):
logging.info(f"caching track {self}") self.logger.info(f"caching track {self}")
database.add_track( self.database.add_track(
musicbrainz_releasetrackid=self.musicbrainz_releasetrackid, musicbrainz_releasetrackid=self.musicbrainz_releasetrackid,
musicbrainz_albumid=self.release.musicbrainz_albumid, musicbrainz_albumid=self.release.musicbrainz_albumid,
feature_aritsts=[artist.musicbrainz_artistid for artist in self.artists], feature_aritsts=[artist.musicbrainz_artistid for artist in self.artists],
@ -253,51 +275,55 @@ class Track:
isrc=self.isrc isrc=self.isrc
) )
def append_artist(self, artist_id: str) -> Artist: def append_artist(self, artist_id: str):
if artist_id is None: if artist_id is None:
return return
for existing_artist in self.artists: for existing_artist in self.artists:
if artist_id == existing_artist.musicbrainz_artistid: if artist_id == existing_artist.musicbrainz_artistid:
return existing_artist return existing_artist
new_artist = Artist(artist_id, new_release_groups=False) new_artist = MetadataDownloader.Artist(self.database, self.logger, artist_id, new_release_groups=False)
self.artists.append(new_artist) self.artists.append(new_artist)
return new_artist return new_artist
def download(self, option: dict):
def download(option: dict):
type_ = option['type'] type_ = option['type']
mb_id = option['id'] mb_id = option['id']
metadata_list = []
if type_ == "artist": if type_ == "artist":
artist = Artist(mb_id) self.Artist(self.database, self.logger, mb_id)
print(artist) elif type_ == "release_group":
self.ReleaseGroup(self.database, self.logger, mb_id)
elif type_ == "release": elif type_ == "release":
metadata_list = download_release(mb_id) self.Release(self.database, self.logger, mb_id)
elif type_ == "track": elif type_ == "track":
metadata_list = download_track(mb_id) self.Track(self.database, self.logger, mb_id)
print(metadata_list)
metadata_df = pd.DataFrame(metadata_list)
# metadata_df.to_csv(os.path.join(self.temp, file))
return metadata_df
if __name__ == "__main__": if __name__ == "__main__":
"""
import tempfile import tempfile
import os import os
TEMP_FOLDER = "music-downloader" temp_folder = "music-downloader"
TEMP_DIR = os.path.join(tempfile.gettempdir(), TEMP_FOLDER) temp_dir = os.path.join(tempfile.gettempdir(), temp_folder)
if not os.path.exists(TEMP_DIR): if not os.path.exists(temp_dir):
os.mkdir(TEMP_DIR) os.mkdir(temp_dir)
"""
logger = logging.getLogger() logging.basicConfig(level=logging.DEBUG)
logger.setLevel(logging.INFO) db_logger = logging.getLogger("database")
db_logger.setLevel(logging.DEBUG)
download({'id': '5cfecbe4-f600-45e5-9038-ce820eedf3d1', 'type': 'artist'}) import database
database_ = database.Database(os.path.join(temp_dir, "metadata.db"),
os.path.join(temp_dir, "database_structure.sql"), db_logger,
reset_anyways=True)
download_logger = logging.getLogger("metadata downloader")
download_logger.setLevel(logging.INFO)
downloader = MetadataDownloader(database_, download_logger)
downloader.download({'id': '5cfecbe4-f600-45e5-9038-ce820eedf3d1', 'type': 'artist'})
# download({'id': '4b9af532-ef7e-42ab-8b26-c466327cb5e0', 'type': 'release'}) # download({'id': '4b9af532-ef7e-42ab-8b26-c466327cb5e0', 'type': 'release'})
# download({'id': 'c24ed9e7-6df9-44de-8570-975f1a5a75d1', 'type': 'track'}) # download({'id': 'c24ed9e7-6df9-44de-8570-975f1a5a75d1', 'type': 'track'})