Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[script.radioparadise] 2.1.2 #2680

Merged
merged 1 commit into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading