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
This commit is contained in:
@@ -0,0 +1,922 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user