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
923 lines
25 KiB
Markdown
923 lines
25 KiB
Markdown
# minim: Service Integrations
|
|
|
|
## Overview
|
|
|
|
minim integrates with five music services, each with different authentication methods, API coverage, and capabilities:
|
|
|
|
| Service | Auth Method | API Type | Streaming | Lyrics | Credits | Rate Limit |
|
|
|---------|-------------|----------|-----------|--------|---------|------------|
|
|
| Discogs | OAuth 1.0a, Personal Token | Public | No | No | Yes | 60/min (auth), 25/min (unauth) |
|
|
| iTunes | None | Public | No | No | No | ~20/min |
|
|
| Qobuz | Password Grant | Private | Yes | No | No | Unknown |
|
|
| Spotify | OAuth 2.0 (4 flows) | Public + Private | No | Yes | No | 180/30sec |
|
|
| TIDAL | OAuth 2.0 (PKCE, Client Creds) | Public + Private | Yes | Yes | Yes | Unknown |
|
|
|
|
## Discogs Integration
|
|
|
|
### Service Overview
|
|
|
|
**Purpose:** Music database, marketplace, collection management
|
|
**Website:** https://www.discogs.com
|
|
**API Documentation:** https://www.discogs.com/developers
|
|
**API Type:** Public, documented, RESTful
|
|
|
|
### Authentication
|
|
|
|
**Method 1: OAuth 1.0a**
|
|
|
|
**Setup:**
|
|
1. Create app at https://www.discogs.com/settings/developers
|
|
2. Obtain consumer key and consumer secret
|
|
3. Implement OAuth 1.0a flow (request token → user authorization → access token)
|
|
|
|
**Implementation:**
|
|
```python
|
|
from minim import discogs
|
|
|
|
api = discogs.API(
|
|
consumer_key="Abcd1234Efgh5678",
|
|
consumer_secret="IjklMnopQrstUvwx"
|
|
)
|
|
|
|
# OAuth flow (opens browser)
|
|
api.set_access_token()
|
|
|
|
# Tokens saved to ~/minim.cfg
|
|
```
|
|
|
|
**Method 2: Personal Access Token**
|
|
|
|
**Setup:**
|
|
1. Generate token at https://www.discogs.com/settings/developers
|
|
2. Use token directly (no OAuth flow)
|
|
|
|
**Implementation:**
|
|
```python
|
|
api = discogs.API(personal_access_token="YourPersonalToken")
|
|
```
|
|
|
|
**Comparison:**
|
|
- OAuth 1.0a: Required for write operations (collection, wantlist), higher rate limit
|
|
- Personal Token: Read-only, simpler setup, same rate limit as OAuth
|
|
|
|
### API Coverage
|
|
|
|
**Database:**
|
|
- Search releases, artists, labels, masters
|
|
- Get detailed information (tracklist, credits, images, identifiers)
|
|
- Browse by genre, style, format, year, country
|
|
|
|
**Marketplace:**
|
|
- Search listings
|
|
- Get listing details (price, condition, seller)
|
|
- Not implemented in minim (read-only marketplace access)
|
|
|
|
**Collection:**
|
|
- Get user's collection folders
|
|
- List items in folder
|
|
- Add/remove releases
|
|
- Update notes and rating
|
|
|
|
**Wantlist:**
|
|
- Get user's wantlist
|
|
- Add/remove releases
|
|
- Not implemented: Update notes
|
|
|
|
**User:**
|
|
- Get user profile
|
|
- Get user submissions (releases, artists, labels)
|
|
- Not implemented in minim
|
|
|
|
### Data Model
|
|
|
|
**Release Object:**
|
|
```json
|
|
{
|
|
"id": 249504,
|
|
"title": "OK Computer",
|
|
"artists": [{"name": "Radiohead", "id": 3840}],
|
|
"labels": [{"name": "Parlophone", "catno": "7243 8 55229 2 5"}],
|
|
"formats": [{"name": "CD", "qty": "1", "descriptions": ["Album"]}],
|
|
"year": 1997,
|
|
"country": "UK",
|
|
"genres": ["Electronic", "Rock"],
|
|
"styles": ["Alternative Rock", "Experimental"],
|
|
"tracklist": [
|
|
{"position": "1", "title": "Airbag", "duration": "4:44"},
|
|
{"position": "2", "title": "Paranoid Android", "duration": "6:23"}
|
|
],
|
|
"identifiers": [
|
|
{"type": "Barcode", "value": "724385522925"}
|
|
],
|
|
"images": [
|
|
{"type": "primary", "uri": "https://...", "width": 600, "height": 600}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Artist Object:**
|
|
```json
|
|
{
|
|
"id": 3840,
|
|
"name": "Radiohead",
|
|
"profile": "English rock band formed in 1985...",
|
|
"members": [
|
|
{"name": "Thom Yorke", "id": 239},
|
|
{"name": "Jonny Greenwood", "id": 240}
|
|
],
|
|
"urls": ["https://www.radiohead.com"],
|
|
"images": [...]
|
|
}
|
|
```
|
|
|
|
### Rate Limiting
|
|
|
|
**Authenticated:** 60 requests per minute
|
|
**Unauthenticated:** 25 requests per minute
|
|
|
|
**Headers:**
|
|
- `X-Discogs-Ratelimit`: Total requests allowed per minute
|
|
- `X-Discogs-Ratelimit-Remaining`: Requests remaining in current window
|
|
- `X-Discogs-Ratelimit-Used`: Requests used in current window
|
|
|
|
**Enforcement:** HTTP 429 (Too Many Requests) when limit exceeded.
|
|
|
|
**minim Implementation:** Does not check rate limit headers. Caller responsible for tracking.
|
|
|
|
### Use Cases
|
|
|
|
1. **Metadata Enrichment:** Get detailed release information (catalog numbers, barcodes, formats)
|
|
2. **Collection Management:** Sync local library with Discogs collection
|
|
3. **Credits Extraction:** Get producer, engineer, musician credits from tracklist
|
|
4. **Format Identification:** Determine pressing details (country, year, label, catalog number)
|
|
|
|
### Limitations
|
|
|
|
- No streaming or preview URLs
|
|
- No lyrics
|
|
- Marketplace write operations not implemented
|
|
- User-submitted data (quality varies)
|
|
- Rate limiting requires manual tracking
|
|
|
|
## iTunes Integration
|
|
|
|
### Service Overview
|
|
|
|
**Purpose:** Public music catalog search and lookup
|
|
**Website:** https://www.apple.com/itunes
|
|
**API Documentation:** https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/iTuneSearchAPI
|
|
**API Type:** Public, documented, RESTful
|
|
|
|
### Authentication
|
|
|
|
**None required.** iTunes Search API is completely public.
|
|
|
|
**Implementation:**
|
|
```python
|
|
from minim import itunes
|
|
|
|
api = itunes.SearchAPI()
|
|
results = api.search("Radiohead", media="music", entity="musicArtist")
|
|
```
|
|
|
|
### API Coverage
|
|
|
|
**Search:**
|
|
- Search by term across all media types
|
|
- Filter by media (music, movie, podcast, audiobook, etc.)
|
|
- Filter by entity (song, album, artist, etc.)
|
|
- Filter by attribute (artist name, album name, song name, etc.)
|
|
|
|
**Lookup:**
|
|
- Lookup by iTunes ID
|
|
- Lookup by UPC (album barcode)
|
|
- Lookup by ISBN (books)
|
|
- Lookup by AMG (All Music Guide) ID
|
|
|
|
**Not Available:**
|
|
- Streaming URLs
|
|
- Lyrics
|
|
- User library access
|
|
- Playlist management
|
|
|
|
### Data Model
|
|
|
|
**Track Object:**
|
|
```json
|
|
{
|
|
"trackId": 1109731797,
|
|
"trackName": "Creep",
|
|
"artistName": "Radiohead",
|
|
"collectionName": "Pablo Honey",
|
|
"collectionId": 1109731533,
|
|
"artistId": 657515,
|
|
"trackNumber": 2,
|
|
"trackCount": 12,
|
|
"discNumber": 1,
|
|
"discCount": 1,
|
|
"releaseDate": "1993-02-22T08:00:00Z",
|
|
"primaryGenreName": "Alternative",
|
|
"trackTimeMillis": 238640,
|
|
"country": "USA",
|
|
"isrc": "GBAYE9200070",
|
|
"artworkUrl30": "https://.../30x30bb.jpg",
|
|
"artworkUrl60": "https://.../60x60bb.jpg",
|
|
"artworkUrl100": "https://.../100x100bb.jpg",
|
|
"previewUrl": "https://.../preview.m4a",
|
|
"trackViewUrl": "https://music.apple.com/us/album/creep/1109731533?i=1109731797"
|
|
}
|
|
```
|
|
|
|
**Album Object:**
|
|
```json
|
|
{
|
|
"collectionId": 1109731533,
|
|
"collectionName": "Pablo Honey",
|
|
"artistName": "Radiohead",
|
|
"artistId": 657515,
|
|
"trackCount": 12,
|
|
"releaseDate": "1993-02-22T08:00:00Z",
|
|
"primaryGenreName": "Alternative",
|
|
"copyright": "℗ 1993 XL Recordings Ltd.",
|
|
"country": "USA",
|
|
"artworkUrl100": "https://.../100x100bb.jpg",
|
|
"collectionViewUrl": "https://music.apple.com/us/album/pablo-honey/1109731533"
|
|
}
|
|
```
|
|
|
|
### Rate Limiting
|
|
|
|
**Limit:** Approximately 20 requests per minute (undocumented)
|
|
**Enforcement:** HTTP 403 (Forbidden) when limit exceeded
|
|
**Headers:** No rate limit headers provided
|
|
|
|
**Recommendation:** Implement exponential backoff on 403 errors.
|
|
|
|
### Use Cases
|
|
|
|
1. **Quick Metadata Lookup:** Get basic track/album info without authentication
|
|
2. **UPC Lookup:** Find albums by barcode
|
|
3. **Preview URLs:** Get 30-second preview clips (M4A format)
|
|
4. **Artwork:** Get album artwork in multiple sizes (30x30, 60x60, 100x100)
|
|
|
|
### Limitations
|
|
|
|
- No high-resolution artwork (max 100x100 pixels, can be scaled to 600x600 by changing URL)
|
|
- No streaming URLs (only 30-second previews)
|
|
- No lyrics
|
|
- No user-specific data
|
|
- Rate limit is undocumented and may change
|
|
- Search results limited to 200 items
|
|
|
|
## Qobuz Integration
|
|
|
|
### Service Overview
|
|
|
|
**Purpose:** High-resolution music streaming and downloads
|
|
**Website:** https://www.qobuz.com
|
|
**API Documentation:** None (private API)
|
|
**API Type:** Private, undocumented, reverse-engineered
|
|
|
|
### Authentication
|
|
|
|
**Method:** Password Grant OAuth 2.0
|
|
|
|
**Setup:**
|
|
1. Qobuz account with active subscription
|
|
2. App ID and secret auto-extracted from web player JavaScript
|
|
3. Email and password for authentication
|
|
|
|
**Implementation:**
|
|
```python
|
|
from minim import qobuz
|
|
|
|
api = qobuz.PrivateAPI(
|
|
email="user@example.com",
|
|
password="YourPassword"
|
|
)
|
|
|
|
# Automatic app_id/secret extraction and token acquisition
|
|
api.set_access_token()
|
|
```
|
|
|
|
**App ID/Secret Extraction:**
|
|
```python
|
|
def _get_app_credentials(self):
|
|
# Fetch Qobuz web player
|
|
response = requests.get("https://play.qobuz.com")
|
|
html = response.text
|
|
|
|
# Extract bundle URL from HTML
|
|
bundle_url_match = re.search(r'<script src="(/resources/\d+\.\d+\.\d+-[a-z]\d+/bundle\.js)"', html)
|
|
bundle_url = "https://play.qobuz.com" + bundle_url_match.group(1)
|
|
|
|
# Fetch bundle JavaScript
|
|
bundle_js = requests.get(bundle_url).text
|
|
|
|
# Extract app_id and secrets array
|
|
app_id = re.search(r'production:{api:{appId:"(\d+)"', bundle_js).group(1)
|
|
secrets = re.findall(r'[a-f0-9]{32}', bundle_js)
|
|
|
|
# Test secrets to find valid one
|
|
for secret in secrets:
|
|
if self._test_secret(app_id, secret):
|
|
return app_id, secret
|
|
```
|
|
|
|
**Security Note:** This method violates Qobuz terms of service. Use at your own risk.
|
|
|
|
### API Coverage
|
|
|
|
**Catalog:**
|
|
- Search tracks, albums, artists, playlists
|
|
- Get detailed information
|
|
- Browse by genre, new releases, charts
|
|
|
|
**Streaming:**
|
|
- Get streaming URLs with quality selection
|
|
- Quality levels: MP3 320kbps, FLAC 16/44.1, FLAC 24/96, FLAC Hi-Res (up to 24/192)
|
|
- Download tracks (within subscription terms)
|
|
|
|
**User Library:**
|
|
- Get user playlists
|
|
- Create, update, delete playlists
|
|
- Add/remove tracks from playlists
|
|
- Get favorites (tracks, albums, artists)
|
|
- Add/remove favorites
|
|
|
|
**Not Available:**
|
|
- Lyrics
|
|
- Credits (producer, engineer, etc.)
|
|
- User playback history
|
|
|
|
### Data Model
|
|
|
|
**Track Object:**
|
|
```json
|
|
{
|
|
"id": 12345678,
|
|
"title": "Creep",
|
|
"duration": 238,
|
|
"track_number": 2,
|
|
"media_number": 1,
|
|
"isrc": "GBAYE9200070",
|
|
"performer": {"name": "Radiohead", "id": 12345},
|
|
"album": {
|
|
"id": "0060254734729",
|
|
"title": "Pablo Honey",
|
|
"release_date_original": "1993-02-22",
|
|
"upc": "0060254734729",
|
|
"image": {
|
|
"small": "https://.../230x230.jpg",
|
|
"large": "https://.../600x600.jpg"
|
|
},
|
|
"label": {"name": "XL Recordings"}
|
|
},
|
|
"maximum_bit_depth": 16,
|
|
"maximum_sampling_rate": 44.1
|
|
}
|
|
```
|
|
|
|
**Streaming URL Response:**
|
|
```json
|
|
{
|
|
"url": "https://streaming.qobuz.com/...",
|
|
"format_id": 27,
|
|
"mime_type": "audio/flac",
|
|
"sampling_rate": 44.1,
|
|
"bit_depth": 16,
|
|
"restrictions": []
|
|
}
|
|
```
|
|
|
|
### Quality Levels
|
|
|
|
| Format ID | Quality | Codec | Bitrate/Depth | Subscription Tier |
|
|
|-----------|---------|-------|---------------|-------------------|
|
|
| 5 | MP3 | MP3 | 320 kbps | Studio |
|
|
| 6 | CD | FLAC | 16-bit/44.1kHz | Studio |
|
|
| 7 | Hi-Res | FLAC | 24-bit/96kHz | Studio Sublime |
|
|
| 27 | Hi-Res | FLAC | Up to 24-bit/192kHz | Studio Sublime |
|
|
|
|
**Availability:** Quality depends on:
|
|
1. User's subscription tier
|
|
2. Album's available formats
|
|
3. Geographic restrictions
|
|
|
|
### Rate Limiting
|
|
|
|
**Unknown.** Private API does not document rate limits.
|
|
|
|
**Observation:** Aggressive usage (>100 requests/minute) may trigger temporary blocks.
|
|
|
|
**Recommendation:** Implement conservative rate limiting (10-20 requests/minute).
|
|
|
|
### Use Cases
|
|
|
|
1. **High-Resolution Downloads:** Get FLAC files up to 24-bit/192kHz
|
|
2. **Metadata Enrichment:** Get detailed album info (label, UPC, release date)
|
|
3. **Playlist Management:** Sync playlists between services
|
|
4. **Favorites Sync:** Export/import favorite tracks
|
|
|
|
### Limitations
|
|
|
|
- Private API (may break without notice)
|
|
- Requires active subscription
|
|
- No lyrics or credits
|
|
- Geographic restrictions on content
|
|
- Terms of service violations (use at own risk)
|
|
|
|
## Spotify Integration
|
|
|
|
### Service Overview
|
|
|
|
**Purpose:** Music streaming, discovery, and social features
|
|
**Website:** https://www.spotify.com
|
|
**API Documentation:** https://developer.spotify.com/documentation/web-api
|
|
**API Type:** Public (Web API) + Private (Lyrics)
|
|
|
|
### Authentication
|
|
|
|
**Method:** OAuth 2.0 with four flow types
|
|
|
|
**Flow 1: Authorization Code**
|
|
- Full user access with refresh token
|
|
- Requires user login via browser
|
|
- Best for web applications
|
|
|
|
**Flow 2: PKCE (Proof Key for Code Exchange)**
|
|
- For mobile and desktop apps
|
|
- No client secret required
|
|
- Enhanced security for public clients
|
|
|
|
**Flow 3: Client Credentials**
|
|
- App-only access (no user context)
|
|
- No user login required
|
|
- Limited to catalog endpoints (no user library, playlists, playback)
|
|
|
|
**Flow 4: Web Player (Undocumented)**
|
|
- Extract `sp_dc` cookie from browser
|
|
- Access private endpoints (lyrics)
|
|
- Violates terms of service
|
|
|
|
**Implementation:**
|
|
```python
|
|
from minim import spotify
|
|
|
|
# Authorization Code flow
|
|
api = spotify.WebAPI(
|
|
client_id="your_client_id",
|
|
client_secret="your_client_secret",
|
|
redirect_uri="http://localhost:8888"
|
|
)
|
|
api.set_flow("authorization_code", scopes=[
|
|
"user-library-read",
|
|
"playlist-read-private",
|
|
"user-read-playback-state"
|
|
])
|
|
api.set_access_token() # Opens browser
|
|
|
|
# Client Credentials flow (no user login)
|
|
api = spotify.WebAPI(client_id="...", client_secret="...")
|
|
api.set_flow("client_credentials")
|
|
api.set_access_token()
|
|
```
|
|
|
|
### API Coverage
|
|
|
|
**Catalog:**
|
|
- Search tracks, albums, artists, playlists, shows, episodes
|
|
- Get detailed information
|
|
- Get related artists
|
|
- Get artist top tracks
|
|
- Get album tracks
|
|
- Get audio features (danceability, energy, tempo, etc.)
|
|
- Get audio analysis (detailed beat/bar/section analysis)
|
|
|
|
**User Library:**
|
|
- Get saved tracks, albums, shows, episodes
|
|
- Save/remove items
|
|
- Check if items are saved
|
|
|
|
**Playlists:**
|
|
- Get user playlists
|
|
- Get playlist details and tracks
|
|
- Create, update, delete playlists
|
|
- Add/remove tracks
|
|
- Reorder tracks
|
|
- Upload custom cover image
|
|
|
|
**Playback:**
|
|
- Get current playback state
|
|
- Get available devices
|
|
- Start/pause/skip playback
|
|
- Seek to position
|
|
- Set volume
|
|
- Toggle shuffle/repeat
|
|
- Transfer playback between devices
|
|
|
|
**Personalization:**
|
|
- Get top artists and tracks
|
|
- Get recently played tracks
|
|
- Get recommendations based on seeds
|
|
|
|
**Follow:**
|
|
- Follow/unfollow artists, users, playlists
|
|
- Check if following
|
|
- Get followed artists
|
|
|
|
**Browse:**
|
|
- Get featured playlists
|
|
- Get new releases
|
|
- Get categories
|
|
- Get category playlists
|
|
|
|
**Lyrics (Private API):**
|
|
- Get synchronized lyrics via Musixmatch integration
|
|
- Requires `sp_dc` cookie
|
|
|
|
### Data Model
|
|
|
|
**Track Object:**
|
|
```json
|
|
{
|
|
"id": "3n3Ppam7vgaVa1iaRUc9Lp",
|
|
"name": "Creep",
|
|
"artists": [
|
|
{"name": "Radiohead", "id": "4Z8W4fKeB5YxbusRsdQVPb"}
|
|
],
|
|
"album": {
|
|
"name": "Pablo Honey",
|
|
"id": "6AZv3m27uyRxi8KyJSfUxL",
|
|
"release_date": "1993-02-22",
|
|
"images": [
|
|
{"url": "https://.../640x640.jpg", "width": 640, "height": 640}
|
|
]
|
|
},
|
|
"duration_ms": 238640,
|
|
"track_number": 2,
|
|
"disc_number": 1,
|
|
"explicit": false,
|
|
"external_ids": {"isrc": "GBAYE9200070"},
|
|
"popularity": 82,
|
|
"preview_url": "https://.../preview.mp3"
|
|
}
|
|
```
|
|
|
|
**Audio Features Object:**
|
|
```json
|
|
{
|
|
"id": "3n3Ppam7vgaVa1iaRUc9Lp",
|
|
"danceability": 0.456,
|
|
"energy": 0.789,
|
|
"key": 7,
|
|
"loudness": -6.234,
|
|
"mode": 1,
|
|
"speechiness": 0.034,
|
|
"acousticness": 0.123,
|
|
"instrumentalness": 0.000012,
|
|
"liveness": 0.089,
|
|
"valence": 0.234,
|
|
"tempo": 92.456,
|
|
"duration_ms": 238640,
|
|
"time_signature": 4
|
|
}
|
|
```
|
|
|
|
**Lyrics Object (Private API):**
|
|
```json
|
|
{
|
|
"lyrics": {
|
|
"syncType": "LINE_SYNCED",
|
|
"lines": [
|
|
{"startTimeMs": "0", "words": "When you were here before", "syllables": []},
|
|
{"startTimeMs": "5230", "words": "Couldn't look you in the eye", "syllables": []}
|
|
],
|
|
"language": "en"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Scopes
|
|
|
|
Spotify uses OAuth scopes to control API access. Common scopes:
|
|
|
|
**Library:**
|
|
- `user-library-read`: Read saved tracks/albums
|
|
- `user-library-modify`: Save/remove tracks/albums
|
|
|
|
**Playlists:**
|
|
- `playlist-read-private`: Read private playlists
|
|
- `playlist-read-collaborative`: Read collaborative playlists
|
|
- `playlist-modify-public`: Modify public playlists
|
|
- `playlist-modify-private`: Modify private playlists
|
|
|
|
**Playback:**
|
|
- `user-read-playback-state`: Read playback state
|
|
- `user-modify-playback-state`: Control playback
|
|
- `user-read-currently-playing`: Read currently playing track
|
|
|
|
**Personalization:**
|
|
- `user-top-read`: Read top artists and tracks
|
|
- `user-read-recently-played`: Read recently played tracks
|
|
|
|
**Follow:**
|
|
- `user-follow-read`: Read followed artists/users
|
|
- `user-follow-modify`: Follow/unfollow artists/users
|
|
|
|
**User:**
|
|
- `user-read-private`: Read user profile (country, product)
|
|
- `user-read-email`: Read user email
|
|
|
|
### Rate Limiting
|
|
|
|
**Limit:** Varies by endpoint, typically 180 requests per 30 seconds
|
|
**Enforcement:** HTTP 429 (Too Many Requests)
|
|
**Headers:**
|
|
- `Retry-After`: Seconds to wait before retrying
|
|
|
|
**Recommendation:** Implement exponential backoff and respect `Retry-After` header.
|
|
|
|
### Use Cases
|
|
|
|
1. **Comprehensive Metadata:** Get detailed track info, audio features, related artists
|
|
2. **Playlist Management:** Create, sync, and manage playlists
|
|
3. **Playback Control:** Build custom music players
|
|
4. **Music Discovery:** Get recommendations, browse new releases
|
|
5. **Lyrics Integration:** Display synchronized lyrics (via private API)
|
|
|
|
### Limitations
|
|
|
|
- No streaming URLs (only 30-second previews)
|
|
- No download capability
|
|
- Lyrics require private API (terms of service violation)
|
|
- Rate limiting varies by endpoint
|
|
- Some features require premium subscription
|
|
|
|
## TIDAL Integration
|
|
|
|
### Service Overview
|
|
|
|
**Purpose:** High-fidelity music streaming with MQA support
|
|
**Website:** https://www.tidal.com
|
|
**API Documentation:** Limited (mostly undocumented)
|
|
**API Type:** Public (limited) + Private (extensive)
|
|
|
|
### Authentication
|
|
|
|
**Method:** OAuth 2.0 (PKCE or Client Credentials)
|
|
|
|
**Public API:**
|
|
- Client credentials flow
|
|
- Limited endpoints (catalog search, basic info)
|
|
|
|
**Private API:**
|
|
- PKCE flow with user login
|
|
- Full access (streaming URLs, lyrics, credits)
|
|
|
|
**Implementation:**
|
|
```python
|
|
from minim import tidal
|
|
|
|
# Private API (full access)
|
|
api = tidal.PrivateAPI(
|
|
client_id="your_client_id",
|
|
client_secret="your_client_secret"
|
|
)
|
|
api.set_flow("pkce")
|
|
api.set_access_token() # Opens browser for login
|
|
```
|
|
|
|
**Client ID/Secret:**
|
|
- Extracted from TIDAL desktop/mobile apps
|
|
- Not officially provided by TIDAL
|
|
- Use at own risk (terms of service violation)
|
|
|
|
### API Coverage
|
|
|
|
**Catalog:**
|
|
- Search tracks, albums, artists, playlists, videos
|
|
- Get detailed information
|
|
- Get artist top tracks and albums
|
|
- Get similar artists
|
|
- Get album review and credits
|
|
|
|
**Streaming:**
|
|
- Get streaming URLs with quality selection
|
|
- Quality levels: LOW (96kbps AAC), HIGH (320kbps AAC), LOSSLESS (FLAC 16/44.1), HI_RES (FLAC 24/96+), HI_RES_LOSSLESS (MQA)
|
|
- Manifest decryption for protected streams
|
|
|
|
**Lyrics:**
|
|
- Get synchronized lyrics (LRC format)
|
|
- Get plain text lyrics
|
|
|
|
**Credits:**
|
|
- Get detailed credits (producers, engineers, musicians, composers, etc.)
|
|
- Role-based organization
|
|
|
|
**User Library:**
|
|
- Get user playlists
|
|
- Create, update, delete playlists
|
|
- Add/remove tracks
|
|
- Get favorites (tracks, albums, artists, videos)
|
|
- Add/remove favorites
|
|
|
|
**Not Available:**
|
|
- Playback control (no remote control API)
|
|
- Audio features/analysis
|
|
- Recommendations (limited)
|
|
|
|
### Data Model
|
|
|
|
**Track Object:**
|
|
```json
|
|
{
|
|
"id": 12345678,
|
|
"title": "Creep",
|
|
"duration": 238,
|
|
"trackNumber": 2,
|
|
"volumeNumber": 1,
|
|
"isrc": "GBAYE9200070",
|
|
"explicit": false,
|
|
"audioQuality": "HI_RES",
|
|
"artists": [
|
|
{"name": "Radiohead", "id": 4050}
|
|
],
|
|
"album": {
|
|
"id": 1234567,
|
|
"title": "Pablo Honey",
|
|
"releaseDate": "1993-02-22",
|
|
"cover": "01234567-89ab-cdef-0123-456789abcdef",
|
|
"upc": "0060254734729",
|
|
"numberOfTracks": 12,
|
|
"audioQuality": "HI_RES"
|
|
},
|
|
"streamStartDate": "1993-02-22T00:00:00.000Z"
|
|
}
|
|
```
|
|
|
|
**Streaming Manifest:**
|
|
```json
|
|
{
|
|
"mimeType": "audio/flac",
|
|
"codecs": "flac",
|
|
"encryptionType": "NONE",
|
|
"urls": ["https://streaming.tidal.com/..."],
|
|
"soundQuality": "HI_RES",
|
|
"bitDepth": 24,
|
|
"sampleRate": 96000
|
|
}
|
|
```
|
|
|
|
**Lyrics Object:**
|
|
```json
|
|
{
|
|
"trackId": 12345678,
|
|
"lyricsProvider": "Musixmatch",
|
|
"providerLyricsId": "12345",
|
|
"lyrics": "When you were here before\nCouldn't look you in the eye...",
|
|
"subtitles": "[00:00.00] When you were here before\n[00:05.23] Couldn't look you in the eye..."
|
|
}
|
|
```
|
|
|
|
**Credits Object:**
|
|
```json
|
|
{
|
|
"credits": [
|
|
{
|
|
"type": "Producers",
|
|
"contributors": [
|
|
{"name": "Sean Slade", "id": 12345},
|
|
{"name": "Paul Q. Kolderie", "id": 67890}
|
|
]
|
|
},
|
|
{
|
|
"type": "Performers",
|
|
"contributors": [
|
|
{"name": "Thom Yorke", "id": 111, "role": "Vocals"},
|
|
{"name": "Jonny Greenwood", "id": 222, "role": "Guitar"}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Quality Levels
|
|
|
|
| Quality | Codec | Bitrate/Depth | Subscription Tier |
|
|
|---------|-------|---------------|-------------------|
|
|
| LOW | AAC | 96 kbps | Free (trial) |
|
|
| HIGH | AAC | 320 kbps | HiFi |
|
|
| LOSSLESS | FLAC | 16-bit/44.1kHz | HiFi |
|
|
| HI_RES | FLAC | 24-bit/96kHz+ | HiFi Plus |
|
|
| HI_RES_LOSSLESS | MQA | 24-bit/96kHz+ (MQA) | HiFi Plus |
|
|
|
|
**MQA (Master Quality Authenticated):**
|
|
- Proprietary format by Meridian Audio
|
|
- Requires MQA-compatible DAC for full unfolding
|
|
- Software decoding provides 24-bit/96kHz
|
|
|
|
### Manifest Decryption
|
|
|
|
Some streaming URLs are encrypted. minim handles decryption:
|
|
|
|
```python
|
|
def _decrypt_manifest(self, manifest: dict) -> str:
|
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
from cryptography.hazmat.backends import default_backend
|
|
import base64
|
|
|
|
# Extract encrypted URL
|
|
encrypted_url = manifest["urls"][0]
|
|
|
|
# Decrypt using AES-128-CTR
|
|
key = base64.b64decode(manifest["encryptionKey"])
|
|
nonce = base64.b64decode(manifest["nonce"])
|
|
|
|
cipher = Cipher(
|
|
algorithms.AES(key),
|
|
modes.CTR(nonce),
|
|
backend=default_backend()
|
|
)
|
|
decryptor = cipher.decryptor()
|
|
|
|
decrypted = decryptor.update(base64.b64decode(encrypted_url)) + decryptor.finalize()
|
|
return decrypted.decode("utf-8")
|
|
```
|
|
|
|
### Rate Limiting
|
|
|
|
**Unknown.** Private API does not document rate limits.
|
|
|
|
**Observation:** Moderate usage (<50 requests/minute) appears safe.
|
|
|
|
**Recommendation:** Implement conservative rate limiting and exponential backoff on errors.
|
|
|
|
### Use Cases
|
|
|
|
1. **High-Fidelity Streaming:** Get FLAC files up to 24-bit/192kHz and MQA
|
|
2. **Comprehensive Credits:** Get detailed production credits
|
|
3. **Synchronized Lyrics:** Display time-synced lyrics
|
|
4. **Metadata Enrichment:** Get authoritative release dates, ISRCs, UPCs
|
|
5. **Playlist Management:** Sync playlists between services
|
|
|
|
### Limitations
|
|
|
|
- Private API (may break without notice)
|
|
- Requires active subscription (HiFi or HiFi Plus for lossless)
|
|
- Client ID/secret extraction violates terms of service
|
|
- No playback control API
|
|
- No audio features/analysis
|
|
- Geographic restrictions on content
|
|
|
|
## Integration Comparison
|
|
|
|
### Authentication Complexity
|
|
|
|
**Simplest:** iTunes (no auth)
|
|
**Simple:** Discogs (personal token), Spotify (client credentials)
|
|
**Moderate:** Spotify (authorization code), TIDAL (PKCE)
|
|
**Complex:** Qobuz (app_id extraction + password grant)
|
|
|
|
### API Documentation Quality
|
|
|
|
**Best:** Spotify (comprehensive, well-maintained)
|
|
**Good:** Discogs, iTunes
|
|
**Poor:** TIDAL (limited public docs)
|
|
**None:** Qobuz (fully reverse-engineered)
|
|
|
|
### Streaming Capability
|
|
|
|
**High-Resolution:** Qobuz (up to 24/192 FLAC), TIDAL (up to 24/192 FLAC + MQA)
|
|
**Lossless:** TIDAL (16/44.1 FLAC)
|
|
**Lossy:** TIDAL (AAC), Qobuz (MP3)
|
|
**Preview Only:** Spotify (30sec MP3), iTunes (30sec M4A)
|
|
**None:** Discogs
|
|
|
|
### Metadata Richness
|
|
|
|
**Credits:** TIDAL (excellent), Discogs (good), others (none)
|
|
**Lyrics:** TIDAL (synced), Spotify (synced, private API), others (none)
|
|
**Audio Features:** Spotify (excellent), others (none)
|
|
**Catalog Info:** All services (good)
|
|
|
|
### Terms of Service Compliance
|
|
|
|
**Compliant:** Discogs (public API), iTunes (public API), Spotify (public Web API)
|
|
**Questionable:** Spotify (private lyrics API)
|
|
**Violation:** Qobuz (app_id extraction, private API), TIDAL (client_id extraction, private API)
|
|
|
|
## Summary
|
|
|
|
minim provides comprehensive integration with five music services, each serving different use cases:
|
|
|
|
- **Discogs:** Best for credits, catalog numbers, and collection management
|
|
- **iTunes:** Best for quick, unauthenticated metadata lookup
|
|
- **Qobuz:** Best for high-resolution downloads (within subscription terms)
|
|
- **Spotify:** Best for comprehensive metadata, audio features, and playlist management
|
|
- **TIDAL:** Best for high-fidelity streaming, credits, and synchronized lyrics
|
|
|
|
All integrations follow consistent patterns (authentication, request handling, error raising) while exposing service-specific features. The private API usage (Qobuz, Spotify lyrics, TIDAL) provides powerful capabilities but carries legal and stability risks.
|
|
|
|
For a metadata aggregator project, prioritize public APIs (Spotify Web API, Discogs, iTunes) for production use, and use private APIs only for research or personal projects.
|