Files
musicseerr/backend/repositories/navidrome_models.py
T

151 lines
4.1 KiB
Python

from __future__ import annotations
import logging
from collections.abc import AsyncIterator
from typing import Any
import msgspec
from core.exceptions import NavidromeApiError, NavidromeAuthError, NavidromeSubsonicError
logger = logging.getLogger(__name__)
class SubsonicArtist(msgspec.Struct):
id: str
name: str
albumCount: int = 0
coverArt: str = ""
musicBrainzId: str = ""
class SubsonicSong(msgspec.Struct):
id: str
title: str
album: str = ""
albumId: str = ""
artist: str = ""
artistId: str = ""
track: int = 0
discNumber: int = 1
year: int = 0
duration: int = 0
bitRate: int = 0
suffix: str = ""
contentType: str = ""
musicBrainzId: str = ""
class SubsonicAlbum(msgspec.Struct):
id: str
name: str
artist: str = ""
artistId: str = ""
year: int = 0
genre: str = ""
songCount: int = 0
duration: int = 0
coverArt: str = ""
musicBrainzId: str = ""
song: list[SubsonicSong] | None = None
class SubsonicPlaylist(msgspec.Struct):
id: str
name: str
songCount: int = 0
duration: int = 0
entry: list[SubsonicSong] | None = None
class SubsonicGenre(msgspec.Struct):
name: str = ""
songCount: int = 0
albumCount: int = 0
class SubsonicSearchResult(msgspec.Struct):
artist: list[SubsonicArtist] = msgspec.field(default_factory=list)
album: list[SubsonicAlbum] = msgspec.field(default_factory=list)
song: list[SubsonicSong] = msgspec.field(default_factory=list)
class StreamProxyResult(msgspec.Struct):
status_code: int
headers: dict[str, str]
media_type: str
body_chunks: AsyncIterator[bytes] | None = None
def parse_subsonic_response(data: dict[str, Any]) -> dict[str, Any]:
resp = data.get("subsonic-response")
if resp is None:
raise NavidromeApiError("Missing subsonic-response envelope")
status = resp.get("status", "")
if status != "ok":
error = resp.get("error", {})
code = error.get("code", 0)
message = error.get("message", "Unknown Subsonic API error")
if code in (40, 41):
raise NavidromeAuthError(message, code=code)
raise NavidromeSubsonicError(message, code=code)
return resp
def parse_artist(data: dict[str, Any]) -> SubsonicArtist:
return SubsonicArtist(
id=data.get("id", ""),
name=data.get("name", "Unknown"),
albumCount=data.get("albumCount", 0),
coverArt=data.get("coverArt", ""),
musicBrainzId=data.get("musicBrainzId", ""),
)
def parse_song(data: dict[str, Any]) -> SubsonicSong:
return SubsonicSong(
id=data.get("id", ""),
title=data.get("title", "Unknown"),
album=data.get("album", ""),
albumId=data.get("albumId", ""),
artist=data.get("artist", ""),
artistId=data.get("artistId", ""),
track=data.get("track", 0),
discNumber=data.get("discNumber", 1),
year=data.get("year", 0),
duration=data.get("duration", 0),
bitRate=data.get("bitRate", 0),
suffix=data.get("suffix", ""),
contentType=data.get("contentType", ""),
musicBrainzId=data.get("musicBrainzId", ""),
)
def parse_album(data: dict[str, Any]) -> SubsonicAlbum:
songs: list[SubsonicSong] | None = None
raw_songs = data.get("song")
if raw_songs is not None:
songs = [parse_song(s) for s in raw_songs]
return SubsonicAlbum(
id=data.get("id", ""),
name=data.get("name", data.get("title", "Unknown")),
artist=data.get("artist", ""),
artistId=data.get("artistId", ""),
year=data.get("year", 0),
genre=data.get("genre", ""),
songCount=data.get("songCount", 0),
duration=data.get("duration", 0),
coverArt=data.get("coverArt", ""),
musicBrainzId=data.get("musicBrainzId", ""),
song=songs,
)
def parse_genre(data: dict[str, Any]) -> SubsonicGenre:
return SubsonicGenre(
name=data.get("value", data.get("name", "")),
songCount=data.get("songCount", 0),
albumCount=data.get("albumCount", 0),
)