/
/
/
1"""Parsers for Zvuk Music API responses."""
2
3from __future__ import annotations
4
5from contextlib import suppress
6from datetime import datetime
7from typing import TYPE_CHECKING
8
9from music_assistant_models.enums import (
10 AlbumType,
11 ContentType,
12 ImageType,
13)
14from music_assistant_models.media_items import (
15 Album,
16 Artist,
17 AudioFormat,
18 MediaItemImage,
19 Playlist,
20 ProviderMapping,
21 Track,
22 UniqueList,
23)
24
25from music_assistant.helpers.util import parse_title_and_version
26
27from .constants import IMAGE_SIZE_LARGE, ZVUK_BASE_URL
28
29if TYPE_CHECKING:
30 from zvuk_music import Artist as ZvukArtist
31 from zvuk_music import Playlist as ZvukPlaylist
32 from zvuk_music import Release as ZvukRelease
33 from zvuk_music import Track as ZvukTrack
34 from zvuk_music.models.artist import SimpleArtist as ZvukSimpleArtist
35 from zvuk_music.models.common import Image as ZvukImage
36 from zvuk_music.models.playlist import SimplePlaylist as ZvukSimplePlaylist
37 from zvuk_music.models.release import SimpleRelease as ZvukSimpleRelease
38 from zvuk_music.models.track import SimpleTrack as ZvukSimpleTrack
39
40 from .provider import ZvukMusicProvider
41
42
43def _get_image_url(image: ZvukImage | None, size: int = IMAGE_SIZE_LARGE) -> str | None:
44 """Convert Zvuk Image to full URL.
45
46 :param image: Zvuk Image object.
47 :param size: Image size in pixels.
48 :return: Full image URL or None.
49 """
50 if not image:
51 return None
52 url = image.get_url(size, size)
53 return url if url else None
54
55
56def parse_artist(provider: ZvukMusicProvider, artist_obj: ZvukArtist | ZvukSimpleArtist) -> Artist:
57 """Parse Zvuk artist object to MA Artist model.
58
59 :param provider: The Zvuk Music provider instance.
60 :param artist_obj: Zvuk artist or SimpleArtist object.
61 :return: Music Assistant Artist model.
62 """
63 artist_id = str(artist_obj.id)
64 artist = Artist(
65 item_id=artist_id,
66 provider=provider.instance_id,
67 name=artist_obj.title or "Unknown Artist",
68 provider_mappings={
69 ProviderMapping(
70 item_id=artist_id,
71 provider_domain=provider.domain,
72 provider_instance=provider.instance_id,
73 url=f"{ZVUK_BASE_URL}/artist/{artist_id}",
74 )
75 },
76 )
77
78 if artist_obj.image:
79 image_url = _get_image_url(artist_obj.image)
80 if image_url:
81 artist.metadata.images = UniqueList(
82 [
83 MediaItemImage(
84 type=ImageType.THUMB,
85 path=image_url,
86 provider=provider.instance_id,
87 remotely_accessible=True,
88 )
89 ]
90 )
91
92 return artist
93
94
95def parse_album(provider: ZvukMusicProvider, release_obj: ZvukRelease | ZvukSimpleRelease) -> Album:
96 """Parse Zvuk release object to MA Album model.
97
98 :param provider: The Zvuk Music provider instance.
99 :param release_obj: Zvuk release or SimpleRelease object.
100 :return: Music Assistant Album model.
101 """
102 name, version = parse_title_and_version(
103 release_obj.title or "Unknown Album",
104 )
105 album_id = str(release_obj.id)
106
107 album = Album(
108 item_id=album_id,
109 provider=provider.instance_id,
110 name=name,
111 version=version,
112 provider_mappings={
113 ProviderMapping(
114 item_id=album_id,
115 provider_domain=provider.domain,
116 provider_instance=provider.instance_id,
117 audio_format=AudioFormat(
118 content_type=ContentType.UNKNOWN,
119 ),
120 url=f"{ZVUK_BASE_URL}/release/{album_id}",
121 )
122 },
123 )
124
125 # Parse artists
126 if release_obj.artists:
127 for artist in release_obj.artists:
128 album.artists.append(parse_artist(provider, artist))
129
130 # Determine album type from ReleaseType
131 if release_obj.type:
132 release_type_value = (
133 release_obj.type.value if hasattr(release_obj.type, "value") else str(release_obj.type)
134 )
135 if release_type_value == "compilation":
136 album.album_type = AlbumType.COMPILATION
137 elif release_type_value == "single":
138 album.album_type = AlbumType.SINGLE
139 elif release_type_value == "ep":
140 album.album_type = AlbumType.EP
141 else:
142 album.album_type = AlbumType.ALBUM
143 else:
144 album.album_type = AlbumType.ALBUM
145
146 # Parse date
147 if release_obj.date:
148 # get_year() is available on both Release and SimpleRelease
149 year = release_obj.get_year()
150 if year:
151 album.year = year
152 with suppress(ValueError):
153 album.metadata.release_date = datetime.fromisoformat(release_obj.date)
154
155 # Parse genres (only available on full Release, not SimpleRelease)
156 if hasattr(release_obj, "genres") and release_obj.genres:
157 album.metadata.genres = {genre.name for genre in release_obj.genres if genre.name}
158
159 # Parse explicit flag
160 if release_obj.explicit:
161 album.metadata.explicit = True
162
163 # Add cover image
164 if release_obj.image:
165 image_url = _get_image_url(release_obj.image)
166 if image_url:
167 album.metadata.images = UniqueList(
168 [
169 MediaItemImage(
170 type=ImageType.THUMB,
171 path=image_url,
172 provider=provider.instance_id,
173 remotely_accessible=True,
174 )
175 ]
176 )
177
178 return album
179
180
181def parse_track(provider: ZvukMusicProvider, track_obj: ZvukTrack | ZvukSimpleTrack) -> Track:
182 """Parse Zvuk track object to MA Track model.
183
184 :param provider: The Zvuk Music provider instance.
185 :param track_obj: Zvuk track or SimpleTrack object.
186 :return: Music Assistant Track model.
187 """
188 name, version = parse_title_and_version(
189 track_obj.title or "Unknown Track",
190 )
191 track_id = str(track_obj.id)
192
193 # Duration is already in seconds in Zvuk API
194 duration = track_obj.duration or 0
195
196 track = Track(
197 item_id=track_id,
198 provider=provider.instance_id,
199 name=name,
200 version=version,
201 duration=duration,
202 provider_mappings={
203 ProviderMapping(
204 item_id=track_id,
205 provider_domain=provider.domain,
206 provider_instance=provider.instance_id,
207 audio_format=AudioFormat(
208 content_type=ContentType.UNKNOWN,
209 ),
210 url=f"{ZVUK_BASE_URL}/track/{track_id}",
211 )
212 },
213 )
214
215 # Parse artists
216 if track_obj.artists:
217 track.artists = UniqueList()
218 for artist in track_obj.artists:
219 track.artists.append(parse_artist(provider, artist))
220
221 # Parse album from release (available on both Track and SimpleTrack)
222 if track_obj.release:
223 track.album = provider.get_item_mapping(
224 media_type="album",
225 key=str(track_obj.release.id),
226 name=track_obj.release.title or "Unknown Album",
227 )
228 # Get image from release
229 if track_obj.release.image:
230 image_url = _get_image_url(track_obj.release.image)
231 if image_url:
232 track.metadata.images = UniqueList(
233 [
234 MediaItemImage(
235 type=ImageType.THUMB,
236 path=image_url,
237 provider=provider.instance_id,
238 remotely_accessible=True,
239 )
240 ]
241 )
242
243 # Track number (position in release, only on full Track)
244 if hasattr(track_obj, "position") and track_obj.position is not None:
245 track.track_number = track_obj.position
246
247 # Explicit flag (boolean on both Track and SimpleTrack)
248 if track_obj.explicit:
249 track.metadata.explicit = True
250
251 return track
252
253
254def parse_playlist(
255 provider: ZvukMusicProvider, playlist_obj: ZvukPlaylist | ZvukSimplePlaylist
256) -> Playlist:
257 """Parse Zvuk playlist object to MA Playlist model.
258
259 :param provider: The Zvuk Music provider instance.
260 :param playlist_obj: Zvuk playlist or SimplePlaylist object.
261 :return: Music Assistant Playlist model.
262 """
263 playlist_id = str(playlist_obj.id)
264
265 # Determine if editable (user owns the playlist)
266 # user_id is only available on full Playlist, not SimplePlaylist
267 is_editable = False
268 owner_name = "Zvuk Music"
269 user_id = getattr(playlist_obj, "user_id", None)
270 if user_id and provider.client.user_id:
271 is_editable = str(user_id) == str(provider.client.user_id)
272 if is_editable:
273 owner_name = "Me"
274
275 playlist = Playlist(
276 item_id=playlist_id,
277 provider=provider.instance_id,
278 name=playlist_obj.title or "Unknown Playlist",
279 owner=owner_name,
280 provider_mappings={
281 ProviderMapping(
282 item_id=playlist_id,
283 provider_domain=provider.domain,
284 provider_instance=provider.instance_id,
285 url=f"{ZVUK_BASE_URL}/playlist/{playlist_id}",
286 is_unique=is_editable,
287 )
288 },
289 is_editable=is_editable,
290 )
291
292 # Metadata
293 if playlist_obj.description:
294 playlist.metadata.description = playlist_obj.description
295
296 # Add cover image
297 if playlist_obj.image:
298 image_url = _get_image_url(playlist_obj.image)
299 if image_url:
300 playlist.metadata.images = UniqueList(
301 [
302 MediaItemImage(
303 type=ImageType.THUMB,
304 path=image_url,
305 provider=provider.instance_id,
306 remotely_accessible=True,
307 )
308 ]
309 )
310
311 return playlist
312