Getting Started#
Installation#
Minim is a Python package and can be installed from source using pip, the package installer for Python.
Note
Minim will be coming to PyPI and conda-forge once the PEP 541 request is resolved!
Grab a copy of the Minim repository:
git clone https://github.com/bbye98/minim.git
Enter the repository directory:
cd minim
Optional: Create a virtual environment to prevent dependency conflicts.
Conda
Create an environment named
minim
and install the required dependencies using one of the following commands:conda create -n minim --file requirements_minimal.txt # required dependencies only conda env create -f environment.yml # all dependencies
Activate the environment:
conda activate minim
venv
Create an environment named
minim
:python -m venv minim
Activate the environment using one of the following commands:
source minim/bin/activate # POSIX: bash or zsh minim\Scripts\activate.bat # Windows: cmd.exe minim\Scripts\Activate.ps1 # Windows: PowerShell
The required dependencies will be installed automatically alongside Minim in the next step. To install all dependencies instead:
python -m pip install -r requirements.txt
virtualenv
Create an environment named
minim
:virtualenv minim
Activate the environment using one of the following commands:
source minim/bin/activate # Linux or macOS .\minim\Scripts\activate # Windows
The required dependencies will be installed automatically alongside Minim in the next step. To install all dependencies instead:
python -m pip install -r requirements.txt
Install Minim (and required dependencies, if you have not already done so) using pip:
python -m pip install -e .
Try importing Minim in Python:
python -c "import minim"
If no errors like
ModuleNotFoundError: No module named 'minim'
are raised, you have successfully installed Minim!
Usage#
Music service APIs#
from minim import itunes, qobuz, spotify, tidal
Currently, clients for iTunes Search API, Qobuz API, Spotify Web API, and TIDAL APIs have been implemented. Other than the iTunes Search API, which does not require client credentials or support user authentication and can be used out of the box, the other APIs have a few additional prerequisite steps before they can be used. If you authenticate via Minim, the tokens and their related information will be cached and updated automatically as they expire and are refreshed.
iTunes Search API (minim.itunes.SearchAPI
)#
To use the iTunes Search API, simply create a client by instantiating a minim.itunes.SearchAPI
object with no arguments:
client_itunes = itunes.SearchAPI()
Private Qobuz API (minim.qobuz.PrivateAPI
)#
If you already have a user authentication token, you can provide it and its accompanying app credentials to the client as keyword arguments auth_token
, app_id
, and app_secret
, respectively, and skip this section.
To use the Qobuz API without user authentication, simply create a client by instantiating a minim.qobuz.PrivateAPI
object with no arguments:
client_qobuz = qobuz.PrivateAPI()
To use the Qobuz API with user authentication and get access to all public and protected endpoints, you can pass flow="password"
and provide your Qobuz email and password as keyword arguments email
and password
to the constructor:
client_qobuz = qobuz.PrivateAPI(flow="password", email=<QOBUZ_EMAIL>, password=<QOBUZ_PASSWORD>)
which will authenticate you via a POST request to and retrieve the user authentication token from the Qobuz Web Player, or specify browser=True
to have Minim spawn a web browser with the Qobuz Web Player login page:
client_qobuz = qobuz.PrivateAPI(flow="password", browser=True)
which you can use to log in normally.
Private Spotify Lyrics Service (minim.spotify.PrivateLyricsService
)#
If you already have a user access token, you can provide it and optionally its accompanying expiry time and sp_dc
cookie to the client as keyword arguments access_token
, expiry
, and sp_dc
, respectively, and skip this section.
To use the Spotify Lyrics service,
Launch a web browser and log into the Spotify Web Player.
Find the
sp_dc
cookie in your web browser’s storage.For Chromium-based browsers, press
F12
to open DevTools and navigate toApplication > Storage > Cookies > https://open.spotify.com
.For Firefox, press
Shift
+F9
to open Storage Inspector and nagivate toStorage > Cookies > https://open.spotify.com
.
Create a client by instantiating a
minim.spotify.WebAPI
object with thesp_dc
cookie as a keyword argument:client_spotify_lyrics = spotify.PrivateLyricsService(sp_dc=<SPOTIFY_SP_DC>)
or store the
sp_dc
cookie as an environment variableSPOTIFY_SP_DC
and call the constructor with no arguments:
client_spotify_lyrics = spotify.PrivateLyricsService()
Spotify Web API (minim.spotify.WebAPI
)#
If you already have an access token, you can provide it and optionally its accompanying refresh token, expiry time, and client credentials to the client as keyword arguments access_token
, refresh_token
, expiry
, client_id
, and client_secret
, respectively, and skip this section.
First, register a Spotify application here and grab its client credentials. For the redirect URI, use http://localhost:8888/callback
. You can replace 8888
with an open port of your choice, but you will need to pass port=<SPOTIFY_PORT>
when you create a client.
To use the Spotify Web API without user authentication, you can provide the client credentials as keyword arguments client_id
and client_secret
to the constructor:
client_spotify = spotify.WebAPI(client_id=<SPOTIFY_CLIENT_ID>,
client_secret=<SPOTIFY_CLIENT_SECRET>)
or store the client credentials as environment variables SPOTIFY_CLIENT_ID
and SPOTIFY_CLIENT_SECRET
and call the constructor with no arguments:
client_spotify = spotify.WebAPI()
To use the Spotify Web API with user authentication,
Get the necessary authorization scopes using
spotify.WebAPI.get_scopes()
:
scopes = spotify.WebAPI.get_scopes("all")
Create a client with
flow="pkce"
, the client credentials inclient_id
andclient_secret
, the authorization scopes inscopes
, and optionallyframework="http.server"
to automate the authorization code retrieval process:client_spotify = spotify.WebAPI(client_id=<SPOTIFY_CLIENT_ID>, client_secret=<SPOTIFY_CLIENT_SECRET>, flow="pkce", scopes=scopes, framework="http.server")
If
framework=None
, open the authorization URL in a web browser.Log into your Spotify account and authorize Minim by clicking
Agree
.If
framework=None
, copy and paste the redirect URI into the prompt.
TIDAL API (minim.tidal.API
)#
If you already have a client-only access token, you can provide it and optionally its accompanying refresh token, expiry time, and client credentials to the client as keyword arguments access_token
, refresh_token
, expiry
, client_id
, and client_secret
, respectively, and skip this section.
First, register a TIDAL application here and jot down the client credentials.
To use the TIDAL API, you can provide the client credentials as keyword arguments client_id
and client_secret
to the minim.tidal.API
constructor:
client_tidal = tidal.API(client_id=<TIDAL_CLIENT_ID>, client_secret=<TIDAL_CLIENT_SECRET>)
or store the client credentials as environment variables TIDAL_CLIENT_ID
and TIDAL_CLIENT_SECRET
and create a client with no arguments:
client_tidal = tidal.API()
Private TIDAL API (minim.tidal.PrivateAPI
)#
If you already have an access token, you can provide it and optionally its accompanying refresh token, expiry time, and client credentials to the client as keyword arguments access_token
, refresh_token
, expiry
, client_id
, and client_secret
, respectively, and skip this section.
To use the TIDAL API without user authentication, simply create a client by instantiating a minim.tidal.PrivateAPI
object with no arguments:
client_tidal_private = tidal.PrivateAPI()
To use the TIDAL API with user authentication,
Get client credentials from the TIDAL Web Player or the Android, iOS, macOS, and Windows applications by using a web debugging proxy tool to intercept web traffic.
Create a client with the client credentials in
client_id
andclient_secret
and optionallybrowser=True
to automatically open a web browser for the authorization flow. Use the authorization code with PKCE flow:client_tidal_private = tidal.PrivateAPI(client_id=<TIDAL_CLIENT_ID>, client_secret=<TIDAL_CLIENT_SECRET>, flow="pkce", browser=True)
if you obtained client credentials from the TIDAL Web Player or the desktop applications, or the device code flow:
client_tidal_private = tidal.PrivateAPI(client_id=<TIDAL_CLIENT_ID>, client_secret=<TIDAL_CLIENT_SECRET>, flow="device", browser=True)
if you obtained client credentials from the Android or iOS applications.
Follow the instructions in the console (
browser=False
) or the web browser (browser=True
) to log into your TIDAL account and authorize Minim.
Examples#
Searching for artists#
Each of the APIs has a search()
method that can be used to search for and retrieve information about an artist, such as the EDM group Galantis:
iTunes Search API#
client_itunes.search("Galantis", entity="musicArtist", limit=1)["results"][0]
Show code cell output
{'wrapperType': 'artist',
'artistType': 'Artist',
'artistName': 'Galantis',
'artistLinkUrl': 'https://music.apple.com/us/artist/galantis/543322169?uo=4',
'artistId': 543322169,
'amgArtistId': 2616267,
'primaryGenreName': 'Dance',
'primaryGenreId': 17}
Private Qobuz API#
client_qobuz.search("Galantis", limit=1, strict=True)["artists"]["items"][0]
Show code cell output
{'picture': 'https://static.qobuz.com/images/artists/covers/small/8dcf30e5c8e30281ecbb13b0886426c8.jpg',
'image': {'small': 'https://static.qobuz.com/images/artists/covers/small/8dcf30e5c8e30281ecbb13b0886426c8.jpg',
'medium': 'https://static.qobuz.com/images/artists/covers/medium/8dcf30e5c8e30281ecbb13b0886426c8.jpg',
'large': 'https://static.qobuz.com/images/artists/covers/large/8dcf30e5c8e30281ecbb13b0886426c8.jpg',
'extralarge': 'https://static.qobuz.com/images/artists/covers/large/8dcf30e5c8e30281ecbb13b0886426c8.jpg',
'mega': 'https://static.qobuz.com/images/artists/covers/large/8dcf30e5c8e30281ecbb13b0886426c8.jpg'},
'name': 'Galantis',
'slug': 'galantis',
'albums_count': 143,
'id': 865362}
Spotify Web API#
client_spotify.search("Galantis", "artist", limit=1)["items"][0]
Show code cell output
{'external_urls': {'spotify': 'https://open.spotify.com/artist/4sTQVOfp9vEMCemLw50sbu'},
'followers': {'href': None, 'total': 3343551},
'genres': ['dance pop', 'edm', 'pop', 'pop dance'],
'href': 'https://api.spotify.com/v1/artists/4sTQVOfp9vEMCemLw50sbu',
'id': '4sTQVOfp9vEMCemLw50sbu',
'images': [{'height': 640,
'url': 'https://i.scdn.co/image/ab6761610000e5eb7bda087d6fb48d481efd3344',
'width': 640},
{'height': 320,
'url': 'https://i.scdn.co/image/ab676161000051747bda087d6fb48d481efd3344',
'width': 320},
{'height': 160,
'url': 'https://i.scdn.co/image/ab6761610000f1787bda087d6fb48d481efd3344',
'width': 160}],
'name': 'Galantis',
'popularity': 70,
'type': 'artist',
'uri': 'spotify:artist:4sTQVOfp9vEMCemLw50sbu'}
TIDAL API#
client_tidal.search("Galantis", "US", type="ARTISTS", limit=1)["artists"][0]
Show code cell output
{'resource': {'id': '4676988',
'name': 'Galantis',
'picture': [{'url': 'https://resources.tidal.com/images/a627e21c/60f7/4e90/b2bb/e50b178c4f0b/1024x256.jpg',
'width': 1024,
'height': 256},
{'url': 'https://resources.tidal.com/images/a627e21c/60f7/4e90/b2bb/e50b178c4f0b/1080x720.jpg',
'width': 1080,
'height': 720},
{'url': 'https://resources.tidal.com/images/a627e21c/60f7/4e90/b2bb/e50b178c4f0b/160x107.jpg',
'width': 160,
'height': 107},
{'url': 'https://resources.tidal.com/images/a627e21c/60f7/4e90/b2bb/e50b178c4f0b/160x160.jpg',
'width': 160,
'height': 160},
{'url': 'https://resources.tidal.com/images/a627e21c/60f7/4e90/b2bb/e50b178c4f0b/320x214.jpg',
'width': 320,
'height': 214},
{'url': 'https://resources.tidal.com/images/a627e21c/60f7/4e90/b2bb/e50b178c4f0b/320x320.jpg',
'width': 320,
'height': 320},
{'url': 'https://resources.tidal.com/images/a627e21c/60f7/4e90/b2bb/e50b178c4f0b/480x480.jpg',
'width': 480,
'height': 480},
{'url': 'https://resources.tidal.com/images/a627e21c/60f7/4e90/b2bb/e50b178c4f0b/640x428.jpg',
'width': 640,
'height': 428},
{'url': 'https://resources.tidal.com/images/a627e21c/60f7/4e90/b2bb/e50b178c4f0b/750x500.jpg',
'width': 750,
'height': 500},
{'url': 'https://resources.tidal.com/images/a627e21c/60f7/4e90/b2bb/e50b178c4f0b/750x750.jpg',
'width': 750,
'height': 750}],
'tidalUrl': 'https://tidal.com/browse/artist/4676988'},
'id': '4676988',
'status': 200,
'message': 'success'}
Private TIDAL API#
client_tidal_private.search("Galantis", type="artist", limit=1)["items"][0]
Show code cell output
{'id': 4676988,
'name': 'Galantis',
'artistTypes': ['ARTIST', 'CONTRIBUTOR'],
'url': 'http://www.tidal.com/artist/4676988',
'picture': 'a627e21c-60f7-4e90-b2bb-e50b178c4f0b',
'popularity': 72,
'artistRoles': [{'categoryId': -1, 'category': 'Artist'},
{'categoryId': 3, 'category': 'Engineer'},
{'categoryId': 11, 'category': 'Performer'},
{'categoryId': 10, 'category': 'Production team'},
{'categoryId': 1, 'category': 'Producer'},
{'categoryId': 2, 'category': 'Songwriter'}],
'mixes': {'ARTIST_MIX': '000202a7e72fd90d0c0df2ed56ddea'}}
Searching for tracks#
The search()
methods can also be used to search for and retrieve information about a track, such as “Everybody Talks” by Neon Trees:
iTunes Search API#
client_itunes.search("Everybody Talks", media="music", limit=1)["results"][0]
Show code cell output
{'wrapperType': 'track',
'kind': 'song',
'artistId': 350172836,
'collectionId': 1443469527,
'trackId': 1443469581,
'artistName': 'Neon Trees',
'collectionName': 'Picture Show',
'trackName': 'Everybody Talks',
'collectionCensoredName': 'Picture Show',
'trackCensoredName': 'Everybody Talks',
'artistViewUrl': 'https://music.apple.com/us/artist/neon-trees/350172836?uo=4',
'collectionViewUrl': 'https://music.apple.com/us/album/everybody-talks/1443469527?i=1443469581&uo=4',
'trackViewUrl': 'https://music.apple.com/us/album/everybody-talks/1443469527?i=1443469581&uo=4',
'previewUrl': 'https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview122/v4/5c/29/bf/5c29bf6b-ca2c-4e8b-2be6-c51a282c7dae/mzaf_1255557534804450018.plus.aac.p.m4a',
'artworkUrl30': 'https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/80/e3/95/80e39565-35f9-2496-c6f8-6572490c4a7b/12UMGIM12509.rgb.jpg/30x30bb.jpg',
'artworkUrl60': 'https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/80/e3/95/80e39565-35f9-2496-c6f8-6572490c4a7b/12UMGIM12509.rgb.jpg/60x60bb.jpg',
'artworkUrl100': 'https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/80/e3/95/80e39565-35f9-2496-c6f8-6572490c4a7b/12UMGIM12509.rgb.jpg/100x100bb.jpg',
'collectionPrice': 6.99,
'trackPrice': 1.29,
'releaseDate': '2012-01-01T12:00:00Z',
'collectionExplicitness': 'explicit',
'trackExplicitness': 'explicit',
'discCount': 1,
'discNumber': 1,
'trackCount': 12,
'trackNumber': 3,
'trackTimeMillis': 177280,
'country': 'USA',
'currency': 'USD',
'primaryGenreName': 'Alternative',
'contentAdvisoryRating': 'Explicit',
'isStreamable': True}
Private Qobuz API#
track_qobuz = client_qobuz.search("Everybody Talks", "ReleaseName", limit=1,
strict=True)["tracks"]["items"][0]
track_qobuz
Show code cell output
{'maximum_bit_depth': 16,
'copyright': '2022 Arko Boom 2022 Arko Boom',
'performers': 'Arko Boom, MainArtist - Arkos Todd, Songwriter, ComposerLyricist',
'audio_info': {'replaygain_track_peak': 1, 'replaygain_track_gain': -3.06},
'performer': {'name': 'Arko Boom', 'id': 15899504},
'album': {'image': {'small': 'https://static.qobuz.com/images/covers/fc/7v/ilfmuz10e7vfc_230.jpg',
'thumbnail': 'https://static.qobuz.com/images/covers/fc/7v/ilfmuz10e7vfc_50.jpg',
'large': 'https://static.qobuz.com/images/covers/fc/7v/ilfmuz10e7vfc_600.jpg'},
'maximum_bit_depth': 16,
'media_count': 1,
'artist': {'image': None,
'name': 'Arko Boom',
'id': 15899504,
'albums_count': 1,
'slug': 'arko-boom',
'picture': None},
'upc': '0859766309663',
'released_at': 1665180000,
'label': {'name': 'Arko Boom',
'id': 4026379,
'albums_count': 1,
'supplier_id': 95,
'slug': 'arko-boom'},
'title': 'Speedy',
'qobuz_id': 178369185,
'version': None,
'duration': 536,
'parental_warning': False,
'tracks_count': 4,
'popularity': 0,
'genre': {'path': [133],
'color': '#5eabc1',
'name': 'Hip-Hop/Rap',
'id': 133,
'slug': 'rap-hip-hop'},
'maximum_channel_count': 2,
'id': 'ilfmuz10e7vfc',
'maximum_sampling_rate': 44.1,
'previewable': True,
'sampleable': True,
'displayable': True,
'streamable': True,
'streamable_at': 1711522800,
'downloadable': False,
'purchasable_at': None,
'purchasable': False,
'release_date_original': '2022-10-08',
'release_date_download': '2022-10-08',
'release_date_stream': '2022-10-08',
'release_date_purchase': '2022-10-08',
'hires': False,
'hires_streamable': False},
'work': None,
'composer': {'name': 'Arkos Todd', 'id': 15899505},
'isrc': 'TCAGM2280786',
'title': 'Everybody Talks',
'version': None,
'duration': 127,
'parental_warning': False,
'track_number': 2,
'maximum_channel_count': 2,
'id': 178369187,
'media_number': 1,
'maximum_sampling_rate': 44.1,
'release_date_original': '2022-10-08',
'release_date_download': '2022-10-08',
'release_date_stream': '2022-10-08',
'release_date_purchase': '2022-10-08',
'purchasable': True,
'streamable': True,
'previewable': True,
'sampleable': True,
'downloadable': True,
'displayable': True,
'purchasable_at': 1711522800,
'streamable_at': 1711522800,
'hires': False,
'hires_streamable': False}
Spotify Web API#
track_spotify = client_spotify.search("Everybody Talks", "track",
limit=1)["items"][0]
track_spotify
Show code cell output
{'album': {'album_type': 'album',
'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/0RpddSzUHfncUWNJXKOsjy'},
'href': 'https://api.spotify.com/v1/artists/0RpddSzUHfncUWNJXKOsjy',
'id': '0RpddSzUHfncUWNJXKOsjy',
'name': 'Neon Trees',
'type': 'artist',
'uri': 'spotify:artist:0RpddSzUHfncUWNJXKOsjy'}],
'available_markets': ['AR',
'AU',
'AT',
'BE',
'BO',
'BR',
'BG',
'CA',
'CL',
'CO',
'CR',
'CY',
'CZ',
'DK',
'DO',
'DE',
'EC',
'EE',
'SV',
'FI',
'FR',
'GR',
'GT',
'HN',
'HK',
'HU',
'IS',
'IE',
'IT',
'LV',
'LT',
'LU',
'MY',
'MT',
'NL',
'NZ',
'NI',
'NO',
'PA',
'PY',
'PE',
'PH',
'PL',
'PT',
'SG',
'SK',
'ES',
'SE',
'CH',
'TW',
'TR',
'UY',
'US',
'GB',
'AD',
'LI',
'MC',
'ID',
'TH',
'VN',
'RO',
'IL',
'ZA',
'SA',
'AE',
'BH',
'QA',
'OM',
'KW',
'EG',
'TN',
'LB',
'JO',
'PS',
'IN',
'BY',
'KZ',
'MD',
'UA',
'AL',
'BA',
'HR',
'ME',
'MK',
'RS',
'SI',
'KR',
'BD',
'PK',
'LK',
'GH',
'KE',
'NG',
'TZ',
'UG',
'AG',
'AM',
'BS',
'BB',
'BZ',
'BT',
'BW',
'BF',
'CV',
'CW',
'DM',
'FJ',
'GM',
'GD',
'GW',
'GY',
'HT',
'JM',
'KI',
'LS',
'LR',
'MW',
'MV',
'ML',
'MH',
'FM',
'NA',
'NR',
'NE',
'PW',
'PG',
'WS',
'ST',
'SN',
'SC',
'SL',
'SB',
'KN',
'LC',
'VC',
'SR',
'TL',
'TO',
'TT',
'TV',
'AZ',
'BN',
'BI',
'KH',
'CM',
'TD',
'KM',
'GQ',
'SZ',
'GA',
'GN',
'KG',
'LA',
'MO',
'MR',
'MN',
'NP',
'RW',
'TG',
'UZ',
'ZW',
'BJ',
'MG',
'MU',
'MZ',
'AO',
'CI',
'DJ',
'ZM',
'CD',
'CG',
'IQ',
'TJ',
'VE',
'XK'],
'external_urls': {'spotify': 'https://open.spotify.com/album/0uRFz92JmjwDbZbB7hEBIr'},
'href': 'https://api.spotify.com/v1/albums/0uRFz92JmjwDbZbB7hEBIr',
'id': '0uRFz92JmjwDbZbB7hEBIr',
'images': [{'height': 640,
'url': 'https://i.scdn.co/image/ab67616d0000b2734a6c0376235e5aa44e59d2c2',
'width': 640},
{'height': 300,
'url': 'https://i.scdn.co/image/ab67616d00001e024a6c0376235e5aa44e59d2c2',
'width': 300},
{'height': 64,
'url': 'https://i.scdn.co/image/ab67616d000048514a6c0376235e5aa44e59d2c2',
'width': 64}],
'name': 'Picture Show',
'release_date': '2012-01-01',
'release_date_precision': 'day',
'total_tracks': 11,
'type': 'album',
'uri': 'spotify:album:0uRFz92JmjwDbZbB7hEBIr'},
'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/0RpddSzUHfncUWNJXKOsjy'},
'href': 'https://api.spotify.com/v1/artists/0RpddSzUHfncUWNJXKOsjy',
'id': '0RpddSzUHfncUWNJXKOsjy',
'name': 'Neon Trees',
'type': 'artist',
'uri': 'spotify:artist:0RpddSzUHfncUWNJXKOsjy'}],
'available_markets': ['AR',
'AU',
'AT',
'BE',
'BO',
'BR',
'BG',
'CA',
'CL',
'CO',
'CR',
'CY',
'CZ',
'DK',
'DO',
'DE',
'EC',
'EE',
'SV',
'FI',
'FR',
'GR',
'GT',
'HN',
'HK',
'HU',
'IS',
'IE',
'IT',
'LV',
'LT',
'LU',
'MY',
'MT',
'NL',
'NZ',
'NI',
'NO',
'PA',
'PY',
'PE',
'PH',
'PL',
'PT',
'SG',
'SK',
'ES',
'SE',
'CH',
'TW',
'TR',
'UY',
'US',
'GB',
'AD',
'LI',
'MC',
'ID',
'TH',
'VN',
'RO',
'IL',
'ZA',
'SA',
'AE',
'BH',
'QA',
'OM',
'KW',
'EG',
'TN',
'LB',
'JO',
'PS',
'IN',
'BY',
'KZ',
'MD',
'UA',
'AL',
'BA',
'HR',
'ME',
'MK',
'RS',
'SI',
'KR',
'BD',
'PK',
'LK',
'GH',
'KE',
'NG',
'TZ',
'UG',
'AG',
'AM',
'BS',
'BB',
'BZ',
'BT',
'BW',
'BF',
'CV',
'CW',
'DM',
'FJ',
'GM',
'GD',
'GW',
'GY',
'HT',
'JM',
'KI',
'LS',
'LR',
'MW',
'MV',
'ML',
'MH',
'FM',
'NA',
'NR',
'NE',
'PW',
'PG',
'WS',
'ST',
'SN',
'SC',
'SL',
'SB',
'KN',
'LC',
'VC',
'SR',
'TL',
'TO',
'TT',
'TV',
'AZ',
'BN',
'BI',
'KH',
'CM',
'TD',
'KM',
'GQ',
'SZ',
'GA',
'GN',
'KG',
'LA',
'MO',
'MR',
'MN',
'NP',
'RW',
'TG',
'UZ',
'ZW',
'BJ',
'MG',
'MU',
'MZ',
'AO',
'CI',
'DJ',
'ZM',
'CD',
'CG',
'IQ',
'TJ',
'VE',
'XK'],
'disc_number': 1,
'duration_ms': 177280,
'explicit': True,
'external_ids': {'isrc': 'USUM71119189'},
'external_urls': {'spotify': 'https://open.spotify.com/track/2iUmqdfGZcHIhS3b9E9EWq'},
'href': 'https://api.spotify.com/v1/tracks/2iUmqdfGZcHIhS3b9E9EWq',
'id': '2iUmqdfGZcHIhS3b9E9EWq',
'is_local': False,
'name': 'Everybody Talks',
'popularity': 80,
'preview_url': None,
'track_number': 3,
'type': 'track',
'uri': 'spotify:track:2iUmqdfGZcHIhS3b9E9EWq'}
TIDAL API#
client_tidal.search("Everybody Talks", "US", type="TRACKS", limit=1)["tracks"][0]
Show code cell output
{'resource': {'artifactType': 'track',
'id': '14492425',
'title': 'Everybody Talks',
'artists': [{'id': '3665225',
'name': 'Neon Trees',
'picture': [{'url': 'https://resources.tidal.com/images/e6f17398/759e/45a0/9673/6ded6811e199/1024x256.jpg',
'width': 1024,
'height': 256},
{'url': 'https://resources.tidal.com/images/e6f17398/759e/45a0/9673/6ded6811e199/1080x720.jpg',
'width': 1080,
'height': 720},
{'url': 'https://resources.tidal.com/images/e6f17398/759e/45a0/9673/6ded6811e199/160x107.jpg',
'width': 160,
'height': 107},
{'url': 'https://resources.tidal.com/images/e6f17398/759e/45a0/9673/6ded6811e199/160x160.jpg',
'width': 160,
'height': 160},
{'url': 'https://resources.tidal.com/images/e6f17398/759e/45a0/9673/6ded6811e199/320x214.jpg',
'width': 320,
'height': 214},
{'url': 'https://resources.tidal.com/images/e6f17398/759e/45a0/9673/6ded6811e199/320x320.jpg',
'width': 320,
'height': 320},
{'url': 'https://resources.tidal.com/images/e6f17398/759e/45a0/9673/6ded6811e199/480x480.jpg',
'width': 480,
'height': 480},
{'url': 'https://resources.tidal.com/images/e6f17398/759e/45a0/9673/6ded6811e199/640x428.jpg',
'width': 640,
'height': 428},
{'url': 'https://resources.tidal.com/images/e6f17398/759e/45a0/9673/6ded6811e199/750x500.jpg',
'width': 750,
'height': 500},
{'url': 'https://resources.tidal.com/images/e6f17398/759e/45a0/9673/6ded6811e199/750x750.jpg',
'width': 750,
'height': 750}],
'main': True}],
'album': {'id': '14492422',
'title': 'Picture Show',
'imageCover': [{'url': 'https://resources.tidal.com/images/1c2d7c90/034e/485a/be1f/24a669c7e6ee/1080x1080.jpg',
'width': 1080,
'height': 1080},
{'url': 'https://resources.tidal.com/images/1c2d7c90/034e/485a/be1f/24a669c7e6ee/1280x1280.jpg',
'width': 1280,
'height': 1280},
{'url': 'https://resources.tidal.com/images/1c2d7c90/034e/485a/be1f/24a669c7e6ee/160x160.jpg',
'width': 160,
'height': 160},
{'url': 'https://resources.tidal.com/images/1c2d7c90/034e/485a/be1f/24a669c7e6ee/320x320.jpg',
'width': 320,
'height': 320},
{'url': 'https://resources.tidal.com/images/1c2d7c90/034e/485a/be1f/24a669c7e6ee/640x640.jpg',
'width': 640,
'height': 640},
{'url': 'https://resources.tidal.com/images/1c2d7c90/034e/485a/be1f/24a669c7e6ee/750x750.jpg',
'width': 750,
'height': 750},
{'url': 'https://resources.tidal.com/images/1c2d7c90/034e/485a/be1f/24a669c7e6ee/80x80.jpg',
'width': 80,
'height': 80}],
'videoCover': []},
'duration': 177,
'trackNumber': 3,
'volumeNumber': 1,
'isrc': 'USUM71119189',
'copyright': 'A Mercury Records Release; ℗ 2011 UMG Recordings, Inc.',
'mediaMetadata': {'tags': ['LOSSLESS']},
'properties': {'content': ['explicit']},
'tidalUrl': 'https://tidal.com/browse/track/14492425'},
'id': '14492425',
'status': 200,
'message': 'success'}
Private TIDAL API#
track_tidal_private = client_tidal_private.search("Everybody Talks",
type="track",
limit=1)["items"][0]
track_tidal_private
Show code cell output
{'id': 14492425,
'title': 'Everybody Talks',
'duration': 177,
'replayGain': -11.7,
'peak': 0.999969,
'allowStreaming': True,
'streamReady': True,
'adSupportedStreamReady': True,
'djReady': True,
'stemReady': False,
'streamStartDate': '2012-04-17T00:00:00.000+0000',
'premiumStreamingOnly': False,
'trackNumber': 3,
'volumeNumber': 1,
'version': None,
'popularity': 60,
'copyright': 'A Mercury Records Release; ℗ 2011 UMG Recordings, Inc.',
'bpm': 155,
'url': 'http://www.tidal.com/track/14492425',
'isrc': 'USUM71119189',
'editable': False,
'explicit': True,
'audioQuality': 'LOSSLESS',
'audioModes': ['STEREO'],
'mediaMetadata': {'tags': ['LOSSLESS']},
'artist': {'id': 3665225,
'name': 'Neon Trees',
'type': 'MAIN',
'picture': 'e6f17398-759e-45a0-9673-6ded6811e199'},
'artists': [{'id': 3665225,
'name': 'Neon Trees',
'type': 'MAIN',
'picture': 'e6f17398-759e-45a0-9673-6ded6811e199'}],
'album': {'id': 14492422,
'title': 'Picture Show',
'cover': '1c2d7c90-034e-485a-be1f-24a669c7e6ee',
'vibrantColor': '#f8af88',
'videoCover': None},
'mixes': {'TRACK_MIX': '0019768c833a193c29829e5bf473fc'}}
Creating, modifying, and deleting a personal playlist#
If the clients are authenticated, you can create and modify user playlists. As an example, we will create a private playlist named “Minim”, make it public, add “Everybody Talks” by Neon Trees to it, and then delete it.
Private Qobuz API#
playlist_qobuz = client_qobuz.create_playlist(
"Minim",
description="A playlist created using Minim.",
public=False
)
client_qobuz.update_playlist(playlist_qobuz["id"], public=True)
client_qobuz.add_playlist_tracks(playlist_qobuz["id"], track_qobuz["id"])
playlist_qobuz = client_qobuz.get_playlist(playlist_qobuz["id"])
playlist_qobuz["owner"] = None # remove personal identifying information
playlist_qobuz
Show code cell output
{'owner': None,
'users_count': 0,
'images150': ['https://static.qobuz.com/images/covers/fc/7v/ilfmuz10e7vfc_150.jpg'],
'images': ['https://static.qobuz.com/images/covers/fc/7v/ilfmuz10e7vfc_50.jpg'],
'is_collaborative': False,
'description': 'A playlist created using Minim.',
'created_at': 1716796037,
'images300': ['https://static.qobuz.com/images/covers/fc/7v/ilfmuz10e7vfc_300.jpg'],
'duration': 127,
'updated_at': 1716796038,
'published_to': None,
'genres': [],
'tracks_count': 1,
'public_at': 1716796037,
'name': 'Minim',
'is_public': True,
'published_from': None,
'id': 21785616,
'slug': 'minim-12',
'is_featured': False,
'tracks': {'offset': 0,
'limit': 50,
'total': 1,
'items': [{'maximum_bit_depth': 16,
'copyright': '2022 Arko Boom 2022 Arko Boom',
'performers': 'Arko Boom, MainArtist - Arkos Todd, Songwriter, ComposerLyricist',
'audio_info': {'replaygain_track_peak': 1, 'replaygain_track_gain': -3.06},
'performer': {'name': 'Arko Boom', 'id': 15899504},
'album': {'image': {'small': 'https://static.qobuz.com/images/covers/fc/7v/ilfmuz10e7vfc_230.jpg',
'thumbnail': 'https://static.qobuz.com/images/covers/fc/7v/ilfmuz10e7vfc_50.jpg',
'large': 'https://static.qobuz.com/images/covers/fc/7v/ilfmuz10e7vfc_600.jpg'},
'maximum_bit_depth': 16,
'media_count': 1,
'artist': {'image': None,
'name': 'Arko Boom',
'id': 15899504,
'albums_count': 1,
'slug': 'arko-boom',
'picture': None},
'upc': '0859766309663',
'released_at': 1665180000,
'label': {'name': 'Arko Boom',
'id': 4026379,
'albums_count': 1,
'supplier_id': 95,
'slug': 'arko-boom'},
'title': 'Speedy',
'qobuz_id': 178369185,
'version': None,
'duration': 536,
'parental_warning': False,
'tracks_count': 4,
'popularity': 0,
'genre': {'path': [133],
'color': '#5eabc1',
'name': 'Hip-Hop/Rap',
'id': 133,
'slug': 'rap-hip-hop'},
'maximum_channel_count': 2,
'id': 'ilfmuz10e7vfc',
'maximum_sampling_rate': 44.1,
'previewable': True,
'sampleable': True,
'displayable': True,
'streamable': True,
'streamable_at': 1711522800,
'downloadable': False,
'purchasable_at': None,
'purchasable': False,
'release_date_original': '2022-10-08',
'release_date_download': '2022-10-08',
'release_date_stream': '2022-10-08',
'release_date_purchase': '2022-10-08',
'hires': False,
'hires_streamable': False},
'work': None,
'composer': {'name': 'Arkos Todd', 'id': 15899505},
'isrc': 'TCAGM2280786',
'title': 'Everybody Talks',
'version': None,
'duration': 127,
'parental_warning': False,
'track_number': 2,
'maximum_channel_count': 2,
'id': 178369187,
'media_number': 1,
'maximum_sampling_rate': 44.1,
'release_date_original': '2022-10-08',
'release_date_download': '2022-10-08',
'release_date_stream': '2022-10-08',
'release_date_purchase': '2022-10-08',
'purchasable': True,
'streamable': True,
'previewable': True,
'sampleable': True,
'downloadable': True,
'displayable': True,
'purchasable_at': 1711522800,
'streamable_at': 1711522800,
'hires': False,
'hires_streamable': False,
'position': 1,
'created_at': 1716796038,
'playlist_track_id': 4800410944}]}}
client_qobuz.delete_playlist(playlist_qobuz["id"])
Spotify Web API#
playlist_spotify = client_spotify.create_playlist(
"Minim",
description="A playlist created using Minim.",
public=False
)
client_spotify.change_playlist_details(playlist_spotify["id"], public=True)
client_spotify.add_playlist_items(playlist_spotify["id"],
[f"spotify:track:{track_spotify['id']}"])
playlist_spotify = client_spotify.get_playlist(playlist_spotify["id"])
# remove personal identifying information
playlist_spotify["owner"] = playlist_spotify["tracks"]["items"][0]["added_by"] = None
playlist_spotify
Show code cell output
{'collaborative': False,
'description': 'A playlist created using Minim.',
'external_urls': {'spotify': 'https://open.spotify.com/playlist/1tPv85ugl5VCyv3LRFEn4F'},
'followers': {'href': None, 'total': 0},
'href': 'https://api.spotify.com/v1/playlists/1tPv85ugl5VCyv3LRFEn4F',
'id': '1tPv85ugl5VCyv3LRFEn4F',
'images': [{'height': None,
'url': 'https://i.scdn.co/image/ab67616d00001e024a6c0376235e5aa44e59d2c2',
'width': None}],
'name': 'Minim',
'owner': None,
'primary_color': None,
'public': True,
'snapshot_id': 'AAAAAQcwPwQ+ZPBO3L6L2C171Urki7mV',
'tracks': {'href': 'https://api.spotify.com/v1/playlists/1tPv85ugl5VCyv3LRFEn4F/tracks?offset=0&limit=100',
'items': [{'added_at': '2024-05-27T07:47:19Z',
'added_by': None,
'is_local': False,
'primary_color': None,
'track': {'preview_url': None,
'available_markets': ['AR',
'AU',
'AT',
'BE',
'BO',
'BR',
'BG',
'CA',
'CL',
'CO',
'CR',
'CY',
'CZ',
'DK',
'DO',
'DE',
'EC',
'EE',
'SV',
'FI',
'FR',
'GR',
'GT',
'HN',
'HK',
'HU',
'IS',
'IE',
'IT',
'LV',
'LT',
'LU',
'MY',
'MT',
'NL',
'NZ',
'NI',
'NO',
'PA',
'PY',
'PE',
'PH',
'PL',
'PT',
'SG',
'SK',
'ES',
'SE',
'CH',
'TW',
'TR',
'UY',
'US',
'GB',
'AD',
'LI',
'MC',
'ID',
'TH',
'VN',
'RO',
'IL',
'ZA',
'SA',
'AE',
'BH',
'QA',
'OM',
'KW',
'EG',
'TN',
'LB',
'JO',
'PS',
'IN',
'BY',
'KZ',
'MD',
'UA',
'AL',
'BA',
'HR',
'ME',
'MK',
'RS',
'SI',
'KR',
'BD',
'PK',
'LK',
'GH',
'KE',
'NG',
'TZ',
'UG',
'AG',
'AM',
'BS',
'BB',
'BZ',
'BT',
'BW',
'BF',
'CV',
'CW',
'DM',
'FJ',
'GM',
'GD',
'GW',
'GY',
'HT',
'JM',
'KI',
'LS',
'LR',
'MW',
'MV',
'ML',
'MH',
'FM',
'NA',
'NR',
'NE',
'PW',
'PG',
'WS',
'ST',
'SN',
'SC',
'SL',
'SB',
'KN',
'LC',
'VC',
'SR',
'TL',
'TO',
'TT',
'TV',
'AZ',
'BN',
'BI',
'KH',
'CM',
'TD',
'KM',
'GQ',
'SZ',
'GA',
'GN',
'KG',
'LA',
'MO',
'MR',
'MN',
'NP',
'RW',
'TG',
'UZ',
'ZW',
'BJ',
'MG',
'MU',
'MZ',
'AO',
'CI',
'DJ',
'ZM',
'CD',
'CG',
'IQ',
'TJ',
'VE',
'XK'],
'explicit': True,
'type': 'track',
'episode': False,
'track': True,
'album': {'available_markets': ['AR',
'AU',
'AT',
'BE',
'BO',
'BR',
'BG',
'CA',
'CL',
'CO',
'CR',
'CY',
'CZ',
'DK',
'DO',
'DE',
'EC',
'EE',
'SV',
'FI',
'FR',
'GR',
'GT',
'HN',
'HK',
'HU',
'IS',
'IE',
'IT',
'LV',
'LT',
'LU',
'MY',
'MT',
'NL',
'NZ',
'NI',
'NO',
'PA',
'PY',
'PE',
'PH',
'PL',
'PT',
'SG',
'SK',
'ES',
'SE',
'CH',
'TW',
'TR',
'UY',
'US',
'GB',
'AD',
'LI',
'MC',
'ID',
'TH',
'VN',
'RO',
'IL',
'ZA',
'SA',
'AE',
'BH',
'QA',
'OM',
'KW',
'EG',
'TN',
'LB',
'JO',
'PS',
'IN',
'BY',
'KZ',
'MD',
'UA',
'AL',
'BA',
'HR',
'ME',
'MK',
'RS',
'SI',
'KR',
'BD',
'PK',
'LK',
'GH',
'KE',
'NG',
'TZ',
'UG',
'AG',
'AM',
'BS',
'BB',
'BZ',
'BT',
'BW',
'BF',
'CV',
'CW',
'DM',
'FJ',
'GM',
'GD',
'GW',
'GY',
'HT',
'JM',
'KI',
'LS',
'LR',
'MW',
'MV',
'ML',
'MH',
'FM',
'NA',
'NR',
'NE',
'PW',
'PG',
'WS',
'ST',
'SN',
'SC',
'SL',
'SB',
'KN',
'LC',
'VC',
'SR',
'TL',
'TO',
'TT',
'TV',
'AZ',
'BN',
'BI',
'KH',
'CM',
'TD',
'KM',
'GQ',
'SZ',
'GA',
'GN',
'KG',
'LA',
'MO',
'MR',
'MN',
'NP',
'RW',
'TG',
'UZ',
'ZW',
'BJ',
'MG',
'MU',
'MZ',
'AO',
'CI',
'DJ',
'ZM',
'CD',
'CG',
'IQ',
'TJ',
'VE',
'XK'],
'type': 'album',
'album_type': 'album',
'href': 'https://api.spotify.com/v1/albums/0uRFz92JmjwDbZbB7hEBIr',
'id': '0uRFz92JmjwDbZbB7hEBIr',
'images': [{'url': 'https://i.scdn.co/image/ab67616d0000b2734a6c0376235e5aa44e59d2c2',
'width': 640,
'height': 640},
{'url': 'https://i.scdn.co/image/ab67616d00001e024a6c0376235e5aa44e59d2c2',
'width': 300,
'height': 300},
{'url': 'https://i.scdn.co/image/ab67616d000048514a6c0376235e5aa44e59d2c2',
'width': 64,
'height': 64}],
'name': 'Picture Show',
'release_date': '2012-01-01',
'release_date_precision': 'day',
'uri': 'spotify:album:0uRFz92JmjwDbZbB7hEBIr',
'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/0RpddSzUHfncUWNJXKOsjy'},
'href': 'https://api.spotify.com/v1/artists/0RpddSzUHfncUWNJXKOsjy',
'id': '0RpddSzUHfncUWNJXKOsjy',
'name': 'Neon Trees',
'type': 'artist',
'uri': 'spotify:artist:0RpddSzUHfncUWNJXKOsjy'}],
'external_urls': {'spotify': 'https://open.spotify.com/album/0uRFz92JmjwDbZbB7hEBIr'},
'total_tracks': 11},
'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/0RpddSzUHfncUWNJXKOsjy'},
'href': 'https://api.spotify.com/v1/artists/0RpddSzUHfncUWNJXKOsjy',
'id': '0RpddSzUHfncUWNJXKOsjy',
'name': 'Neon Trees',
'type': 'artist',
'uri': 'spotify:artist:0RpddSzUHfncUWNJXKOsjy'}],
'disc_number': 1,
'track_number': 3,
'duration_ms': 177280,
'external_ids': {'isrc': 'USUM71119189'},
'external_urls': {'spotify': 'https://open.spotify.com/track/2iUmqdfGZcHIhS3b9E9EWq'},
'href': 'https://api.spotify.com/v1/tracks/2iUmqdfGZcHIhS3b9E9EWq',
'id': '2iUmqdfGZcHIhS3b9E9EWq',
'name': 'Everybody Talks',
'popularity': 80,
'uri': 'spotify:track:2iUmqdfGZcHIhS3b9E9EWq',
'is_local': False},
'video_thumbnail': {'url': None}}],
'limit': 100,
'next': None,
'offset': 0,
'previous': None,
'total': 1},
'type': 'playlist',
'uri': 'spotify:playlist:1tPv85ugl5VCyv3LRFEn4F'}
client_spotify.unfollow_playlist(playlist_spotify["id"])
Private TIDAL API#
playlist_tidal_private = client_tidal_private.create_playlist(
"Minim",
description="A playlist created using Minim.",
public=False
)
client_tidal_private.set_playlist_privacy(playlist_tidal_private["data"]["uuid"],
True)
client_tidal_private.add_playlist_items(playlist_tidal_private["data"]["uuid"],
track_tidal_private["id"])
playlist_tidal_private = client_tidal_private.get_user_playlist(
playlist_tidal_private["data"]["uuid"]
)
# remove personal identifying information
playlist_tidal_private["playlist"]["creator"] = playlist_tidal_private["profile"] = None
playlist_tidal_private
Show code cell output
{'playlist': {'uuid': 'd577851a-4e6a-4cf4-81d1-91aa4851fbe0',
'type': 'USER',
'creator': None,
'contentBehavior': 'UNRESTRICTED',
'sharingLevel': 'PUBLIC',
'status': 'READY',
'source': 'DEFAULT',
'title': 'Minim',
'description': 'A playlist created using Minim.',
'image': 'abc9781d-acae-4b50-9233-1674305e8fb4',
'squareImage': 'b735427d-fff9-4688-a366-70a68d0f27fc',
'url': 'http://www.tidal.com/playlist/d577851a-4e6a-4cf4-81d1-91aa4851fbe0',
'created': '2024-05-27T07:47:20.316+0000',
'lastUpdated': '2024-05-27T07:47:20.842+0000',
'lastItemAddedAt': '2024-05-27T07:47:20.842+0000',
'duration': 177,
'numberOfTracks': 1,
'numberOfVideos': 0,
'promotedArtists': [],
'trn': 'trn:playlist:d577851a-4e6a-4cf4-81d1-91aa4851fbe0'},
'followInfo': {'nrOfFollowers': 0,
'tidalResourceName': 'trn:playlist:d577851a-4e6a-4cf4-81d1-91aa4851fbe0',
'followed': True,
'followType': 'PLAYLIST'},
'profile': None}
client_tidal_private.delete_playlist(playlist_tidal_private["playlist"]["uuid"])
Audio file handlers#
from pathlib import Path
from minim.audio import Audio, FLACAudio, MP3Audio, MP4Audio, OggAudio, WAVEAudio
Minim uses Mutagen to load and edit audio files and FFmpeg to convert between different audio formats. Currently, the most common audio formats, such as AAC, ALAC, FLAC, MP3, Opus, Vorbis, and WAVE, are supported.
Examples#
Loading and editing audio files#
To load an audio file, pass the filename as a str
or a pathlib.Path
object either to the minim.audio.Audio
constructor for Minim to automatically detect the audio format:
file = Path().resolve().parents[2] / "tests/data/samples/middle_c.wav"
middle_c = Audio(file)
or the specific class for the audio format if known (in this case, minim.audio.WAVEAudio
):
middle_c = WAVEAudio(Path().resolve().parents[2] / "tests/data/samples/middle_c.wav")
For this example, both approaches return a minim.audio.WAVEAudio
object:
type(middle_c)
minim.audio.WAVEAudio
The metadata stored in the audio file can be accessed using dot notation or getattr()
:
for attr in ["title", "album", "artist", "genre", "codec", "bit_depth"]:
print(f"{attr.capitalize().replace('_', ' ')}: {getattr(middle_c, attr)}")
Title: Middle C
Album: Minim
Artist: Square Wave
Genre: Game
Codec: lpcm
Bit depth: 24
and edited similarly using dot notation or setattr()
:
middle_c.title = "Middle C (261.63 Hz)"
middle_c.write_metadata()
If changes are made, don’t forget to write them to file using minim.audio.Audio.write_metadata()
.
Converting between audio formats#
Conversion between the supported audio formats is powered by FFmpeg.
To re-encode the previous WAVE audio using the ALAC codec and store it in a MP4 container, use the minim.audio.Audio.convert()
method:
middle_c.convert("alac", filename="middle_c_alac")
Show code cell output
size= 116kB time=00:00:01.02 bitrate= 930.3kbits/s speed= 128x
The call above not only converts the WAVE audio into ALAC audio, but also updates the variable middle_c
to now point to a minim.audio.MP4Audio
file handler for the new file middle_c.m4a
and maintains the metadata:
type(middle_c)
minim.audio.MP4Audio
for attr in ["title", "album", "artist", "genre", "codec", "bit_depth"]:
print(f"{attr.capitalize().replace('_', ' ')}: {getattr(middle_c, attr)}")
Title: Middle C
Album: Minim
Artist: Square Wave
Genre: Game
Codec: alac
Bit depth: 24