/
/
/
1"""Parsers for Radio Paradise provider."""
2
3from typing import Any
4
5from music_assistant_models.enums import ImageType
6from music_assistant_models.media_items import (
7 MediaItemImage,
8 ProviderMapping,
9 Radio,
10)
11from music_assistant_models.streamdetails import StreamMetadata
12
13from .constants import COVER_BASE_URL, RADIO_PARADISE_CHANNELS, STATION_ICONS_BASE_URL
14
15
16def parse_radio(channel_id: str, instance_id: str, provider_domain: str) -> Radio:
17 """
18 Create a Radio object from cached channel information.
19
20 :param channel_id: A known Radio Paradise channel id; callers must validate.
21 :param instance_id: The provider instance id.
22 :param provider_domain: The provider domain string.
23 """
24 channel_info = RADIO_PARADISE_CHANNELS[channel_id]
25
26 radio = Radio(
27 provider=instance_id,
28 item_id=channel_id,
29 name=channel_info["name"],
30 provider_mappings={
31 ProviderMapping(
32 provider_domain=provider_domain,
33 provider_instance=instance_id,
34 item_id=channel_id,
35 available=True,
36 )
37 },
38 )
39
40 icon_url = f"{STATION_ICONS_BASE_URL}/{channel_info['station_icon']}"
41 radio.metadata.add_image(
42 MediaItemImage(
43 provider=instance_id,
44 type=ImageType.THUMB,
45 path=icon_url,
46 remotely_accessible=True,
47 )
48 )
49
50 return radio
51
52
53def _build_upcoming_string(metadata: dict[str, Any], current_song: dict[str, Any]) -> str | None:
54 """
55 Build "Up Next: Artist - Track â Later: Artist2, Artist3" string.
56
57 :param metadata: Full metadata response with next song and block data.
58 :param current_song: Current track data to exclude from upcoming list.
59 """
60 next_song = metadata.get("next")
61 if not next_song:
62 return None
63
64 next_artist = next_song.get("artist", "")
65 next_title = next_song.get("title", "")
66 if not next_artist or not next_title:
67 return None
68
69 result = f"Up Next: {next_artist} - {next_title}"
70
71 # Get additional artists from block data for "Later" section
72 block_data = metadata.get("block_data")
73 if block_data and "song" in block_data:
74 current_event = current_song.get("event")
75 next_event = next_song.get("event")
76 current_elapsed = int(current_song.get("elapsed", 0))
77
78 # Collect unique artists that come after current and next song
79 seen_artists = {next_artist}
80 later_artists = []
81
82 sorted_keys = sorted(block_data["song"].keys(), key=int)
83 for song_key in sorted_keys:
84 song = block_data["song"][song_key]
85 song_event = song.get("event")
86
87 # Skip current and next song, only include songs after current
88 if (
89 song_event not in (current_event, next_event)
90 and int(song.get("elapsed", 0)) > current_elapsed
91 ):
92 artist_name = song.get("artist", "")
93 if artist_name and artist_name not in seen_artists:
94 seen_artists.add(artist_name)
95 later_artists.append(artist_name)
96 if len(later_artists) >= 3:
97 break
98
99 if later_artists:
100 result += f" â Later: {', '.join(later_artists)}"
101
102 return result
103
104
105def build_stream_metadata(
106 current_song: dict[str, Any],
107 metadata: dict[str, Any],
108 *,
109 show_upcoming: bool = False,
110) -> StreamMetadata:
111 """
112 Build StreamMetadata with current track info.
113
114 :param current_song: Current track data from Radio Paradise API.
115 :param metadata: Full metadata response with next song and block data.
116 :param show_upcoming: If True, show upcoming info in artist field.
117 """
118 # Extract track info
119 artist = current_song.get("artist", "Unknown Artist")
120 title = current_song.get("title", "Unknown Title")
121 album = current_song.get("album")
122 year = current_song.get("year")
123
124 # Build album string with year if available
125 album_display = album
126 if album and year:
127 album_display = f"{album} ({year})"
128 elif year:
129 album_display = str(year)
130
131 # Alternate artist field with upcoming info
132 artist_display = artist
133 if show_upcoming:
134 upcoming = _build_upcoming_string(metadata, current_song)
135 if upcoming:
136 artist_display = upcoming
137
138 # Play API returns a relative path (e.g. "covers/l/19806.jpg") while now_playing
139 # returns an absolute URL â normalise to a single absolute URL form.
140 cover = current_song.get("cover")
141 image_url: str | None = None
142 if cover:
143 image_url = cover if cover.startswith("http") else f"{COVER_BASE_URL}/{cover}"
144
145 # Get track duration (API returns milliseconds, convert to seconds)
146 duration = current_song.get("duration")
147 if duration:
148 duration = int(duration) // 1000
149
150 return StreamMetadata(
151 title=title,
152 artist=artist_display,
153 album=album_display,
154 image_url=image_url,
155 duration=duration,
156 )
157