a1f6701bac
- 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
1180 lines
26 KiB
Markdown
1180 lines
26 KiB
Markdown
# 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:**
|
|
```python
|
|
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:**
|
|
```python
|
|
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:**
|
|
```python
|
|
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()
|
|
```python
|
|
audio.read_metadata() -> None
|
|
```
|
|
|
|
Read metadata from file and populate attributes. Called automatically by constructor.
|
|
|
|
#### write_metadata()
|
|
```python
|
|
audio.write_metadata() -> None
|
|
```
|
|
|
|
Write current attribute values to file tags.
|
|
|
|
**Example:**
|
|
```python
|
|
audio = Audio("track.flac")
|
|
audio.title = "New Title"
|
|
audio.artist = "New Artist"
|
|
audio.write_metadata()
|
|
```
|
|
|
|
#### convert()
|
|
```python
|
|
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:**
|
|
```python
|
|
audio = Audio("track.flac")
|
|
mp3_audio = audio.convert("track.mp3", "mp3", bitrate="320k")
|
|
```
|
|
|
|
#### set_metadata_using_itunes()
|
|
```python
|
|
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:**
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
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:**
|
|
```python
|
|
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()
|
|
```python
|
|
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:**
|
|
```python
|
|
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
|
|
|
|
#### search()
|
|
```python
|
|
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:**
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
api.add_to_collection(
|
|
username: str,
|
|
folder_id: int,
|
|
release_id: int
|
|
) -> dict
|
|
```
|
|
|
|
Add release to collection.
|
|
|
|
**Requires:** OAuth authentication.
|
|
|
|
#### remove_from_collection()
|
|
```python
|
|
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()
|
|
```python
|
|
api.get_wantlist(
|
|
username: str,
|
|
per_page: int = 50,
|
|
page: int = 1
|
|
) -> dict
|
|
```
|
|
|
|
Get user's wantlist.
|
|
|
|
#### add_to_wantlist()
|
|
```python
|
|
api.add_to_wantlist(username: str, release_id: int) -> dict
|
|
```
|
|
|
|
Add release to wantlist.
|
|
|
|
**Requires:** OAuth authentication.
|
|
|
|
#### remove_from_wantlist()
|
|
```python
|
|
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:**
|
|
```python
|
|
itunes.SearchAPI()
|
|
```
|
|
|
|
**Authentication:** None required (public API).
|
|
|
|
### Methods
|
|
|
|
#### search()
|
|
```python
|
|
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:**
|
|
```python
|
|
api = itunes.SearchAPI()
|
|
results = api.search("Radiohead", media="music", entity="musicArtist", limit=10)
|
|
|
|
for artist in results["results"]:
|
|
print(artist["artistName"], artist["artistId"])
|
|
```
|
|
|
|
#### lookup()
|
|
```python
|
|
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:**
|
|
```python
|
|
# 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:**
|
|
```python
|
|
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
|
|
|
|
#### search()
|
|
```python
|
|
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()
|
|
```python
|
|
api.get_artist(artist_id: int) -> dict
|
|
```
|
|
|
|
Get artist details.
|
|
|
|
#### get_album()
|
|
```python
|
|
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()
|
|
```python
|
|
api.get_track(track_id: int) -> dict
|
|
```
|
|
|
|
Get track details.
|
|
|
|
#### get_track_file_url()
|
|
```python
|
|
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:**
|
|
```python
|
|
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()
|
|
```python
|
|
api.get_user_playlists(limit: int = 50, offset: int = 0) -> dict
|
|
```
|
|
|
|
Get user's playlists.
|
|
|
|
#### get_playlist()
|
|
```python
|
|
api.get_playlist(playlist_id: str) -> dict
|
|
```
|
|
|
|
Get playlist details with tracks.
|
|
|
|
#### create_playlist()
|
|
```python
|
|
api.create_playlist(
|
|
name: str,
|
|
description: str = "",
|
|
is_public: bool = False
|
|
) -> dict
|
|
```
|
|
|
|
Create new playlist.
|
|
|
|
#### add_playlist_tracks()
|
|
```python
|
|
api.add_playlist_tracks(
|
|
playlist_id: str,
|
|
track_ids: list[int]
|
|
) -> dict
|
|
```
|
|
|
|
Add tracks to playlist.
|
|
|
|
#### get_favorites()
|
|
```python
|
|
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()
|
|
```python
|
|
api.add_favorite(item_id: int, type: str = "tracks") -> dict
|
|
```
|
|
|
|
Add item to favorites.
|
|
|
|
#### remove_favorite()
|
|
```python
|
|
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:**
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
api.set_access_token(method: str = "http.server")
|
|
```
|
|
|
|
Obtain access token via OAuth flow.
|
|
|
|
**Parameters:**
|
|
- `method`: Callback method (`"http.server"`, `"flask"`, `"playwright"`)
|
|
|
|
**Example:**
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
api.get_artist(artist_id: str) -> dict
|
|
```
|
|
|
|
Get artist details.
|
|
|
|
#### get_track()
|
|
```python
|
|
api.get_track(track_id: str, market: str = None) -> dict
|
|
```
|
|
|
|
Get track details.
|
|
|
|
#### get_playlist()
|
|
```python
|
|
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)
|
|
|
|
#### search()
|
|
```python
|
|
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:**
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
api.save_tracks(track_ids: list[str]) -> None
|
|
```
|
|
|
|
Save tracks to user's library.
|
|
|
|
**Requires:** `user-library-modify` scope.
|
|
|
|
#### remove_saved_tracks()
|
|
```python
|
|
api.remove_saved_tracks(track_ids: list[str]) -> None
|
|
```
|
|
|
|
Remove tracks from user's library.
|
|
|
|
**Requires:** `user-library-modify` scope.
|
|
|
|
#### check_saved_tracks()
|
|
```python
|
|
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()
|
|
```python
|
|
api.get_user_playlists(limit: int = 20, offset: int = 0) -> dict
|
|
```
|
|
|
|
Get current user's playlists.
|
|
|
|
**Requires:** `playlist-read-private` scope.
|
|
|
|
#### create_playlist()
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
api.remove_playlist_items(
|
|
playlist_id: str,
|
|
uris: list[str]
|
|
) -> dict
|
|
```
|
|
|
|
Remove tracks from playlist.
|
|
|
|
### Playback Methods
|
|
|
|
#### get_playback_state()
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
api.pause_playback(device_id: str = None) -> None
|
|
```
|
|
|
|
Pause playback.
|
|
|
|
#### skip_to_next()
|
|
```python
|
|
api.skip_to_next(device_id: str = None) -> None
|
|
```
|
|
|
|
Skip to next track.
|
|
|
|
#### skip_to_previous()
|
|
```python
|
|
api.skip_to_previous(device_id: str = None) -> None
|
|
```
|
|
|
|
Skip to previous track.
|
|
|
|
### Audio Features
|
|
|
|
#### get_audio_features()
|
|
```python
|
|
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:**
|
|
```python
|
|
spotify.PrivateLyricsService(sp_dc: str = None)
|
|
```
|
|
|
|
**Parameters:**
|
|
- `sp_dc`: Spotify `sp_dc` cookie value (from browser)
|
|
|
|
#### get_lyrics()
|
|
```python
|
|
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:**
|
|
```python
|
|
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:**
|
|
```python
|
|
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:**
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
api.get_artist(artist_id: int) -> dict
|
|
```
|
|
|
|
Get artist details.
|
|
|
|
#### get_album()
|
|
```python
|
|
api.get_album(album_id: int) -> dict
|
|
```
|
|
|
|
Get album details.
|
|
|
|
#### get_track()
|
|
```python
|
|
api.get_track(track_id: int) -> dict
|
|
```
|
|
|
|
Get track details.
|
|
|
|
### PrivateAPI-Specific Methods
|
|
|
|
#### get_track_stream_url()
|
|
```python
|
|
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:**
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
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()
|
|
```python
|
|
api.get_user_playlists(user_id: int = None, limit: int = 50, offset: int = 0) -> dict
|
|
```
|
|
|
|
Get user's playlists.
|
|
|
|
#### get_playlist()
|
|
```python
|
|
api.get_playlist(playlist_id: str) -> dict
|
|
```
|
|
|
|
Get playlist details with tracks.
|
|
|
|
#### create_playlist()
|
|
```python
|
|
api.create_playlist(title: str, description: str = "") -> dict
|
|
```
|
|
|
|
Create playlist.
|
|
|
|
#### add_playlist_items()
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
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.
|