From ce6564096845a81845d16a85bde453c25c9de187 Mon Sep 17 00:00:00 2001 From: Alexander Dietrich Date: Fri, 15 Nov 2024 20:54:25 +0100 Subject: [PATCH] [script.radioparadise] 2.1.2 --- script.radioparadise/CHANGELOG.md | 5 ++ script.radioparadise/addon.xml | 2 +- .../resources/lib/radioparadise.py | 68 +++++++++++------ script.radioparadise/resources/lib/service.py | 73 ++++++++++--------- script.radioparadise/resources/settings.xml | 1 + 5 files changed, 89 insertions(+), 60 deletions(-) diff --git a/script.radioparadise/CHANGELOG.md b/script.radioparadise/CHANGELOG.md index dc70373d6..e7cfbe9b7 100644 --- a/script.radioparadise/CHANGELOG.md +++ b/script.radioparadise/CHANGELOG.md @@ -1,3 +1,8 @@ +## v2.1.2 + +- Add Serenity to Auto Play options +- Fix metadata handling + ## v2.1.1 - Improve metadata handling diff --git a/script.radioparadise/addon.xml b/script.radioparadise/addon.xml index 393541793..96032e328 100644 --- a/script.radioparadise/addon.xml +++ b/script.radioparadise/addon.xml @@ -1,5 +1,5 @@ - + diff --git a/script.radioparadise/resources/lib/radioparadise.py b/script.radioparadise/resources/lib/radioparadise.py index f84c2bb68..945e6df43 100644 --- a/script.radioparadise/resources/lib/radioparadise.py +++ b/script.radioparadise/resources/lib/radioparadise.py @@ -1,4 +1,3 @@ -from collections import OrderedDict import json from pathlib import Path import re @@ -7,35 +6,42 @@ import requests import xbmcaddon +from .logger import Logger + NOWPLAYING_URL = 'https://api.radioparadise.com/api/nowplaying_list_v2022?chan={}&list_num=10' COVER_URL = 'https://img.radioparadise.com/{}' SLIDESHOW_URL = 'https://img.radioparadise.com/slideshow/720/{}.jpg' -BREAK_COVER_URL = 'https://img.radioparadise.com/covers/l/101.jpg' -BREAK_SONG = ('Commercial-free', 'Listener-supported') +# Metadata for the "station break", which does not appear in the API +BREAK_SONG = None +# Song key for the "station break" +BREAK_KEY = None +# Characters to allow in song keys KEY_FILTER_RE = re.compile(r'[^\w\']+') -# Number of songs to cache -MAX_SONGS = 30 # Number of seconds to wait for API responses UPDATE_TIMEOUT = 3 # Number of seconds to wait before retrying API updates UPDATE_WAIT = 5 +# Maximum number of seconds to wait between API updates +MAX_UPDATE_WAIT = 300 # List of channel objects from channels.json CHANNELS = None # Map of stream URL to channel object CHANNEL_INFO = None +LOG = Logger('rp_api') + class NowPlaying(): """Provides song information from the "nowplaying" API.""" def __init__(self): """Constructor""" - self.songs = OrderedDict() + self.songs = dict() self.set_channel(None) def get_song_data(self, song_key): @@ -43,7 +49,10 @@ def get_song_data(self, song_key): The "cover" value will be an absolute URL. """ - return self.songs.get(song_key) + if song_key != BREAK_KEY: + return self.songs.get(song_key) + else: + return BREAK_SONG def get_next_song(self, song_key): """Return a dict for song_key's successor, or None. @@ -59,7 +68,6 @@ def set_channel(self, channel_id): self.url = NOWPLAYING_URL.format(channel_id) else: self.url = None - self.current = None self.next_update = 0 self.songs.clear() @@ -78,12 +86,15 @@ def update(self): try: res = requests.get(self.url, timeout=UPDATE_TIMEOUT) res.raise_for_status() + data = res.json() except Exception: self.next_update = time.time() + UPDATE_WAIT raise + current_song = None + + self.songs.clear() next_key = None - data = res.json() for index, song in enumerate(data['song']): if song['artist'] is None: song['artist'] = 'Unknown Artist' @@ -98,28 +109,28 @@ def update(self): self.songs[key] = song next_key = key if index == 0: - self.current = song - if (break_key := build_key(BREAK_SONG)) not in self.songs: - self.songs[break_key] = { - 'artist': BREAK_SONG[0], - 'title': BREAK_SONG[1], - 'cover': BREAK_COVER_URL, - 'duration': '30000', - } + current_song = song now = time.time() - next_update = (self.current['play_time'] + int(self.current['duration'])) / 1000 + if current_song: + next_update = (current_song['play_time'] + int(current_song['duration'])) / 1000 + LOG.log(f'update: {current_song["artist"]} - {current_song["title"]}') + else: + next_update = 0 + LOG.log(f'update: No song data.') + if next_update > now: - self.next_update = next_update + self.next_update = min(next_update, now + MAX_UPDATE_WAIT) else: self.next_update = now + UPDATE_WAIT - while len(self.songs) > MAX_SONGS: - self.songs.popitem(last=False) - def build_key(strings): - """Return a normalized tuple of words in the strings.""" + """Return a normalized tuple of words in the strings. + + A few songs in the RP library (mostly classical music) format artist and + title differently in stream metadata vs. the API, hence this key. + """ result = [] for s in strings: words = KEY_FILTER_RE.sub(' ', s).casefold().split() @@ -128,7 +139,16 @@ def build_key(strings): def init(): - global CHANNELS, CHANNEL_INFO + global BREAK_SONG, BREAK_KEY, CHANNELS, CHANNEL_INFO + + BREAK_SONG = { + 'artist': 'Commercial-free', + 'title': 'Listener-supported', + 'cover': 'https://img.radioparadise.com/covers/l/101.jpg', + 'duration': '60000', + } + BREAK_KEY = build_key((BREAK_SONG['artist'], BREAK_SONG['title'])) + addon = xbmcaddon.Addon() addon_path = addon.getAddonInfo('path') channels_json = Path(addon_path, 'resources', 'channels.json') diff --git a/script.radioparadise/resources/lib/service.py b/script.radioparadise/resources/lib/service.py index 8e3a33f82..b042c2562 100644 --- a/script.radioparadise/resources/lib/service.py +++ b/script.radioparadise/resources/lib/service.py @@ -100,43 +100,48 @@ def update(self): self.restart() elif self.stream_url: self.now_playing.update() - self.update_slideshow() self.update_song() + self.update_slideshow() def update_player(self): """Update the Kodi player with song metadata.""" song = self.song - if song and self.isPlayingAudio(): - item = self.getPlayingItem() - tag = item.getMusicInfoTag() - tag.setArtist(song.data['artist']) - tag.setTitle(song.data['title']) - tag.setGenres([]) - tag.setAlbum(song.data.get('album', '')) - rating = song.data.get('listener_rating', 0) - tag.setRating(rating) - tag.setUserRating(int(round(rating))) - tag.setYear(int(song.data.get('year', 0))) - item.setArt({'thumb': song.cover}) - item.setArt({'fanart': song.fanart}) - self.updateInfoTag(item) + player_key = self.get_song_key() + if song is None or player_key is None or song.key != player_key: + return + + item = self.getPlayingItem() + tag = item.getMusicInfoTag() + tag.setArtist(song.data['artist']) + tag.setTitle(song.data['title']) + tag.setGenres([]) + tag.setAlbum(song.data.get('album', '')) + rating = song.data.get('listener_rating', 0) + tag.setRating(rating) + tag.setUserRating(int(round(rating))) + tag.setYear(int(song.data.get('year', 0))) + item.setArt({'thumb': song.cover}) + item.setArt({'fanart': song.fanart}) + self.updateInfoTag(item) def clear_player(self): """Clear most of the Kodi player's song information.""" - if self.isPlayingAudio(): - info = self.getMusicInfoTag() - item = self.getPlayingItem() - tag = item.getMusicInfoTag() - tag.setArtist(info.getArtist()) - tag.setTitle(info.getTitle()) - tag.setGenres([]) - tag.setAlbum('') - tag.setRating(0) - tag.setUserRating(0) - tag.setYear(0) - item.setArt({'thumb': None}) - item.setArt({'fanart': None}) - self.updateInfoTag(item) + if not self.isPlayingAudio(): + return + + info = self.getMusicInfoTag() + item = self.getPlayingItem() + tag = item.getMusicInfoTag() + tag.setArtist(info.getArtist()) + tag.setTitle(info.getTitle()) + tag.setGenres([]) + tag.setAlbum('') + tag.setRating(0) + tag.setUserRating(0) + tag.setYear(0) + item.setArt({'thumb': None}) + item.setArt({'fanart': None}) + self.updateInfoTag(item) def update_slideshow(self): """Update the slideshow, if necessary.""" @@ -161,6 +166,7 @@ def update_song(self): if self.tracked_key is not None: self.tracked_time = time.time() self.tracked_key = player_key + LOG.log(f'player_key: {player_key}') start_time = None song_data = None @@ -172,14 +178,12 @@ def update_song(self): start_time = self.tracked_time song_data = self.now_playing.get_song_data(player_key) self.slideshow.set_slides(None) - if song.start_time == 0: - song.start_time = self.tracked_time - song.duration - else: + elif song.expired(): start_time = song.start_time + song.duration song_data = self.now_playing.get_next_song(player_key) # Fall back to stream metadata if song_data is None: - self.song = None + song.start_time = 0 self.tracked_time = 0 self.slideshow.set_slides(None) self.clear_player() @@ -199,8 +203,7 @@ def update_song(self): self.slideshow.set_slides(None) fanart = None - song_key = build_key((song_data['artist'], song_data['title'])) - self.song = Song(song_key, song_data, fanart, start_time) + self.song = Song(player_key, song_data, fanart, start_time) LOG.log(f'Song: {self.song}') self.update_player() diff --git a/script.radioparadise/resources/settings.xml b/script.radioparadise/resources/settings.xml index 8fc14729f..6a566766b 100644 --- a/script.radioparadise/resources/settings.xml +++ b/script.radioparadise/resources/settings.xml @@ -24,6 +24,7 @@ +