music-assistant-server

10.3 KBPY
converters.py
10.3 KB302 lines • python
1"""Converters for Bandcamp API models to Music Assistant models."""
2
3from datetime import datetime
4
5from bandcamp_async_api.models import BCAlbum as APIAlbum
6from bandcamp_async_api.models import BCArtist as APIArtist
7from bandcamp_async_api.models import BCTrack as APITrack
8from bandcamp_async_api.models import (
9    SearchResultAlbum,
10    SearchResultArtist,
11    SearchResultTrack,
12)
13from music_assistant_models.enums import ImageType, LinkType, MediaType
14from music_assistant_models.media_items import Album as MAAlbum
15from music_assistant_models.media_items import Artist as MAArtist
16from music_assistant_models.media_items import (
17    ItemMapping,
18    MediaItemImage,
19    MediaItemLink,
20    ProviderMapping,
21    UniqueList,
22)
23from music_assistant_models.media_items import Track as MATrack
24
25
26class BandcampConverters:
27    """Converters for Bandcamp API models to Music Assistant models."""
28
29    def __init__(self, domain: str, instance_id: str):
30        """Initialize converters with provider information."""
31        self.domain = domain
32        self.instance_id = instance_id
33
34    def streaming_url_from_api(
35        self, streaming_info: dict[str, str]
36    ) -> tuple[str | None, int | None]:
37        """Parse streaming URL info."""
38        # Extract streaming URL with priority: mp3-v0 > mp3-128
39        bitrate = None
40        streaming_url = None
41        if "mp3-v0" in streaming_info:
42            streaming_url = streaming_info["mp3-v0"]
43            bitrate = None  # VBR
44        elif "mp3-320" in streaming_info:
45            streaming_url = streaming_info["mp3-320"]
46            bitrate = 320
47        elif "mp3-128" in streaming_info:
48            streaming_url = streaming_info["mp3-128"]
49            bitrate = 128
50        elif streaming_info:
51            # Fallback to first available URL
52            streaming_url = next(iter(streaming_info.values()))
53        return streaming_url, bitrate
54
55    def track_from_search(self, item: SearchResultTrack) -> MATrack:
56        """Create a Track from new API SearchResultTrack."""
57        track_id = f"{item.artist_id}-{item.album_id or 0}-{item.id}"
58        return MATrack(
59            item_id=track_id,
60            provider=self.instance_id,
61            name=item.name,
62            artists=UniqueList(
63                [
64                    ItemMapping(
65                        media_type=MediaType.ARTIST,
66                        item_id=str(item.artist_id),
67                        provider=self.instance_id,
68                        name=item.artist_name,
69                    )
70                ]
71            ),
72            album=(
73                ItemMapping(
74                    media_type=MediaType.ALBUM,
75                    item_id=f"{item.artist_id}-{item.album_id or 0}",
76                    provider=self.instance_id,
77                    name=item.album_name,
78                )
79                if item.album_id
80                else None
81            ),
82            provider_mappings={
83                ProviderMapping(
84                    item_id=track_id,
85                    provider_domain=self.domain,
86                    provider_instance=self.instance_id,
87                    url=item.url,
88                )
89            },
90        )
91
92    def album_from_search(self, item: SearchResultAlbum) -> MAAlbum:
93        """Create an Album from new API SearchResultAlbum."""
94        album_id = f"{item.artist_id}-{item.id}"
95        output = MAAlbum(
96            item_id=album_id,
97            provider=self.instance_id,
98            name=item.name,
99            uri=item.url,
100            artists=UniqueList(
101                [
102                    ItemMapping(
103                        media_type=MediaType.ARTIST,
104                        item_id=str(item.artist_id),
105                        provider=self.instance_id,
106                        name=item.artist_name,
107                        uri=item.artist_url,
108                    )
109                ]
110            ),
111            provider_mappings={
112                ProviderMapping(
113                    item_id=album_id,
114                    provider_domain=self.domain,
115                    provider_instance=self.instance_id,
116                    url=item.url,
117                )
118            },
119        )
120        output.metadata.add_image(
121            MediaItemImage(
122                type=ImageType.LANDSCAPE,
123                path=item.image_url,
124                provider=self.instance_id,
125                remotely_accessible=True,
126            )
127        )
128        return output
129
130    def artist_from_search(self, item: SearchResultArtist) -> MAArtist:
131        """Create an Artist from new API SearchResultArtist."""
132        output = MAArtist(
133            item_id=str(item.id),
134            provider=self.instance_id,
135            name=item.name,
136            uri=item.url,
137            provider_mappings={
138                ProviderMapping(
139                    item_id=str(item.id),
140                    provider_domain=self.domain,
141                    provider_instance=self.instance_id,
142                    url=item.url,
143                )
144            },
145        )
146        output.metadata.genres = item.tags
147        if item.url:
148            output.metadata.description = item.url
149        output.metadata.add_image(
150            MediaItemImage(
151                type=ImageType.LANDSCAPE,
152                path=item.image_url,
153                provider=self.instance_id,
154                remotely_accessible=True,
155            )
156        )
157        return output
158
159    def track_from_api(
160        self,
161        track: APITrack,
162        album_id: str | int | None = None,
163        album_name: str = "",
164        album_image_url: str = "",
165    ) -> MATrack:
166        """Convert a Track object from the API to MA Track format."""
167        album_id = album_id or 0
168        output = MATrack(
169            item_id=f"{track.artist.id}-{album_id}-{track.id}",
170            provider=self.instance_id,
171            name=track.title,
172            artists=UniqueList(
173                [
174                    ItemMapping(
175                        media_type=MediaType.ARTIST,
176                        item_id=str(track.artist.id),
177                        provider=self.instance_id,
178                        name=track.artist.name,
179                    )
180                ]
181            ),
182            disc_number=0,
183            duration=track.duration,
184            provider_mappings={
185                ProviderMapping(
186                    item_id=f"{track.artist.id}-{album_id}-{track.id}",
187                    provider_domain=self.domain,
188                    provider_instance=self.instance_id,
189                    url=track.url,
190                )
191            },
192        )
193        if output.track_number is not None:
194            output.track_number = track.track_number
195
196        if album_id:
197            output.album = ItemMapping(
198                media_type=MediaType.ALBUM,
199                item_id=f"{track.artist.id}-{album_id}",
200                provider=self.instance_id,
201                name=album_name,
202            )
203        elif hasattr(track, "album") and track.album:
204            # If the track has an album attribute, use that information
205            output.album = ItemMapping(
206                media_type=MediaType.ALBUM,
207                item_id=f"{track.artist.id}-{track.album.id}",
208                provider=self.instance_id,
209                name=track.album.title,
210            )
211
212        streaming_url, _ = self.streaming_url_from_api(track.streaming_url)
213        if streaming_url:
214            output.metadata.links = {
215                MediaItemLink(
216                    type=LinkType.UNKNOWN,
217                    url=streaming_url,
218                )
219            }
220        output.metadata.lyrics = track.lyrics
221        if album_image_url:
222            output.metadata.add_image(
223                MediaItemImage(
224                    type=ImageType.LANDSCAPE,
225                    path=album_image_url,
226                    provider=self.instance_id,
227                    remotely_accessible=True,
228                )
229            )
230        return output
231
232    def artist_from_api(self, artist: APIArtist) -> MAArtist:
233        """Convert an API Artist object to MA Artist format."""
234        output = MAArtist(
235            item_id=str(artist.id),
236            uri=artist.url,
237            provider=self.instance_id,
238            name=artist.name,
239            provider_mappings={
240                ProviderMapping(
241                    item_id=str(artist.id),
242                    provider_domain=self.domain,
243                    provider_instance=self.instance_id,
244                    url=artist.url,
245                )
246            },
247        )
248        output.metadata.description = f"{artist.url}\n{artist.bio or ''}".strip()
249        output.metadata.add_image(
250            MediaItemImage(
251                type=ImageType.LANDSCAPE,
252                path=artist.image_url,
253                provider=self.instance_id,
254                remotely_accessible=True,
255            )
256        )
257        return output
258
259    def album_from_api(self, album: APIAlbum) -> MAAlbum:
260        """Convert an API Album object to MA Album format."""
261        album_id = f"{album.artist.id}-{album.id}"
262        output = MAAlbum(
263            item_id=album_id,
264            provider=self.instance_id,
265            name=album.title,
266            artists=UniqueList(
267                [
268                    ItemMapping(
269                        media_type=MediaType.ARTIST,
270                        item_id=str(album.artist.id),
271                        provider=self.instance_id,
272                        name=album.artist.name,
273                        image=MediaItemImage(
274                            path=album.art_url,
275                            type=ImageType.LANDSCAPE,
276                            provider=self.instance_id,
277                            remotely_accessible=True,
278                        ),
279                    )
280                ]
281            ),
282            provider_mappings={
283                ProviderMapping(
284                    item_id=album_id,
285                    provider_domain=self.domain,
286                    provider_instance=self.instance_id,
287                    url=album.url,
288                )
289            },
290            year=datetime.fromtimestamp(album.release_date).year,
291        )
292        output.metadata.add_image(
293            MediaItemImage(
294                type=ImageType.LANDSCAPE,
295                path=album.art_url,
296                provider=self.instance_id,
297                remotely_accessible=True,
298            )
299        )
300        output.metadata.description = f"{album.url}\n{album.about or ''}".strip()
301        return output
302