Skip to content

Commit

Permalink
[script.radioparadise] 2.1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
alxndr42 committed Nov 15, 2024
1 parent 22a250d commit ce65640
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 60 deletions.
5 changes: 5 additions & 0 deletions script.radioparadise/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## v2.1.2

- Add Serenity to Auto Play options
- Fix metadata handling

## v2.1.1

- Improve metadata handling
Expand Down
2 changes: 1 addition & 1 deletion script.radioparadise/addon.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.radioparadise" name="Radio Paradise" version="2.1.1" provider-name="Alexander Dietrich">
<addon id="script.radioparadise" name="Radio Paradise" version="2.1.2" provider-name="Alexander Dietrich">
<requires>
<import addon="xbmc.python" version="3.0.1"/>
<import addon="script.module.requests" version="2.0.0"/>
Expand Down
68 changes: 44 additions & 24 deletions script.radioparadise/resources/lib/radioparadise.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from collections import OrderedDict
import json
from pathlib import Path
import re
Expand All @@ -7,43 +6,53 @@
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):
"""Return a dict for the build_key()-created key, or None.
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.
Expand All @@ -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()

Expand All @@ -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'
Expand All @@ -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()
Expand All @@ -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')
Expand Down
73 changes: 38 additions & 35 deletions script.radioparadise/resources/lib/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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()

Expand Down
1 change: 1 addition & 0 deletions script.radioparadise/resources/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<option label="30204">1</option>
<option label="30205">2</option>
<option label="30206">3</option>
<option label="30208">4</option>
</options>
</constraints>
<control type="spinner" format="string"/>
Expand Down

0 comments on commit ce65640

Please sign in to comment.