music-assistant-server

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