/
/
/
1"""Test SiriusXM channel updates producing stream metadata with artwork."""
2
3from __future__ import annotations
4
5from datetime import UTC, datetime, timedelta
6from typing import Any
7from unittest.mock import MagicMock, patch
8
9from music_assistant_models.enums import ContentType, MediaType, StreamType
10from music_assistant_models.media_items import AudioFormat
11from music_assistant_models.streamdetails import StreamDetails
12from sxm.models import (
13 XMAlbum,
14 XMArt,
15 XMArtist,
16 XMChannel,
17 XMCut,
18 XMCutMarker,
19 XMImage,
20 XMLiveChannel,
21 XMSong,
22)
23
24from music_assistant.providers.siriusxm import SiriusXMProvider
25
26CHANNEL_ID = "9420"
27ALBUM_ART_URL = "https://example.com/album-art.jpg"
28CHANNEL_LOGO_URL = "https://example.com/channel-logo.png"
29
30
31def _make_channel() -> XMChannel:
32 return XMChannel(
33 guid="guid",
34 id=CHANNEL_ID,
35 name="Test Channel",
36 streaming_name="Test Channel",
37 sort_order=1,
38 short_description="short",
39 medium_description="medium",
40 url="https://example.com/channel",
41 is_available=True,
42 is_favorite=False,
43 is_mature=False,
44 channel_number=42,
45 images=[
46 XMImage(
47 name=None,
48 url="https://example.com/other.png",
49 art_type="IMAGE",
50 width=100,
51 height=100,
52 ),
53 XMImage(name=None, url=CHANNEL_LOGO_URL, art_type="IMAGE", width=300, height=300),
54 ],
55 categories=[],
56 )
57
58
59def _make_live_channel(cut: XMCut) -> XMLiveChannel:
60 now = datetime.now(UTC)
61 cut_marker = XMCutMarker(
62 guid="cut-guid",
63 time=now - timedelta(minutes=1),
64 time_seconds=int((now - timedelta(minutes=1)).timestamp()),
65 duration=timedelta(minutes=3),
66 cut=cut,
67 )
68 return XMLiveChannel(
69 id=CHANNEL_ID,
70 hls_infos=[],
71 custom_hls_infos=[],
72 episode_markers=[],
73 cut_markers=[cut_marker],
74 )
75
76
77def _make_provider() -> Any:
78 provider = MagicMock()
79 provider._current_stream_details = StreamDetails(
80 item_id=CHANNEL_ID,
81 provider="siriusxm",
82 audio_format=AudioFormat(content_type=ContentType.AAC),
83 stream_type=StreamType.HLS,
84 media_type=MediaType.RADIO,
85 path="dummy",
86 )
87 provider._channels_by_id = {CHANNEL_ID: _make_channel()}
88 return provider
89
90
91def _run_channel_updated(provider: Any, live_channel: XMLiveChannel) -> None:
92 with patch.object(XMLiveChannel, "from_dict", return_value=live_channel):
93 SiriusXMProvider._channel_updated(provider, {})
94
95
96def test_channel_update_sets_song_album_art() -> None:
97 """The current song's album art is used as stream metadata image."""
98 provider = _make_provider()
99 song = XMSong(
100 title="Test Song",
101 artists=[XMArtist(name="Test Artist")],
102 album=XMAlbum(
103 title="Test Album",
104 arts=[XMArt(name=None, url=ALBUM_ART_URL, art_type="IMAGE")],
105 ),
106 )
107 _run_channel_updated(provider, _make_live_channel(song))
108
109 metadata = provider._current_stream_details.stream_metadata
110 assert metadata is not None
111 assert metadata.title == "Test Song"
112 assert metadata.artist == "Test Artist"
113 assert metadata.image_url == ALBUM_ART_URL
114
115
116def test_channel_update_falls_back_to_channel_logo() -> None:
117 """Without album art the channel logo is used as stream metadata image."""
118 provider = _make_provider()
119 song = XMSong(title="Test Song", artists=[XMArtist(name="Test Artist")], album=None)
120 _run_channel_updated(provider, _make_live_channel(song))
121
122 metadata = provider._current_stream_details.stream_metadata
123 assert metadata is not None
124 assert metadata.image_url == CHANNEL_LOGO_URL
125
126
127def test_channel_update_non_song_cut_uses_channel_logo() -> None:
128 """A cut that is not a song has no album art, so the channel logo is used."""
129 provider = _make_provider()
130 cut = XMCut(title="Talk Segment", artists=[XMArtist(name="Test Host")])
131 _run_channel_updated(provider, _make_live_channel(cut))
132
133 metadata = provider._current_stream_details.stream_metadata
134 assert metadata is not None
135 assert metadata.title == "Talk Segment"
136 assert metadata.artist == "Test Host"
137 assert metadata.image_url == CHANNEL_LOGO_URL
138
139
140def test_channel_update_ignores_other_channel() -> None:
141 """An update for another channel does not touch the current stream metadata."""
142 provider = _make_provider()
143 song = XMSong(title="Test Song", artists=[XMArtist(name="Test Artist")], album=None)
144 live_channel = _make_live_channel(song)
145 live_channel.id = "other-channel"
146 _run_channel_updated(provider, live_channel)
147
148 assert provider._current_stream_details.stream_metadata is None
149