-
Notifications
You must be signed in to change notification settings - Fork 306
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[script.radioparadise] 2.0.0 (#2513)
- Updates for Kodi v20 - Improve metadata handling
- Loading branch information
Showing
19 changed files
with
1,423 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
## v2.0.0 | ||
|
||
- Updates for Kodi v20 | ||
- Improve metadata handling | ||
|
||
## v1.0.5 | ||
|
||
- Rename World/Etc mix to Global | ||
|
||
## v1.0.4 | ||
|
||
- Update logos | ||
- Improve metadata error handling | ||
|
||
## v1.0.3 | ||
|
||
- Add mix selection by script parameter | ||
|
||
## v1.0.2 | ||
|
||
- Handle missing stream metadata | ||
|
||
## v1.0.1 | ||
|
||
- Restart playback automatically | ||
|
||
## v1.0.0 | ||
|
||
- Initial release |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
Radio Paradise logos and photos: Copyright Radio Paradise | ||
|
||
All other files ("This program"): Copyright Alexander Dietrich | ||
|
||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
|
||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
|
||
You should have received a copy of the GNU General Public License | ||
along with this program. If not, see <https://www.gnu.org/licenses/>. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Radio Paradise addon for Kodi | ||
|
||
Plays [Radio Paradise][] music mixes, accompanied by the HD slideshow. | ||
|
||
[radio paradise]: https://radioparadise.com/ | ||
|
||
## Features | ||
|
||
- Radio Paradise music mixes in AAC or FLAC | ||
- HD slideshow (optional) | ||
- Auto Play (optional) | ||
|
||
## Requirements | ||
|
||
- Kodi [release][] v20 or later | ||
|
||
[release]: https://kodi.wiki/view/Releases | ||
|
||
## Mix Selection by Script Parameter | ||
|
||
In addition to the Auto Play feature, the addon script can be called with a | ||
parameter to start a particular RP mix: | ||
|
||
```python | ||
RunScript('script.radioparadise', 1) | ||
``` | ||
|
||
Mix parameter: | ||
|
||
| Value | Mix | | ||
| --- | --- | | ||
| 0 | RP Main Mix | | ||
| 1 | RP Mellow Mix | | ||
| 2 | RP Rock Mix | | ||
| 3 | RP Global Mix | | ||
|
||
This can be used to add shortcuts for RP mixes in [favourites.xml][]. | ||
|
||
[favourites.xml]: https://kodi.wiki/view/Favourites.xml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||
<addon id="script.radioparadise" name="Radio Paradise" version="2.0.0" provider-name="Alexander Dietrich"> | ||
<requires> | ||
<import addon="xbmc.python" version="3.0.1"/> | ||
<import addon="script.module.requests" version="2.0.0"/> | ||
</requires> | ||
<extension point="xbmc.python.script" library="script.py"> | ||
<provides>audio</provides> | ||
</extension> | ||
<extension point="xbmc.service" library="service.py"/> | ||
<extension point="xbmc.addon.metadata"> | ||
<summary lang="en_GB">Radio Paradise addon for Kodi</summary> | ||
<description lang="en_GB">An eclectic DJ-mixed blend of rock, indie, electronica, world music, and more. Listener supported & always 100% commercial free.</description> | ||
<website>https://radioparadise.com/</website> | ||
<source>https://github.com/alxndr42/script.radioparadise</source> | ||
<license>GPL-3.0-or-later</license> | ||
<platform>all</platform> | ||
<assets> | ||
<icon>resources/icon.png</icon> | ||
<fanart>resources/fanart.jpg</fanart> | ||
<clearlogo>resources/clearlogo.png</clearlogo> | ||
</assets> | ||
</extension> | ||
</addon> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 46 additions & 0 deletions
46
script.radioparadise/resources/language/resource.language.en_gb/strings.po
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
msgid "" | ||
msgstr "" | ||
|
||
msgctxt "#30101" | ||
msgid "Audio Format" | ||
msgstr "" | ||
|
||
msgctxt "#30102" | ||
msgid "Auto Play" | ||
msgstr "" | ||
|
||
msgctxt "#30103" | ||
msgid "Visuals" | ||
msgstr "" | ||
|
||
msgctxt "#30104" | ||
msgid "Slide Duration (seconds)" | ||
msgstr "" | ||
|
||
msgctxt "#30201" | ||
msgid "AAC" | ||
msgstr "" | ||
|
||
msgctxt "#30202" | ||
msgid "FLAC" | ||
msgstr "" | ||
|
||
msgctxt "#30203" | ||
msgid "RP Main Mix" | ||
msgstr "" | ||
|
||
msgctxt "#30204" | ||
msgid "RP Mellow Mix" | ||
msgstr "" | ||
|
||
msgctxt "#30205" | ||
msgid "RP Rock Mix" | ||
msgstr "" | ||
|
||
msgctxt "#30206" | ||
msgid "RP Global Mix" | ||
msgstr "" | ||
|
||
msgctxt "#30207" | ||
msgid "RP Slideshow" | ||
msgstr "" |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
from collections import OrderedDict | ||
import re | ||
import time | ||
|
||
import requests | ||
|
||
|
||
NOWPLAYING_URL = 'https://api.radioparadise.com/api/nowplaying_list?chan={}' | ||
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') | ||
|
||
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 | ||
|
||
STREAMS = [ | ||
{ | ||
'channel': 0, | ||
'title': 'RP Main Mix', | ||
'url_aac': 'http://stream.radioparadise.com/aac-128', | ||
'url_flac': 'http://stream.radioparadise.com/flacm', | ||
}, | ||
{ | ||
'channel': 1, | ||
'title': 'RP Mellow Mix', | ||
'url_aac': 'http://stream.radioparadise.com/mellow-128', | ||
'url_flac': 'http://stream.radioparadise.com/mellow-flacm', | ||
}, | ||
{ | ||
'channel': 2, | ||
'title': 'RP Rock Mix', | ||
'url_aac': 'http://stream.radioparadise.com/rock-128', | ||
'url_flac': 'http://stream.radioparadise.com/rock-flacm', | ||
}, | ||
{ | ||
'channel': 3, | ||
'title': 'RP Global Mix', | ||
'url_aac': 'http://stream.radioparadise.com/global-128', | ||
'url_flac': 'http://stream.radioparadise.com/global-flacm', | ||
}, | ||
] | ||
STREAM_INFO = {s['url_aac']: s for s in STREAMS} | ||
STREAM_INFO.update({s['url_flac']: s for s in STREAMS}) | ||
|
||
|
||
class NowPlaying(): | ||
"""Provides song information from the "nowplaying" API.""" | ||
|
||
def __init__(self): | ||
"""Constructor""" | ||
self.songs = OrderedDict() | ||
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) | ||
|
||
def get_next_song(self, song_key): | ||
"""Return a dict for song_key's successor, or None. | ||
The "cover" value will be an absolute URL. | ||
""" | ||
next_key = self.songs.get(song_key, {}).get('next_key') | ||
return self.songs.get(next_key) | ||
|
||
def set_channel(self, channel): | ||
"""Set the RP channel number, or None.""" | ||
if channel is not None: | ||
self.url = NOWPLAYING_URL.format(channel) | ||
else: | ||
self.url = None | ||
self.current = None | ||
self.next_update = 0 | ||
self.songs.clear() | ||
|
||
def update(self): | ||
"""Update song information from the API, if necessary. | ||
Calls the API only if the "refresh" timer has expired. | ||
Raises an exception on error responses or timeouts. | ||
""" | ||
if self.url is None: | ||
return | ||
if time.time() < self.next_update: | ||
return | ||
|
||
try: | ||
res = requests.get(self.url, timeout=UPDATE_TIMEOUT) | ||
res.raise_for_status() | ||
except Exception: | ||
self.next_update = time.time() + UPDATE_WAIT | ||
raise | ||
|
||
next_key = None | ||
data = res.json() | ||
song_items = sorted(list(data['song'].items()), key=lambda s: int(s[0])) | ||
for index, song in song_items: | ||
if song['artist'] is None: | ||
song['artist'] = 'Unknown Artist' | ||
if song['title'] is None: | ||
song['title'] = 'Unknown Title' | ||
song['cover'] = COVER_URL.format(song['cover']) | ||
slides = song.get('slideshow', '').split(',') | ||
slides = [SLIDESHOW_URL.format(s) for s in slides if s] | ||
song['slide_urls'] = slides | ||
song['next_key'] = next_key | ||
key = build_key((song['artist'], song['title'])) | ||
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', | ||
} | ||
self.next_update = time.time() + data['refresh'] | ||
while len(self.songs) > MAX_SONGS: | ||
self.songs.popitem(last=False) | ||
|
||
|
||
def build_key(strings): | ||
"""Return a normalized tuple of words in the strings.""" | ||
result = [] | ||
for s in strings: | ||
words = KEY_FILTER_RE.sub(' ', s).casefold().split() | ||
result.extend(words) | ||
return tuple(sorted(result)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import sys | ||
|
||
import xbmc | ||
import xbmcaddon | ||
import xbmcgui | ||
|
||
from .radioparadise import STREAMS | ||
|
||
|
||
class Window(xbmcgui.WindowXML): | ||
def onInit(self): | ||
xbmc.executebuiltin('Container.SetViewMode(100)') | ||
listitems = [] | ||
for s in STREAMS: | ||
item = xbmcgui.ListItem(s['title']) | ||
item.setProperty('channel', str(s['channel'])) | ||
listitems.append(item) | ||
self.clearList() | ||
self.addItems(listitems) | ||
xbmc.sleep(100) | ||
self.setFocusId(self.getCurrentContainerId()) | ||
|
||
def onClick(self, controlId): | ||
if controlId == 100: | ||
item = self.getListItem(self.getCurrentListPosition()) | ||
channel = int(item.getProperty('channel')) | ||
play_channel(channel) | ||
self.close() | ||
|
||
|
||
def play_channel(channel_number): | ||
"""Play the channel, unless it's already playing.""" | ||
stream = STREAMS[channel_number] | ||
addon = xbmcaddon.Addon() | ||
audio_format = addon.getSetting('audio_format') | ||
if audio_format == 'flac': | ||
url = stream['url_flac'] | ||
else: | ||
url = stream['url_aac'] | ||
player = xbmc.Player() | ||
if not player.isPlayingAudio() or player.getPlayingFile() != url: | ||
player.stop() | ||
player.play(url) | ||
|
||
|
||
def run_script(): | ||
addon = xbmcaddon.Addon() | ||
if len(sys.argv) == 2: | ||
auto_play = int(sys.argv[1]) | ||
else: | ||
auto_play = addon.getSettingInt('auto_play') | ||
if auto_play == -1: | ||
addon_path = addon.getAddonInfo('path') | ||
window = Window('script-radioparadise.xml', addon_path) | ||
window.doModal() | ||
del window | ||
else: | ||
play_channel(auto_play) |
Oops, something went wrong.