- gRPC service with MusicBrainz provider - PostgreSQL schema with migrations - Service layer with database-first caching - Repository pattern for data access - YAML configuration support - Research documentation for 17 music metadata projects
26 KiB
minim: API Reference
API Type
minim is a Python library, not an HTTP API. This document describes the public Python API surface for each module.
Import Pattern:
from minim import spotify, tidal, qobuz, discogs, itunes
from minim.audio import Audio
audio Module
Audio Class
Purpose: Read, write, and convert audio file metadata.
Constructor:
Audio(filepath: str)
Parameters:
filepath: Path to audio file (FLAC, MP3, MP4/M4A, Ogg Vorbis, WAVE)
Returns: Subclass instance (FLAC, MP3, MP4, OggVorbis, WAVE) based on file format.
Raises:
FileNotFoundError: File does not existValueError: Unsupported audio format
Example:
audio = Audio("track.flac") # Returns FLAC instance
print(audio.title, audio.artist, audio.album)
Attributes
All Audio instances expose these attributes:
| Attribute | Type | Description |
|---|---|---|
filepath |
str |
Path to audio file |
title |
str |
Track title |
artist |
str |
Primary artist(s), comma-separated |
album |
str |
Album title |
album_artist |
str |
Album artist (for compilations) |
date |
str |
Release date (YYYY-MM-DD or YYYY) |
genre |
str |
Genre classification |
track_number |
int |
Track position in album |
disc_number |
int |
Disc number (for multi-disc albums) |
isrc |
str |
International Standard Recording Code |
upc |
str |
Universal Product Code (album barcode) |
artwork |
bytes |
Album cover image (JPEG or PNG) |
lyrics |
str |
Song lyrics |
comment |
str |
Freeform comment field |
copyright |
str |
Copyright notice |
duration |
float |
Track duration in seconds |
bitrate |
int |
Bitrate in bits per second |
sample_rate |
int |
Sample rate in Hz |
channels |
int |
Number of audio channels (1=mono, 2=stereo) |
Methods
read_metadata()
audio.read_metadata() -> None
Read metadata from file and populate attributes. Called automatically by constructor.
write_metadata()
audio.write_metadata() -> None
Write current attribute values to file tags.
Example:
audio = Audio("track.flac")
audio.title = "New Title"
audio.artist = "New Artist"
audio.write_metadata()
convert()
audio.convert(output_path: str, format: str, **ffmpeg_options) -> Audio
Convert audio file to different format using FFmpeg.
Parameters:
output_path: Destination file pathformat: Target format ("flac","mp3","m4a","ogg","wav")**ffmpeg_options: Additional FFmpeg arguments (e.g.,bitrate="320k",sample_rate=48000)
Returns: New Audio instance for converted file with metadata copied.
Requires: FFmpeg installed and in PATH.
Example:
audio = Audio("track.flac")
mp3_audio = audio.convert("track.mp3", "mp3", bitrate="320k")
set_metadata_using_itunes()
audio.set_metadata_using_itunes(data: dict) -> None
Map iTunes Search API track object to audio metadata.
Parameters:
data: Track object fromitunes.SearchAPI.lookup()orsearch()
Example:
itunes_api = itunes.SearchAPI()
results = itunes_api.search("Radiohead Creep", media="music", entity="song")
track = results["results"][0]
audio = Audio("track.mp3")
audio.set_metadata_using_itunes(track)
audio.write_metadata()
set_metadata_using_qobuz()
audio.set_metadata_using_qobuz(data: dict) -> None
Map Qobuz API track object to audio metadata.
Parameters:
data: Track object fromqobuz.PrivateAPI.get_track()
set_metadata_using_spotify()
audio.set_metadata_using_spotify(data: dict) -> None
Map Spotify Web API track object to audio metadata.
Parameters:
data: Track object fromspotify.WebAPI.get_track()orsearch()
Example:
spotify_api = spotify.WebAPI(client_id="...", client_secret="...")
spotify_api.set_flow("client_credentials")
spotify_api.set_access_token()
track = spotify_api.get_track("3n3Ppam7vgaVa1iaRUc9Lp") # Radiohead - Creep
audio = Audio("track.flac")
audio.set_metadata_using_spotify(track)
audio.write_metadata()
set_metadata_using_tidal()
audio.set_metadata_using_tidal(data: dict) -> None
Map TIDAL API track object to audio metadata.
Parameters:
data: Track object fromtidal.API.get_track()ortidal.PrivateAPI.get_track()
discogs Module
API Class
Purpose: Interact with Discogs database, marketplace, collection, and wantlist.
Constructor:
discogs.API(
consumer_key: str = None,
consumer_secret: str = None,
access_token: str = None,
access_token_secret: str = None,
personal_access_token: str = None
)
Parameters:
consumer_key: OAuth consumer key (from Discogs app settings)consumer_secret: OAuth consumer secretaccess_token: OAuth access token (obtained viaset_access_token())access_token_secret: OAuth access token secretpersonal_access_token: Alternative to OAuth (from Discogs settings)
Authentication: OAuth 1.0a or personal access token. OAuth required for write operations (collection, wantlist).
Methods
search()
api.search(
query: str = None,
type: str = None,
title: str = None,
artist: str = None,
label: str = None,
genre: str = None,
year: str = None,
format: str = None,
country: str = None,
per_page: int = 50,
page: int = 1
) -> dict
Search Discogs database.
Parameters:
query: General search querytype: Result type ("release","master","artist","label")title: Filter by release titleartist: Filter by artist namelabel: Filter by label namegenre: Filter by genreyear: Filter by release yearformat: Filter by format (e.g.,"Vinyl","CD")country: Filter by release countryper_page: Results per page (max 100)page: Page number
Returns: Dict with results array and pagination info.
Example:
api = discogs.API(personal_access_token="...")
results = api.search(artist="Radiohead", type="release", format="Vinyl")
for release in results["results"]:
print(release["title"], release["year"])
get_artist()
api.get_artist(artist_id: int) -> dict
Get artist details.
Parameters:
artist_id: Discogs artist ID
Returns: Artist object with name, profile, images, members, URLs.
get_release()
api.get_release(release_id: int) -> dict
Get release details.
Parameters:
release_id: Discogs release ID
Returns: Release object with tracklist, artists, labels, formats, identifiers (barcode, catalog number).
get_master_release()
api.get_master_release(master_id: int) -> dict
Get master release details (canonical version of a release with multiple pressings).
Parameters:
master_id: Discogs master release ID
Returns: Master release object with main release info and versions list.
get_label()
api.get_label(label_id: int) -> dict
Get label details.
Parameters:
label_id: Discogs label ID
Returns: Label object with name, profile, images, sublabels, releases.
get_collection_folders()
api.get_collection_folders(username: str) -> dict
Get user's collection folders.
Parameters:
username: Discogs username
Returns: Array of folder objects with ID, name, count.
get_collection_items()
api.get_collection_items(
username: str,
folder_id: int = 0,
per_page: int = 50,
page: int = 1
) -> dict
Get items in collection folder.
Parameters:
username: Discogs usernamefolder_id: Folder ID (0 = "All" folder)per_page: Results per pagepage: Page number
Returns: Array of collection items with release info, notes, rating.
add_to_collection()
api.add_to_collection(
username: str,
folder_id: int,
release_id: int
) -> dict
Add release to collection.
Requires: OAuth authentication.
remove_from_collection()
api.remove_from_collection(
username: str,
folder_id: int,
release_id: int,
instance_id: int
) -> None
Remove release from collection.
Requires: OAuth authentication.
get_wantlist()
api.get_wantlist(
username: str,
per_page: int = 50,
page: int = 1
) -> dict
Get user's wantlist.
add_to_wantlist()
api.add_to_wantlist(username: str, release_id: int) -> dict
Add release to wantlist.
Requires: OAuth authentication.
remove_from_wantlist()
api.remove_from_wantlist(username: str, release_id: int) -> None
Remove release from wantlist.
Requires: OAuth authentication.
itunes Module
SearchAPI Class
Purpose: Search and lookup items in iTunes catalog.
Constructor:
itunes.SearchAPI()
Authentication: None required (public API).
Methods
search()
api.search(
term: str,
country: str = "US",
media: str = "all",
entity: str = None,
attribute: str = None,
limit: int = 50,
lang: str = "en_us",
explicit: str = "Yes"
) -> dict
Search iTunes catalog.
Parameters:
term: Search querycountry: Two-letter country code (e.g.,"US","GB","JP")media: Media type ("all","music","movie","podcast","audiobook", etc.)entity: Entity type ("song","album","musicArtist","movie", etc.)attribute: Search attribute ("artistTerm","albumTerm","songTerm", etc.)limit: Max results (max 200)lang: Language codeexplicit: Include explicit content ("Yes"or"No")
Returns: Dict with resultCount and results array.
Example:
api = itunes.SearchAPI()
results = api.search("Radiohead", media="music", entity="musicArtist", limit=10)
for artist in results["results"]:
print(artist["artistName"], artist["artistId"])
lookup()
api.lookup(
id: int = None,
amg_artist_id: int = None,
amg_album_id: int = None,
upc: str = None,
isbn: str = None,
entity: str = None,
limit: int = 50
) -> dict
Lookup item by ID or identifier.
Parameters:
id: iTunes ID (artist, album, track, etc.)amg_artist_id: All Music Guide artist IDamg_album_id: All Music Guide album IDupc: Universal Product Code (album barcode)isbn: ISBN (for books)entity: Entity type to returnlimit: Max results
Returns: Dict with resultCount and results array.
Example:
# Lookup by iTunes ID
results = api.lookup(id=1419227, entity="album")
# Lookup by UPC
results = api.lookup(upc="724384260910")
qobuz Module
PrivateAPI Class
Purpose: Interact with Qobuz catalog and streaming service.
Note: Uses undocumented private API endpoints. May break without notice.
Constructor:
qobuz.PrivateAPI(
app_id: str = None,
app_secret: str = None,
email: str = None,
password: str = None,
access_token: str = None
)
Parameters:
app_id: Qobuz app ID (auto-extracted from web player if not provided)app_secret: Qobuz app secret (auto-extracted)email: User email for password grantpassword: User passwordaccess_token: Existing access token
Authentication: Password grant OAuth. Requires Qobuz subscription for streaming URLs.
Methods
search()
api.search(
query: str,
type: str = "tracks",
limit: int = 50,
offset: int = 0
) -> dict
Search Qobuz catalog.
Parameters:
query: Search querytype: Result type ("tracks","albums","artists","playlists")limit: Max resultsoffset: Pagination offset
Returns: Dict with type-specific results array.
get_artist()
api.get_artist(artist_id: int) -> dict
Get artist details.
get_album()
api.get_album(album_id: str) -> dict
Get album details with tracklist.
Parameters:
album_id: Qobuz album ID (string, not int)
Returns: Album object with tracks, artists, label, release date, UPC.
get_track()
api.get_track(track_id: int) -> dict
Get track details.
get_track_file_url()
api.get_track_file_url(
track_id: int,
quality: int = 27
) -> dict
Get streaming URL for track.
Parameters:
track_id: Qobuz track IDquality: Audio quality (5=MP3 320kbps, 6=FLAC 16/44.1, 7=FLAC 24/96, 27=FLAC Hi-Res)
Returns: Dict with url, format_id, mime_type, sampling_rate, bit_depth.
Requires: Active Qobuz subscription. Quality availability depends on subscription tier.
Example:
api = qobuz.PrivateAPI(email="user@example.com", password="...")
api.set_access_token()
track = api.get_track(12345678)
file_url = api.get_track_file_url(track["id"], quality=27)
# Download track
import requests
audio_data = requests.get(file_url["url"]).content
with open("track.flac", "wb") as f:
f.write(audio_data)
get_user_playlists()
api.get_user_playlists(limit: int = 50, offset: int = 0) -> dict
Get user's playlists.
get_playlist()
api.get_playlist(playlist_id: str) -> dict
Get playlist details with tracks.
create_playlist()
api.create_playlist(
name: str,
description: str = "",
is_public: bool = False
) -> dict
Create new playlist.
add_playlist_tracks()
api.add_playlist_tracks(
playlist_id: str,
track_ids: list[int]
) -> dict
Add tracks to playlist.
get_favorites()
api.get_favorites(
type: str = "tracks",
limit: int = 50,
offset: int = 0
) -> dict
Get user's favorites.
Parameters:
type: Favorite type ("tracks","albums","artists")
add_favorite()
api.add_favorite(item_id: int, type: str = "tracks") -> dict
Add item to favorites.
remove_favorite()
api.remove_favorite(item_id: int, type: str = "tracks") -> dict
Remove item from favorites.
spotify Module
WebAPI Class
Purpose: Interact with Spotify Web API.
Constructor:
spotify.WebAPI(
client_id: str = None,
client_secret: str = None,
redirect_uri: str = "http://localhost:8888",
access_token: str = None,
refresh_token: str = None
)
Parameters:
client_id: Spotify app client IDclient_secret: Spotify app client secretredirect_uri: OAuth redirect URI (must match app settings)access_token: Existing access tokenrefresh_token: Existing refresh token
Authentication: OAuth 2.0 with four flow types (set via set_flow()).
Authentication Methods
set_flow()
api.set_flow(
flow_type: str = "authorization_code",
scopes: list[str] = None
)
Configure OAuth flow.
Parameters:
flow_type: Flow type ("authorization_code","pkce","client_credentials","web_player")scopes: Permission scopes (see Spotify documentation for available scopes)
Common Scopes:
user-read-private: Read user profileuser-read-email: Read user emailuser-library-read: Read saved tracks/albumsuser-library-modify: Modify saved tracks/albumsplaylist-read-private: Read private playlistsplaylist-modify-public: Modify public playlistsplaylist-modify-private: Modify private playlistsuser-read-playback-state: Read playback stateuser-modify-playback-state: Control playback
set_access_token()
api.set_access_token(method: str = "http.server")
Obtain access token via OAuth flow.
Parameters:
method: Callback method ("http.server","flask","playwright")
Example:
api = spotify.WebAPI(client_id="...", client_secret="...")
api.set_flow("authorization_code", scopes=["user-library-read", "playlist-read-private"])
api.set_access_token() # Opens browser for user login
Catalog Methods
get_album()
api.get_album(album_id: str, market: str = None) -> dict
Get album details.
Parameters:
album_id: Spotify album IDmarket: ISO 3166-1 alpha-2 country code (affects track availability)
get_artist()
api.get_artist(artist_id: str) -> dict
Get artist details.
get_track()
api.get_track(track_id: str, market: str = None) -> dict
Get track details.
get_playlist()
api.get_playlist(
playlist_id: str,
market: str = None,
fields: str = None
) -> dict
Get playlist details.
Parameters:
playlist_id: Spotify playlist IDmarket: Country codefields: Comma-separated list of fields to return (filters response)
search()
api.search(
query: str,
types: list[str] = ["track"],
market: str = None,
limit: int = 20,
offset: int = 0
) -> dict
Search Spotify catalog.
Parameters:
query: Search query (supports field filters likeartist:Radiohead track:Creep)types: Result types (["track"],["album"],["artist"],["playlist"], or combinations)market: Country codelimit: Max results per type (max 50)offset: Pagination offset
Returns: Dict with keys for each type (tracks, albums, artists, playlists), each containing items array and pagination info.
Example:
results = api.search("Radiohead Creep", types=["track", "album"], limit=10)
for track in results["tracks"]["items"]:
print(track["name"], track["artists"][0]["name"])
Library Methods
get_saved_tracks()
api.get_saved_tracks(limit: int = 20, offset: int = 0, market: str = None) -> dict
Get user's saved tracks.
Requires: user-library-read scope.
save_tracks()
api.save_tracks(track_ids: list[str]) -> None
Save tracks to user's library.
Requires: user-library-modify scope.
remove_saved_tracks()
api.remove_saved_tracks(track_ids: list[str]) -> None
Remove tracks from user's library.
Requires: user-library-modify scope.
check_saved_tracks()
api.check_saved_tracks(track_ids: list[str]) -> list[bool]
Check if tracks are in user's library.
Returns: Array of booleans corresponding to input track IDs.
Playlist Methods
get_user_playlists()
api.get_user_playlists(limit: int = 20, offset: int = 0) -> dict
Get current user's playlists.
Requires: playlist-read-private scope.
create_playlist()
api.create_playlist(
user_id: str,
name: str,
description: str = "",
public: bool = True
) -> dict
Create playlist.
Requires: playlist-modify-public or playlist-modify-private scope.
add_playlist_items()
api.add_playlist_items(
playlist_id: str,
uris: list[str],
position: int = None
) -> dict
Add tracks to playlist.
Parameters:
playlist_id: Spotify playlist IDuris: Track URIs (format:spotify:track:{id})position: Insert position (0-based, None = append)
remove_playlist_items()
api.remove_playlist_items(
playlist_id: str,
uris: list[str]
) -> dict
Remove tracks from playlist.
Playback Methods
get_playback_state()
api.get_playback_state(market: str = None) -> dict
Get current playback state.
Requires: user-read-playback-state scope.
Returns: Dict with device, track, progress, shuffle/repeat state, or None if nothing playing.
start_playback()
api.start_playback(
device_id: str = None,
context_uri: str = None,
uris: list[str] = None,
offset: dict = None,
position_ms: int = None
) -> None
Start or resume playback.
Requires: user-modify-playback-state scope.
pause_playback()
api.pause_playback(device_id: str = None) -> None
Pause playback.
skip_to_next()
api.skip_to_next(device_id: str = None) -> None
Skip to next track.
skip_to_previous()
api.skip_to_previous(device_id: str = None) -> None
Skip to previous track.
Audio Features
get_audio_features()
api.get_audio_features(track_id: str) -> dict
Get audio features for track.
Returns: Dict with:
danceability: 0.0 to 1.0energy: 0.0 to 1.0key: Pitch class (0=C, 1=C#, etc.)loudness: dBmode: Major (1) or minor (0)speechiness: 0.0 to 1.0acousticness: 0.0 to 1.0instrumentalness: 0.0 to 1.0liveness: 0.0 to 1.0valence: 0.0 to 1.0 (positivity)tempo: BPMduration_ms: Track lengthtime_signature: 3 to 7
PrivateLyricsService Class
Purpose: Get lyrics via Spotify's Musixmatch integration.
Note: Undocumented private API. May break without notice.
Constructor:
spotify.PrivateLyricsService(sp_dc: str = None)
Parameters:
sp_dc: Spotifysp_dccookie value (from browser)
get_lyrics()
service.get_lyrics(track_id: str) -> dict
Get lyrics for track.
Returns: Dict with lyrics object containing lines array. Each line has startTimeMs, words, syllables.
Example:
service = spotify.PrivateLyricsService(sp_dc="...")
lyrics = service.get_lyrics("3n3Ppam7vgaVa1iaRUc9Lp")
for line in lyrics["lyrics"]["lines"]:
print(f"[{line['startTimeMs']}ms] {line['words']}")
tidal Module
API Class
Purpose: Interact with TIDAL public API.
Constructor:
tidal.API(
client_id: str = None,
client_secret: str = None,
access_token: str = None,
refresh_token: str = None
)
Authentication: OAuth 2.0 (client credentials or PKCE).
PrivateAPI Class
Purpose: Interact with TIDAL private API (streaming URLs, lyrics, credits).
Note: Uses undocumented endpoints. May break without notice.
Constructor:
tidal.PrivateAPI(
client_id: str = None,
client_secret: str = None,
access_token: str = None,
refresh_token: str = None,
user_id: int = None,
country_code: str = "US"
)
Methods (Common to Both Classes)
search()
api.search(
query: str,
type: str = "TRACKS",
limit: int = 50,
offset: int = 0
) -> dict
Search TIDAL catalog.
Parameters:
query: Search querytype: Result type ("TRACKS","ALBUMS","ARTISTS","PLAYLISTS")limit: Max resultsoffset: Pagination offset
get_artist()
api.get_artist(artist_id: int) -> dict
Get artist details.
get_album()
api.get_album(album_id: int) -> dict
Get album details.
get_track()
api.get_track(track_id: int) -> dict
Get track details.
PrivateAPI-Specific Methods
get_track_stream_url()
api.get_track_stream_url(
track_id: int,
quality: str = "LOSSLESS"
) -> str
Get streaming URL for track.
Parameters:
track_id: TIDAL track IDquality: Audio quality ("LOW","HIGH","LOSSLESS","HI_RES","HI_RES_LOSSLESS")
Returns: Direct streaming URL (time-limited).
Quality Levels:
LOW: 96 kbps AACHIGH: 320 kbps AACLOSSLESS: FLAC 16-bit/44.1kHz (CD quality)HI_RES: FLAC 24-bit/96kHz or higherHI_RES_LOSSLESS: MQA (Master Quality Authenticated)
Requires: Active TIDAL subscription. Quality availability depends on subscription tier (HiFi, HiFi Plus).
Example:
api = tidal.PrivateAPI(client_id="...", client_secret="...")
api.set_flow("pkce")
api.set_access_token()
url = api.get_track_stream_url(12345678, quality="HI_RES")
import requests
audio_data = requests.get(url).content
with open("track.flac", "wb") as f:
f.write(audio_data)
get_track_lyrics()
api.get_track_lyrics(track_id: int) -> dict
Get lyrics for track.
Returns: Dict with lyrics (plain text) and subtitles (LRC format with timestamps).
get_track_credits()
api.get_track_credits(track_id: int) -> dict
Get credits for track (producers, engineers, musicians, etc.).
Returns: Dict with credits array containing type and contributors.
get_user_playlists()
api.get_user_playlists(user_id: int = None, limit: int = 50, offset: int = 0) -> dict
Get user's playlists.
get_playlist()
api.get_playlist(playlist_id: str) -> dict
Get playlist details with tracks.
create_playlist()
api.create_playlist(title: str, description: str = "") -> dict
Create playlist.
add_playlist_items()
api.add_playlist_items(playlist_id: str, track_ids: list[int]) -> dict
Add tracks to playlist.
Error Handling
All API methods raise RuntimeError on HTTP errors:
try:
track = api.get_track(12345)
except RuntimeError as e:
print(f"API error: {e}")
Error Message Format: "{METHOD} {URL} failed: {status_code} {response_text}"
No Typed Exceptions: All errors are generic RuntimeError. Parse error message to determine cause.
Rate Limiting
Not Implemented: minim does not enforce rate limits. Caller must respect service limits:
- Discogs: 60 req/min (authenticated), 25 req/min (unauthenticated)
- iTunes: ~20 req/min (undocumented)
- Qobuz: Unknown (private API)
- Spotify: Varies by endpoint, typically 180 req/30sec
- TIDAL: Unknown (private API)
Recommendation: Implement exponential backoff and respect Retry-After headers.
Summary
minim provides comprehensive Python APIs for five music services:
- discogs: Database, marketplace, collection, wantlist
- itunes: Public search and lookup
- qobuz: Catalog, streaming, playlists, favorites
- spotify: Full Web API coverage, playback control, audio features, lyrics
- tidal: Catalog, high-resolution streaming, lyrics, credits
All APIs follow consistent patterns (authentication, request handling, error raising) while exposing service-specific features. The Audio class bridges API responses to audio file metadata, enabling automated tagging workflows.