Files
metadata-agregator/docs/research/minim/analysis/API.md
T
Alexander a1f6701bac feat: initial implementation of metadata aggregator
- 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
2026-04-28 16:28:53 +02:00

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 exist
  • ValueError: 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 path
  • format: 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 from itunes.SearchAPI.lookup() or search()

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 from qobuz.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 from spotify.WebAPI.get_track() or search()

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 from tidal.API.get_track() or tidal.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 secret
  • access_token: OAuth access token (obtained via set_access_token())
  • access_token_secret: OAuth access token secret
  • personal_access_token: Alternative to OAuth (from Discogs settings)

Authentication: OAuth 1.0a or personal access token. OAuth required for write operations (collection, wantlist).

Methods

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 query
  • type: Result type ("release", "master", "artist", "label")
  • title: Filter by release title
  • artist: Filter by artist name
  • label: Filter by label name
  • genre: Filter by genre
  • year: Filter by release year
  • format: Filter by format (e.g., "Vinyl", "CD")
  • country: Filter by release country
  • per_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 username
  • folder_id: Folder ID (0 = "All" folder)
  • per_page: Results per page
  • page: 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

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 query
  • country: 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 code
  • explicit: 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 ID
  • amg_album_id: All Music Guide album ID
  • upc: Universal Product Code (album barcode)
  • isbn: ISBN (for books)
  • entity: Entity type to return
  • limit: 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 grant
  • password: User password
  • access_token: Existing access token

Authentication: Password grant OAuth. Requires Qobuz subscription for streaming URLs.

Methods

api.search(
    query: str,
    type: str = "tracks",
    limit: int = 50,
    offset: int = 0
) -> dict

Search Qobuz catalog.

Parameters:

  • query: Search query
  • type: Result type ("tracks", "albums", "artists", "playlists")
  • limit: Max results
  • offset: 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 ID
  • quality: 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 ID
  • client_secret: Spotify app client secret
  • redirect_uri: OAuth redirect URI (must match app settings)
  • access_token: Existing access token
  • refresh_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 profile
  • user-read-email: Read user email
  • user-library-read: Read saved tracks/albums
  • user-library-modify: Modify saved tracks/albums
  • playlist-read-private: Read private playlists
  • playlist-modify-public: Modify public playlists
  • playlist-modify-private: Modify private playlists
  • user-read-playback-state: Read playback state
  • user-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 ID
  • market: 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 ID
  • market: Country code
  • fields: Comma-separated list of fields to return (filters response)
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 like artist:Radiohead track:Creep)
  • types: Result types (["track"], ["album"], ["artist"], ["playlist"], or combinations)
  • market: Country code
  • limit: 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 ID
  • uris: 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.0
  • energy: 0.0 to 1.0
  • key: Pitch class (0=C, 1=C#, etc.)
  • loudness: dB
  • mode: Major (1) or minor (0)
  • speechiness: 0.0 to 1.0
  • acousticness: 0.0 to 1.0
  • instrumentalness: 0.0 to 1.0
  • liveness: 0.0 to 1.0
  • valence: 0.0 to 1.0 (positivity)
  • tempo: BPM
  • duration_ms: Track length
  • time_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: Spotify sp_dc cookie 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)

api.search(
    query: str,
    type: str = "TRACKS",
    limit: int = 50,
    offset: int = 0
) -> dict

Search TIDAL catalog.

Parameters:

  • query: Search query
  • type: Result type ("TRACKS", "ALBUMS", "ARTISTS", "PLAYLISTS")
  • limit: Max results
  • offset: 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 ID
  • quality: Audio quality ("LOW", "HIGH", "LOSSLESS", "HI_RES", "HI_RES_LOSSLESS")

Returns: Direct streaming URL (time-limited).

Quality Levels:

  • LOW: 96 kbps AAC
  • HIGH: 320 kbps AAC
  • LOSSLESS: FLAC 16-bit/44.1kHz (CD quality)
  • HI_RES: FLAC 24-bit/96kHz or higher
  • HI_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.