yandex-music-downloader/ymd/ym_api/models.py
2024-07-26 21:58:32 +07:00

174 lines
4.9 KiB
Python

import datetime as dt
from dataclasses import dataclass
from typing import Optional
TITLE_TEMPLATE = "{title} ({version})"
@dataclass
class CoverInfo:
cover_url_template: Optional[str]
def cover_url(self, resolution: int) -> Optional[str]:
if self.cover_url_template is None:
return
return self.cover_url_template.replace("%%", f"{resolution}x{resolution}")
@classmethod
def from_json(cls, data: dict) -> "CoverInfo":
og_image = data.get("ogImage")
if og_image is None or og_image == "":
return cls(None)
return cls(f"https://{og_image}")
@dataclass
class PlaylistId:
owner: str
kind: int
@dataclass
class BasicArtistInfo:
id: str
name: str
@classmethod
def from_json(cls, data: dict) -> "BasicArtistInfo":
return cls(id=data["id"], name=data["name"])
@dataclass
class FullArtistInfo(BasicArtistInfo):
albums: list["BasicAlbumInfo"]
cover_info: CoverInfo
@classmethod
def from_json(cls, data: dict) -> "FullArtistInfo":
base = BasicArtistInfo.from_json(data["artist"])
albums = map(BasicAlbumInfo.from_json, data.get("albums", []))
albums = [a for a in albums if a is not None]
cover_info = CoverInfo.from_json(data)
return cls(**base.__dict__, cover_info=cover_info, albums=albums)
@dataclass
class BasicAlbumInfo:
id: str
title: str
release_date: Optional[dt.datetime]
year: Optional[int]
artists: list[BasicArtistInfo]
meta_type: str
@classmethod
def from_json(cls, data: dict) -> Optional["BasicAlbumInfo"]:
artists = parse_artists(data["artists"])
title = parse_title(data)
if release_date := data.get("releaseDate"):
release_date = dt.datetime.fromisoformat(release_date)
return cls(
id=data["id"],
title=title,
year=data.get("year"),
meta_type=data["metaType"],
artists=artists,
release_date=release_date,
)
@dataclass
class BasicTrackInfo:
title: str
id: str
real_id: str
album: BasicAlbumInfo
number: int
disc_number: int
artists: list[BasicArtistInfo]
has_lyrics: bool
cover_info: CoverInfo
@classmethod
def from_json(cls, data: dict) -> Optional["BasicTrackInfo"]:
if not data.get("available", False):
return None
track_id = str(data["id"])
title = parse_title(data)
albums_data = data["albums"]
artists = parse_artists(data["artists"])
track_position = {"index": 1, "volume": 1}
if len(albums_data):
album_data = albums_data[0]
album = BasicAlbumInfo.from_json(album_data)
track_position = album_data.get("trackPosition", track_position)
else:
album = BasicAlbumInfo(
id=track_id,
title=title,
release_date=None,
year=None,
meta_type="music",
artists=artists,
)
if album is None:
raise ValueError
cover_info = CoverInfo.from_json(data)
has_lyrics = data.get("lyricsInfo", {}).get("hasAvailableTextLyrics", False)
return cls(
title=title,
id=track_id,
real_id=data["realId"],
number=track_position["index"],
disc_number=track_position["volume"],
artists=artists,
album=album,
has_lyrics=has_lyrics,
cover_info=cover_info,
)
@dataclass
class FullTrackInfo(BasicTrackInfo):
lyrics: str
@classmethod
def from_json(cls, data: dict) -> Optional["FullTrackInfo"]:
base = BasicTrackInfo.from_json(data["track"])
if base is None:
return None
lyrics = data["lyric"][0]["fullLyrics"]
return cls(**base.__dict__, lyrics=lyrics)
@dataclass
class FullAlbumInfo(BasicAlbumInfo):
tracks: list[BasicTrackInfo]
@classmethod
def from_json(cls, data: dict) -> "FullAlbumInfo":
base = BasicAlbumInfo.from_json(data)
tracks = data.get("volumes", [])
tracks = [t for v in tracks for t in v]
tracks = map(BasicTrackInfo.from_json, tracks)
tracks = [t for t in tracks if t is not None]
return cls(**base.__dict__, tracks=tracks)
def parse_artists(data: list) -> list[BasicArtistInfo]:
artists = []
for artist in data:
artists.append(artist)
if decomposed := artist.get("decomposed"):
for d_artist in decomposed:
if isinstance(d_artist, dict):
artists.append(d_artist)
return [BasicArtistInfo.from_json(a) for a in artists]
def parse_title(data: dict) -> str:
title = data["title"]
if version := data.get("version"):
title = TITLE_TEMPLATE.format(title=title, version=version)
return title