Files
deezspot-spotizerr-dev/docs/librespot_client.md
2025-08-28 06:52:04 -06:00

6.7 KiB

LibrespotClient wrapper

A thin, high-level wrapper over the internal librespot API that returns Web API-like dictionaries for albums, tracks, artists, and playlists. Use this to standardize access to Spotify metadata throughout the codebase.

  • Import path: from deezspot.libutils import LibrespotClient
  • Backed by: librespot internal API (Session + Metadata/Playlist protos)
  • Thread-safe for read operations; uses an internal in-memory cache for track object expansions

Initialization

from deezspot.libutils import LibrespotClient

# 1) Create with stored credentials file (recommended)
client = LibrespotClient(stored_credentials_path="/absolute/path/to/credentials.json")

# 2) Or reuse an existing librespot Session
# from librespot.core import Session
# session = ...
# client = LibrespotClient(session=session)
  • stored_credentials_path: path to JSON created by librespot credential flow
  • session: optional existing librespot.core.Session (if provided, stored_credentials_path is not required)
  • max_workers: optional concurrency cap for track expansion (default 16, bounded [1, 32])

Always dispose when done:

client.close()

ID/URI inputs

All data-fetching methods accept either:

  • A Spotify URI (e.g., spotify:album:..., spotify:track:..., etc)
  • A base62 ID (e.g., 3KuXEGcqLcnEYWnn3OEGy0)
  • A public open.spotify.com/... URL (album/track/artist/playlist)
  • Or the corresponding librespot.metadata.*Id class

You can also use the helper if needed:

# kind in {"track", "album", "artist", "playlist"}
track_id = LibrespotClient.parse_input_id("track", "https://open.spotify.com/track/...")

Public API

get_album(album, include_tracks=False) -> dict

Fetches album metadata.

  • album: URI/base62/URL or AlbumId
  • include_tracks: when True, expands the album's tracks to full track objects using concurrent fetches; when False, returns tracks as an array of track base62 IDs

Return shape (subset):

{
  "album_type": "album",
  "total_tracks": 10,
  "available_markets": ["US", "GB"],
  "external_urls": {"spotify": "https://open.spotify.com/album/{id}"},
  "id": "{base62}",
  "images": [{"url": "https://...", "width": 640, "height": 640}],
  "name": "...",
  "release_date": "2020-05-01",
  "release_date_precision": "day",
  "type": "album",
  "uri": "spotify:album:{base62}",
  "artists": [{"id": "...", "name": "...", "type": "artist", "uri": "..."}],
  "tracks": [
    // include_tracks=False -> ["trackBase62", ...]
    // include_tracks=True  -> [{ track object }, ...]
  ],
  "copyrights": [{"text": "...", "type": "..."}],
  "external_ids": {"upc": "..."},
  "label": "...",
  "popularity": 57
}

Usage:

album = client.get_album("spotify:album:...", include_tracks=True)

get_track(track) -> dict

Fetches track metadata.

  • track: URI/base62/URL or TrackId

Return shape (subset):

{
  "album": { /* embedded album (no tracks) */ },
  "artists": [{"id": "...", "name": "..."}],
  "available_markets": ["US", "GB"],
  "disc_number": 1,
  "duration_ms": 221000,
  "explicit": false,
  "external_ids": {"isrc": "..."},
  "external_urls": {"spotify": "https://open.spotify.com/track/{id}"},
  "id": "{base62}",
  "name": "...",
  "popularity": 65,
  "track_number": 1,
  "type": "track",
  "uri": "spotify:track:{base62}",
  "preview_url": "https://p.scdn.co/mp3-preview/{hex}",
  "has_lyrics": true,
  "earliest_live_timestamp": 0,
  "licensor_uuid": "{hex}" // when available
}

Usage:

track = client.get_track("3KuXEGcqLcnEYWnn3OEGy0")

get_artist(artist) -> dict

Fetches artist metadata and returns a full JSON-like mapping of the protobuf (pruned of empty fields).

  • artist: URI/base62/URL or ArtistId

Usage:

artist = client.get_artist("https://open.spotify.com/artist/...")

get_playlist(playlist, expand_items=False) -> dict

Fetches playlist contents.

  • playlist: URI/URL/ID or PlaylistId (Spotify uses non-base62 playlist IDs)
  • expand_items: when True, playlist items containing tracks are expanded to full track objects (concurrent fetch with caching); otherwise, items contain minimal track stubs with id, uri, type, and external_urls

Return shape (subset):

{
  "name": "My Playlist",
  "description": "...",
  "collaborative": false,
  "images": [{"url": "https://..."}],
  "owner": {
    "id": "username",
    "type": "user",
    "uri": "spotify:user:username",
    "external_urls": {"spotify": "https://open.spotify.com/user/username"},
    "display_name": "username"
  },
  "snapshot_id": "base64Revision==",
  "tracks": {
    "offset": 0,
    "total": 42,
    "items": [
      {
        "added_at": "2023-01-01T12:34:56Z",
        "added_by": {"id": "...", "type": "user", "uri": "...", "external_urls": {"spotify": "..."}, "display_name": "..."},
        "is_local": false,
        "track": {
          // expand_items=False -> {"id": "...", "uri": "spotify:track:...", "type": "track", "external_urls": {"spotify": "..."}}
          // expand_items=True  -> full track object
        },
        "item_id": "{hex}" // additional reference, not a Web API field
      }
    ]
  },
  "type": "playlist"
}

Usage:

playlist = client.get_playlist("spotify:playlist:...")
playlist_expanded = client.get_playlist("spotify:playlist:...", expand_items=True)

Concurrency and caching

  • When expanding tracks for albums/playlists, the client concurrently fetches missing track objects using a ThreadPoolExecutor with up to max_workers threads (default 16).
  • A per-instance in-memory cache stores fetched track objects keyed by base62 ID to avoid duplicate network calls in the same process.

Error handling

  • Underlying network/protobuf errors are not swallowed; wrap your calls if you need custom handling.
  • Empty/missing fields are pruned from output structures where appropriate.

Example:

try:
    data = client.get_album("spotify:album:...")
except Exception as exc:
    # handle failure (retry/backoff/logging)
    raise

Migration guide (from direct librespot usage)

Before (direct protobuf access):

album_id = AlbumId.from_base62(base62)
proto = session.api().get_metadata_4_album(album_id)
# manual traversal over `proto`...

After (wrapper):

from deezspot.libutils import LibrespotClient

client = LibrespotClient(stored_credentials_path="/path/to/credentials.json")
try:
    album = client.get_album(base62, include_tracks=True)
finally:
    client.close()

Notes

  • Image URLs are derived from internal file_id bytes using the public Spotify image host.
  • Playlist IDs are not base62; pass the raw ID, URI, or URL.
  • For performance-critical paths, reuse a single LibrespotClient instance (and its cache) per worker.