Use SimplePlugin framework
This commit is contained in:
parent
f16f234679
commit
ac9160ea2c
|
@ -1,5 +1,8 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v2.0.2
|
||||||
|
* Use SimplePlugin framework (http://romanvm.github.io/script.module.simpleplugin/index.html)
|
||||||
|
|
||||||
## v2.0.1
|
## v2.0.1
|
||||||
* New setting 'albums_per_page'
|
* New setting 'albums_per_page'
|
||||||
* New menu structure
|
* New menu structure
|
||||||
|
|
|
@ -8,7 +8,7 @@ Kodi plugin to stream music from Subsonic.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
* Navigate to your `.kodi/addons/` folder
|
* Navigate to your `.kodi/addons/` folder
|
||||||
* Clone this repository: `git clone https://github.com/basilfx/plugin.audio.subsonic.git`
|
* Clone this repository: `git clone https://github.com/gordielachance/plugin.audio.subsonic.git`
|
||||||
* (Re)start Kodi.
|
* (Re)start Kodi.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
514
addon.py
514
addon.py
|
@ -1,514 +0,0 @@
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import urllib
|
|
||||||
import urlparse
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
import xbmcgui
|
|
||||||
import xbmcaddon
|
|
||||||
import xbmcplugin
|
|
||||||
|
|
||||||
# Make sure library folder is on the path
|
|
||||||
addon = xbmcaddon.Addon()
|
|
||||||
sys.path.append(xbmc.translatePath(os.path.join(
|
|
||||||
addon.getAddonInfo("path"), "lib")))
|
|
||||||
|
|
||||||
import libsonic_extra
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(object):
|
|
||||||
"""
|
|
||||||
Plugin container.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, addon_url, addon_handle, addon_args):
|
|
||||||
self.addon_url = addon_url
|
|
||||||
self.addon_handle = addon_handle
|
|
||||||
self.addon_args = addon_args
|
|
||||||
|
|
||||||
# Retrieve plugin settings
|
|
||||||
self.url = addon.getSetting("subsonic_url")
|
|
||||||
self.username = addon.getSetting("username")
|
|
||||||
self.password = addon.getSetting("password")
|
|
||||||
self.apiversion = addon.getSetting("apiversion")
|
|
||||||
self.insecure = addon.getSetting("insecure") == "true"
|
|
||||||
self.legacyauth = addon.getSetting("legacyauth") == "true"
|
|
||||||
|
|
||||||
|
|
||||||
self.albums_per_page = int(addon.getSetting("albums_per_page"))
|
|
||||||
self.tracks_per_page = int(addon.getSetting("tracks_per_page"))
|
|
||||||
|
|
||||||
self.bitrate = int(addon.getSetting("bitrate"))
|
|
||||||
self.transcode_format = addon.getSetting("transcode_format")
|
|
||||||
|
|
||||||
# Create connection
|
|
||||||
self.connection = libsonic_extra.SubsonicClient(
|
|
||||||
self.url, self.username, self.password, self.apiversion, self.insecure, self.legacyauth)
|
|
||||||
|
|
||||||
def build_url(self, query):
|
|
||||||
"""
|
|
||||||
Create URL for page.
|
|
||||||
"""
|
|
||||||
|
|
||||||
parts = list(urlparse.urlparse(self.addon_url))
|
|
||||||
parts[4] = urllib.urlencode(query)
|
|
||||||
|
|
||||||
return urlparse.urlunparse(parts)
|
|
||||||
|
|
||||||
def route(self):
|
|
||||||
"""
|
|
||||||
Map a Kodi request to certain action. This takes the `mode' query
|
|
||||||
parameter and executed the function in this instance with that name.
|
|
||||||
"""
|
|
||||||
|
|
||||||
mode = self.addon_args.get("mode", ["main_menu"])[0]
|
|
||||||
|
|
||||||
if not mode.startswith("_"):
|
|
||||||
getattr(self, mode)()
|
|
||||||
|
|
||||||
def add_track(self, track, show_artist=False):
|
|
||||||
"""
|
|
||||||
Display one track in the list.
|
|
||||||
"""
|
|
||||||
|
|
||||||
url = self.connection.streamUrl(
|
|
||||||
sid=track["id"], maxBitRate=self.bitrate,
|
|
||||||
tformat=self.transcode_format)
|
|
||||||
|
|
||||||
# Create list item
|
|
||||||
if show_artist:
|
|
||||||
title = "%s - %s" % (
|
|
||||||
track.get("artist", "<Unknown>"),
|
|
||||||
track.get("title", "<Unknown>"))
|
|
||||||
else:
|
|
||||||
title = track.get("title", "<Unknown>")
|
|
||||||
|
|
||||||
# Create item
|
|
||||||
li = xbmcgui.ListItem(title)
|
|
||||||
|
|
||||||
# Handle cover art
|
|
||||||
if "coverArt" in track:
|
|
||||||
cover_art_url = self.connection.getCoverArtUrl(track["coverArt"])
|
|
||||||
|
|
||||||
li.setIconImage(cover_art_url)
|
|
||||||
li.setThumbnailImage(cover_art_url)
|
|
||||||
li.setProperty("fanart_image", cover_art_url)
|
|
||||||
|
|
||||||
# Handle metadata
|
|
||||||
li.setProperty("IsPlayable", "true")
|
|
||||||
li.setMimeType(track.get("contentType"))
|
|
||||||
li.setInfo(type="Music", infoLabels={
|
|
||||||
"Artist": track.get("artist"),
|
|
||||||
"Title": track.get("title"),
|
|
||||||
"Year": track.get("year"),
|
|
||||||
"Duration": track.get("duration"),
|
|
||||||
"Genre": track.get("genre"),
|
|
||||||
"TrackNumber": track.get("track")})
|
|
||||||
|
|
||||||
xbmcplugin.addDirectoryItem(
|
|
||||||
handle=self.addon_handle, url=url, listitem=li)
|
|
||||||
|
|
||||||
def add_album(self, album, show_artist=False):
|
|
||||||
"""
|
|
||||||
Display one album in the list.
|
|
||||||
"""
|
|
||||||
|
|
||||||
url = self.build_url({
|
|
||||||
"mode": "track_list",
|
|
||||||
"album_id": album["id"]})
|
|
||||||
|
|
||||||
# Create list item
|
|
||||||
if show_artist:
|
|
||||||
title = "%s - %s" % (
|
|
||||||
album.get("artist", "<Unknown>"),
|
|
||||||
album.get("name", "<Unknown>"))
|
|
||||||
else:
|
|
||||||
title = album.get("name", "<Unknown>")
|
|
||||||
|
|
||||||
# Add year if applicable
|
|
||||||
if album.get("year"):
|
|
||||||
title = "%s [%d]" % (title, album.get("year"))
|
|
||||||
|
|
||||||
# Create item
|
|
||||||
li = xbmcgui.ListItem()
|
|
||||||
li.setLabel(title)
|
|
||||||
|
|
||||||
# Handle cover art
|
|
||||||
if "coverArt" in album:
|
|
||||||
cover_art_url = self.connection.getCoverArtUrl(album["coverArt"])
|
|
||||||
|
|
||||||
li.setIconImage(cover_art_url)
|
|
||||||
li.setThumbnailImage(cover_art_url)
|
|
||||||
li.setProperty("fanart_image", cover_art_url)
|
|
||||||
|
|
||||||
# Handle metadata
|
|
||||||
li.setInfo(type="music", infoLabels={
|
|
||||||
"Artist": album.get("artist"),
|
|
||||||
"Album": album.get("name"),
|
|
||||||
"Year": album.get("year")})
|
|
||||||
|
|
||||||
xbmcplugin.addDirectoryItem(
|
|
||||||
handle=self.addon_handle, url=url, listitem=li, isFolder=True)
|
|
||||||
|
|
||||||
def main_menu(self):
|
|
||||||
"""
|
|
||||||
Display main menu.
|
|
||||||
"""
|
|
||||||
|
|
||||||
menu = [
|
|
||||||
{"mode": "list_artists", "foldername": "Artists"},
|
|
||||||
{"mode": "menu_albums", "foldername": "Albums"},
|
|
||||||
{"mode": "list_playlists", "foldername": "Playlists"},
|
|
||||||
{"mode": "menu_tracks", "foldername": "Tracks"}
|
|
||||||
]
|
|
||||||
|
|
||||||
for entry in menu:
|
|
||||||
url = self.build_url(entry)
|
|
||||||
|
|
||||||
li = xbmcgui.ListItem(entry["foldername"])
|
|
||||||
xbmcplugin.addDirectoryItem(
|
|
||||||
handle=self.addon_handle, url=url, listitem=li, isFolder=True)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def menu_tracks(self):
|
|
||||||
"""
|
|
||||||
Display main menu.
|
|
||||||
"""
|
|
||||||
|
|
||||||
menu = [
|
|
||||||
{"mode": "list_tracks_starred", "foldername": "Starred tracks"},
|
|
||||||
{"mode": "list_tracks_random_genre", "foldername": "Random tracks by genre"},
|
|
||||||
{"mode": "list_tracks_random_year", "foldername": "Random tracks by year"}
|
|
||||||
]
|
|
||||||
|
|
||||||
for entry in menu:
|
|
||||||
url = self.build_url(entry)
|
|
||||||
|
|
||||||
li = xbmcgui.ListItem(entry["foldername"])
|
|
||||||
xbmcplugin.addDirectoryItem(
|
|
||||||
handle=self.addon_handle, url=url, listitem=li, isFolder=True)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def menu_albums(self):
|
|
||||||
"""
|
|
||||||
Display main menu.
|
|
||||||
"""
|
|
||||||
|
|
||||||
menu = [
|
|
||||||
{"mode": "list_albums_newest", "foldername": "Newest albums", "page":1},
|
|
||||||
{"mode": "list_albums_frequent", "foldername": "Most played albums", "page":1},
|
|
||||||
{"mode": "list_albums_recent", "foldername": "Recently played albums", "page":1},
|
|
||||||
{"mode": "list_albums_genre", "foldername": "Albums by Genre"}
|
|
||||||
]
|
|
||||||
|
|
||||||
for entry in menu:
|
|
||||||
url = self.build_url(entry)
|
|
||||||
|
|
||||||
li = xbmcgui.ListItem(entry["foldername"])
|
|
||||||
xbmcplugin.addDirectoryItem(
|
|
||||||
handle=self.addon_handle, url=url, listitem=li, isFolder=True)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def menu_link(self):
|
|
||||||
mode = self.addon_args["mode"][0]
|
|
||||||
menu_item = {"mode": "main_menu", "foldername": "Back to Menu"}
|
|
||||||
menu_item_url = self.build_url(menu_item)
|
|
||||||
menu_item_li = xbmcgui.ListItem(menu_item["foldername"])
|
|
||||||
xbmcplugin.addDirectoryItem(
|
|
||||||
handle=self.addon_handle, url=menu_item_url, listitem=menu_item_li, isFolder=True)
|
|
||||||
|
|
||||||
def next_page_link(self,page):
|
|
||||||
|
|
||||||
page += 1
|
|
||||||
title = "Next page (%s)" % (page)
|
|
||||||
|
|
||||||
mode = self.addon_args["mode"][0]
|
|
||||||
|
|
||||||
menu_item = {"mode": mode, "foldername": title, "page":page}
|
|
||||||
menu_item_url = self.build_url(menu_item)
|
|
||||||
menu_item_li = xbmcgui.ListItem(menu_item["foldername"])
|
|
||||||
xbmcplugin.addDirectoryItem(
|
|
||||||
handle=self.addon_handle, url=menu_item_url, listitem=menu_item_li, isFolder=True)
|
|
||||||
|
|
||||||
def list_playlists(self):
|
|
||||||
"""
|
|
||||||
Display playlists.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for playlist in self.connection.walk_playlists():
|
|
||||||
cover_art_url = self.connection.getCoverArtUrl(
|
|
||||||
playlist["coverArt"])
|
|
||||||
url = self.build_url({
|
|
||||||
"mode": "list_playlist_songs", "playlist_id": playlist["id"]})
|
|
||||||
|
|
||||||
li = xbmcgui.ListItem(playlist["name"], iconImage=cover_art_url)
|
|
||||||
xbmcplugin.addDirectoryItem(
|
|
||||||
handle=self.addon_handle, url=url, listitem=li, isFolder=True)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def list_playlist_songs(self):
|
|
||||||
"""
|
|
||||||
Display playlist tracks.
|
|
||||||
"""
|
|
||||||
|
|
||||||
playlist_id = self.addon_args["playlist_id"][0]
|
|
||||||
|
|
||||||
xbmcplugin.setContent(self.addon_handle, "songs")
|
|
||||||
|
|
||||||
for track in self.connection.walk_playlist(playlist_id):
|
|
||||||
self.add_track(track, show_artist=True)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def list_albums_genre(self):
|
|
||||||
"""
|
|
||||||
Display albums by genre list.
|
|
||||||
"""
|
|
||||||
|
|
||||||
genres = self.connection.walk_genres()
|
|
||||||
genres_sorted = sorted(genres, key=lambda k: k['value'])
|
|
||||||
|
|
||||||
for genre in genres_sorted:
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
genre_slug = genre["value"]
|
|
||||||
genre_name = genre_slug.replace(';',' / ')
|
|
||||||
genre_count = int(genre["albumCount"])
|
|
||||||
|
|
||||||
if genre_count == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
|
|
||||||
genre_name += ' (' + str(genre_count) + ')'
|
|
||||||
|
|
||||||
url = self.build_url({
|
|
||||||
"mode": 'album_list_genre',
|
|
||||||
"foldername": genre_slug.encode("utf-8")})
|
|
||||||
|
|
||||||
li = xbmcgui.ListItem(genre_name)
|
|
||||||
xbmcplugin.addDirectoryItem(
|
|
||||||
handle=self.addon_handle, url=url, listitem=li, isFolder=True)
|
|
||||||
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def list_albums_newest(self):
|
|
||||||
"""
|
|
||||||
Display newest album list.
|
|
||||||
"""
|
|
||||||
|
|
||||||
page = int(self.addon_args["page"][0])
|
|
||||||
size = self.albums_per_page
|
|
||||||
offset = size * ( page -1 )
|
|
||||||
|
|
||||||
xbmcplugin.setContent(self.addon_handle,"albums")
|
|
||||||
|
|
||||||
for album in self.connection.walk_albums(ltype='newest',size=size,offset=offset):
|
|
||||||
self.add_album(album, show_artist=True)
|
|
||||||
|
|
||||||
self.next_page_link(page)
|
|
||||||
self.menu_link()
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def list_albums_frequent(self):
|
|
||||||
"""
|
|
||||||
Display most played albums list.
|
|
||||||
"""
|
|
||||||
|
|
||||||
page = int(self.addon_args["page"][0])
|
|
||||||
size = self.albums_per_page
|
|
||||||
offset = size * ( page -1 )
|
|
||||||
|
|
||||||
xbmcplugin.setContent(self.addon_handle,"albums")
|
|
||||||
|
|
||||||
for album in self.connection.walk_albums(ltype='frequent',size=size,offset=offset):
|
|
||||||
self.add_album(album, show_artist=True)
|
|
||||||
|
|
||||||
self.next_page_link(page)
|
|
||||||
self.menu_link()
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def list_albums_recent(self):
|
|
||||||
"""
|
|
||||||
Display recently played album list.
|
|
||||||
"""
|
|
||||||
|
|
||||||
page = int(self.addon_args["page"][0])
|
|
||||||
size = self.albums_per_page
|
|
||||||
offset = size * ( page -1 )
|
|
||||||
|
|
||||||
xbmcplugin.setContent(self.addon_handle,"albums")
|
|
||||||
|
|
||||||
for album in self.connection.walk_albums(ltype='recent',size=size):
|
|
||||||
self.add_album(album, show_artist=True)
|
|
||||||
|
|
||||||
self.next_page_link(page)
|
|
||||||
self.menu_link()
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
|
|
||||||
def album_list_genre(self):
|
|
||||||
"""
|
|
||||||
Display album list by genre menu.
|
|
||||||
"""
|
|
||||||
|
|
||||||
genre = self.addon_args["foldername"][0].decode("utf-8")
|
|
||||||
size = self.albums_per_page
|
|
||||||
|
|
||||||
xbmcplugin.setContent(self.addon_handle,"albums")
|
|
||||||
|
|
||||||
for album in self.connection.walk_albums(ltype='byGenre',size=size,genre=genre):
|
|
||||||
self.add_album(album, show_artist=True)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def list_artists(self):
|
|
||||||
"""
|
|
||||||
Display artist list
|
|
||||||
"""
|
|
||||||
|
|
||||||
xbmcplugin.setContent(self.addon_handle, "artists")
|
|
||||||
|
|
||||||
for artist in self.connection.walk_artists():
|
|
||||||
cover_art_url = self.connection.getCoverArtUrl(artist["id"])
|
|
||||||
url = self.build_url({
|
|
||||||
"mode": "album_list",
|
|
||||||
"artist_id": artist["id"]})
|
|
||||||
|
|
||||||
li = xbmcgui.ListItem(artist["name"])
|
|
||||||
li.setIconImage(cover_art_url)
|
|
||||||
li.setThumbnailImage(cover_art_url)
|
|
||||||
li.setProperty("fanart_image", cover_art_url)
|
|
||||||
xbmcplugin.addDirectoryItem(
|
|
||||||
handle=self.addon_handle, url=url, listitem=li, isFolder=True)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def album_list(self):
|
|
||||||
"""
|
|
||||||
Display list of albums for certain artist.
|
|
||||||
"""
|
|
||||||
|
|
||||||
artist_id = self.addon_args["artist_id"][0]
|
|
||||||
|
|
||||||
xbmcplugin.setContent(self.addon_handle,"albums")
|
|
||||||
|
|
||||||
for album in self.connection.walk_artist(artist_id):
|
|
||||||
self.add_album(album)
|
|
||||||
|
|
||||||
xbmcplugin.addSortMethod(
|
|
||||||
self.addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
||||||
xbmcplugin.addSortMethod(
|
|
||||||
self.addon_handle, xbmcplugin.SORT_METHOD_ALBUM)
|
|
||||||
xbmcplugin.addSortMethod(
|
|
||||||
self.addon_handle, xbmcplugin.SORT_METHOD_ARTIST)
|
|
||||||
xbmcplugin.addSortMethod(
|
|
||||||
self.addon_handle, xbmcplugin.SORT_METHOD_VIDEO_YEAR)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def track_list(self):
|
|
||||||
"""
|
|
||||||
Display track list.
|
|
||||||
"""
|
|
||||||
|
|
||||||
album_id = self.addon_args["album_id"][0]
|
|
||||||
|
|
||||||
xbmcplugin.setContent(self.addon_handle, "songs")
|
|
||||||
|
|
||||||
for track in self.connection.walk_album(album_id):
|
|
||||||
self.add_track(track)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
|
|
||||||
def list_tracks_starred(self):
|
|
||||||
"""
|
|
||||||
Display starred songs.
|
|
||||||
"""
|
|
||||||
|
|
||||||
xbmcplugin.setContent(self.addon_handle, "songs")
|
|
||||||
|
|
||||||
for starred in self.connection.walk_tracks_starred():
|
|
||||||
self.add_track(starred, show_artist=True)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def list_tracks_random_genre(self):
|
|
||||||
"""
|
|
||||||
Display random genre list.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for genre in self.connection.walk_genres():
|
|
||||||
url = self.build_url({
|
|
||||||
"mode": "random_by_genre_track_list",
|
|
||||||
"foldername": genre["value"].encode("utf-8")})
|
|
||||||
|
|
||||||
li = xbmcgui.ListItem(genre["value"])
|
|
||||||
xbmcplugin.addDirectoryItem(
|
|
||||||
handle=self.addon_handle, url=url, listitem=li, isFolder=True)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def random_by_genre_track_list(self):
|
|
||||||
"""
|
|
||||||
Display random tracks by genre
|
|
||||||
"""
|
|
||||||
|
|
||||||
genre = self.addon_args["foldername"][0].decode("utf-8")
|
|
||||||
|
|
||||||
xbmcplugin.setContent(self.addon_handle, "songs")
|
|
||||||
|
|
||||||
for track in self.connection.walk_tracks_random(
|
|
||||||
size=self.tracks_per_page, genre=genre):
|
|
||||||
self.add_track(track, show_artist=True)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
def list_tracks_random_year(self):
|
|
||||||
"""
|
|
||||||
Display random tracks by year.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from_year = xbmcgui.Dialog().input(
|
|
||||||
"From year", type=xbmcgui.INPUT_NUMERIC)
|
|
||||||
to_year = xbmcgui.Dialog().input(
|
|
||||||
"To year", type=xbmcgui.INPUT_NUMERIC)
|
|
||||||
|
|
||||||
xbmcplugin.setContent(self.addon_handle, "songs")
|
|
||||||
|
|
||||||
for track in self.connection.walk_tracks_random(
|
|
||||||
size=self.tracks_per_page, from_year=from_year, to_year=to_year):
|
|
||||||
self.add_track(track, show_artist=True)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(self.addon_handle)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Entry point for this plugin. Instantiates a new plugin object and runs the
|
|
||||||
action that is given.
|
|
||||||
"""
|
|
||||||
|
|
||||||
addon_url = sys.argv[0]
|
|
||||||
addon_handle = int(sys.argv[1])
|
|
||||||
addon_args = urlparse.parse_qs(sys.argv[2][1:])
|
|
||||||
|
|
||||||
# Route request to action.
|
|
||||||
Plugin(addon_url, addon_handle, addon_args).route()
|
|
||||||
|
|
||||||
# Start plugin from within Kodi.
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
11
addon.xml
11
addon.xml
|
@ -1,9 +1,12 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="plugin.audio.subsonic" name="Subsonic" version="2.0.1" provider-name="BasilFX,grosbouff,lrusak">
|
<addon id="plugin.audio.subsonic" name="Subsonic" version="2.0.2" provider-name="BasilFX,grosbouff,lrusak">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.19.0"/>
|
<import addon="xbmc.python" version="2.14.0"/>
|
||||||
|
<import addon="script.module.dateutil" version="2.4.2"/>
|
||||||
|
<import addon="script.module.simpleplugin" version="2.0.1"/>
|
||||||
|
|
||||||
</requires>
|
</requires>
|
||||||
<extension point="xbmc.python.pluginsource" library="addon.py">
|
<extension point="xbmc.python.pluginsource" library="main.py">
|
||||||
<provides>audio</provides>
|
<provides>audio</provides>
|
||||||
</extension>
|
</extension>
|
||||||
<extension point="xbmc.addon.metadata">
|
<extension point="xbmc.addon.metadata">
|
||||||
|
@ -15,7 +18,7 @@
|
||||||
<license>MIT</license>
|
<license>MIT</license>
|
||||||
<forum></forum>
|
<forum></forum>
|
||||||
<website>http://www.subsonic.org</website>
|
<website>http://www.subsonic.org</website>
|
||||||
<source>https://github.com/basilfx/plugin.audio.subsonic</source>
|
<source>https://github.com/gordielachance/plugin.audio.subsonic</source>
|
||||||
<email></email>
|
<email></email>
|
||||||
</extension>
|
</extension>
|
||||||
</addon>
|
</addon>
|
||||||
|
|
|
@ -0,0 +1,613 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Module: main
|
||||||
|
# Author: Roman V. M.
|
||||||
|
# Created on: 28.11.2014
|
||||||
|
# License: GPL v.3 https://www.gnu.org/copyleft/gpl.html
|
||||||
|
|
||||||
|
from simpleplugin import Plugin
|
||||||
|
from simpleplugin import Addon
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
import dateutil.parser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Make sure library folder is on the path
|
||||||
|
sys.path.append(xbmc.translatePath(
|
||||||
|
os.path.join(Addon().addon.getAddonInfo('path'), 'lib')))
|
||||||
|
|
||||||
|
# Create plugin instance
|
||||||
|
plugin = Plugin()
|
||||||
|
|
||||||
|
|
||||||
|
# initialize_gettext
|
||||||
|
#_ = plugin.initialize_gettext() //https://github.com/romanvm/script.module.simpleplugin/issues/1
|
||||||
|
|
||||||
|
connection = None
|
||||||
|
cache_minutes = int(Addon().get_setting('cache_minutes'))
|
||||||
|
|
||||||
|
import libsonic_extra
|
||||||
|
|
||||||
|
def popup(text, time=5000, image=None):
|
||||||
|
title = Addon().addon.getAddonInfo('name')
|
||||||
|
icon = Addon().addon.getAddonInfo('icon')
|
||||||
|
xbmc.executebuiltin('Notification(%s, %s, %d, %s)' % (title, text,
|
||||||
|
time, icon))
|
||||||
|
|
||||||
|
def get_connection():
|
||||||
|
global connection
|
||||||
|
|
||||||
|
if connection is None:
|
||||||
|
# Create connection
|
||||||
|
|
||||||
|
try:
|
||||||
|
connection = libsonic_extra.SubsonicClient(
|
||||||
|
Addon().get_setting('subsonic_url'),
|
||||||
|
Addon().get_setting('username'),
|
||||||
|
Addon().get_setting('password'),
|
||||||
|
Addon().get_setting('apiversion'),
|
||||||
|
Addon().get_setting('insecure') == 'true',
|
||||||
|
Addon().get_setting('legacyauth') == 'true',
|
||||||
|
)
|
||||||
|
connected = connection.ping()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if connected is False:
|
||||||
|
popup('Connection error')
|
||||||
|
return False
|
||||||
|
|
||||||
|
return connection
|
||||||
|
|
||||||
|
def menu_root(params):
|
||||||
|
|
||||||
|
# get connection
|
||||||
|
connection = get_connection()
|
||||||
|
|
||||||
|
listing = []
|
||||||
|
|
||||||
|
menus = {
|
||||||
|
'artists': {
|
||||||
|
'name': 'Artists',
|
||||||
|
'callback': 'list_artists',
|
||||||
|
'thumb': None
|
||||||
|
},
|
||||||
|
'albums': {
|
||||||
|
'name': 'Albums',
|
||||||
|
'callback': 'menu_albums',
|
||||||
|
'thumb': None
|
||||||
|
},
|
||||||
|
'tracks': {
|
||||||
|
'name': 'Tracks',
|
||||||
|
'callback': 'menu_tracks',
|
||||||
|
'thumb': None
|
||||||
|
},
|
||||||
|
'playlists': {
|
||||||
|
'name': 'Playlists',
|
||||||
|
'callback': 'list_playlists',
|
||||||
|
'thumb': None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Iterate through categories
|
||||||
|
|
||||||
|
for mid in menus:
|
||||||
|
|
||||||
|
# image
|
||||||
|
if 'thumb' in menus[mid]:
|
||||||
|
thumb = menus[mid]['thumb']
|
||||||
|
|
||||||
|
listing.append({
|
||||||
|
'label': menus[mid]['name'],
|
||||||
|
'thumb': thumb, # Item thumbnail
|
||||||
|
'fanart': thumb, # Item thumbnail
|
||||||
|
'url': plugin.get_url(
|
||||||
|
action=menus[mid]['callback'],
|
||||||
|
mid=mid
|
||||||
|
)
|
||||||
|
}) # Item label
|
||||||
|
|
||||||
|
return plugin.create_listing(
|
||||||
|
listing,
|
||||||
|
#succeeded = True, #if False Kodi won’t open a new listing and stays on the current level.
|
||||||
|
#update_listing = False, #if True, Kodi won’t open a sub-listing but refresh the current one.
|
||||||
|
#cache_to_disk = True, #cache this view to disk.
|
||||||
|
#sort_methods = None, #he list of integer constants representing virtual folder sort methods.
|
||||||
|
#view_mode = None, #a numeric code for a skin view mode. View mode codes are different in different skins except for 50 (basic listing).
|
||||||
|
#content = None #string - current plugin content, e.g. ‘movies’ or ‘episodes’.
|
||||||
|
)
|
||||||
|
|
||||||
|
def menu_albums(params):
|
||||||
|
|
||||||
|
# get connection
|
||||||
|
connection = get_connection()
|
||||||
|
|
||||||
|
listing = []
|
||||||
|
|
||||||
|
menus = {
|
||||||
|
'newest': {
|
||||||
|
'name': 'Newest albums',
|
||||||
|
'thumb': None,
|
||||||
|
'args': {"ltype": "newest"}
|
||||||
|
},
|
||||||
|
'frequent': {
|
||||||
|
'name': 'Most played albums',
|
||||||
|
'thumb': None,
|
||||||
|
'args': {"ltype": "frequent"}
|
||||||
|
},
|
||||||
|
'recent': {
|
||||||
|
'name': 'Recently played albums',
|
||||||
|
'thumb': None,
|
||||||
|
'args': {"ltype": "recent"}
|
||||||
|
},
|
||||||
|
'random': {
|
||||||
|
'name': 'Random albums',
|
||||||
|
'thumb': None,
|
||||||
|
'args': {"ltype": "random"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Iterate through categories
|
||||||
|
|
||||||
|
for mid in menus:
|
||||||
|
|
||||||
|
menu = menus.get(mid)
|
||||||
|
|
||||||
|
# image
|
||||||
|
if 'thumb' in menu:
|
||||||
|
thumb = menu.get('thumb')
|
||||||
|
|
||||||
|
listing.append({
|
||||||
|
'label': menu.get('name'),
|
||||||
|
'thumb': menu.get('thumb'), # Item thumbnail
|
||||||
|
'fanart': menu.get('thumb'), # Item thumbnail
|
||||||
|
'url': plugin.get_url(
|
||||||
|
action= 'list_albums',
|
||||||
|
page= 1,
|
||||||
|
query_args= json.dumps(menu.get('args'))
|
||||||
|
)
|
||||||
|
}) # Item label
|
||||||
|
|
||||||
|
return plugin.create_listing(
|
||||||
|
listing,
|
||||||
|
#succeeded = True, #if False Kodi won’t open a new listing and stays on the current level.
|
||||||
|
#update_listing = False, #if True, Kodi won’t open a sub-listing but refresh the current one.
|
||||||
|
#cache_to_disk = True, #cache this view to disk.
|
||||||
|
#sort_methods = None, #he list of integer constants representing virtual folder sort methods.
|
||||||
|
#view_mode = None, #a numeric code for a skin view mode. View mode codes are different in different skins except for 50 (basic listing).
|
||||||
|
#content = None #string - current plugin content, e.g. ‘movies’ or ‘episodes’.
|
||||||
|
)
|
||||||
|
|
||||||
|
def menu_tracks(params):
|
||||||
|
|
||||||
|
# get connection
|
||||||
|
connection = get_connection()
|
||||||
|
|
||||||
|
listing = []
|
||||||
|
|
||||||
|
menus = {
|
||||||
|
'starred': {
|
||||||
|
'name': 'Starred tracks',
|
||||||
|
'thumb': None,
|
||||||
|
'starred': True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Iterate through categories
|
||||||
|
|
||||||
|
for mid in menus:
|
||||||
|
|
||||||
|
menu = menus.get(mid)
|
||||||
|
|
||||||
|
# image
|
||||||
|
if 'thumb' in menu:
|
||||||
|
thumb = menu.get('thumb')
|
||||||
|
|
||||||
|
listing.append({
|
||||||
|
'label': menu.get('name'),
|
||||||
|
'thumb': menu.get('thumb'), # Item thumbnail
|
||||||
|
'fanart': menu.get('thumb'), # Item thumbnail
|
||||||
|
'url': plugin.get_url(
|
||||||
|
action= 'list_tracks',
|
||||||
|
starred= menu.get('starred')
|
||||||
|
)
|
||||||
|
}) # Item label
|
||||||
|
|
||||||
|
return plugin.create_listing(
|
||||||
|
listing,
|
||||||
|
#succeeded = True, #if False Kodi won’t open a new listing and stays on the current level.
|
||||||
|
#update_listing = False, #if True, Kodi won’t open a sub-listing but refresh the current one.
|
||||||
|
#cache_to_disk = True, #cache this view to disk.
|
||||||
|
#sort_methods = None, #he list of integer constants representing virtual folder sort methods.
|
||||||
|
#view_mode = None, #a numeric code for a skin view mode. View mode codes are different in different skins except for 50 (basic listing).
|
||||||
|
#content = None #string - current plugin content, e.g. ‘movies’ or ‘episodes’.
|
||||||
|
)
|
||||||
|
|
||||||
|
@plugin.cached(cache_minutes) #if cache is enabled, cache data for the following function
|
||||||
|
def list_artists(params):
|
||||||
|
|
||||||
|
# get connection
|
||||||
|
connection = get_connection()
|
||||||
|
|
||||||
|
listing = []
|
||||||
|
|
||||||
|
# Get items
|
||||||
|
items = connection.walk_artists()
|
||||||
|
|
||||||
|
# Iterate through items
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
listing.append({
|
||||||
|
'label': item['name'],
|
||||||
|
'thumb': connection.getCoverArtUrl(item.get('id')),
|
||||||
|
'fanart': connection.getCoverArtUrl(item.get('id')),
|
||||||
|
'url': plugin.get_url(action='list_artist_albums',
|
||||||
|
artist_id=item.get('id')
|
||||||
|
),
|
||||||
|
'info': {
|
||||||
|
'music': { ##http://romanvm.github.io/Kodistubs/_autosummary/xbmcgui.html#xbmcgui.ListItem.setInfo
|
||||||
|
'count': item.get('albumCount'),
|
||||||
|
'artist': item.get('name')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Sort methods - List of integer constants representing virtual folder sort methods. - see SortFileItem.h from Kodi core
|
||||||
|
sortable_by = (
|
||||||
|
0, #SORT_METHOD_NONE
|
||||||
|
11, #SORT_METHOD_ARTIST
|
||||||
|
40 #SORT_METHOD_UNSORTED
|
||||||
|
)
|
||||||
|
|
||||||
|
return plugin.create_listing(
|
||||||
|
listing,
|
||||||
|
#succeeded = True, #if False Kodi won’t open a new listing and stays on the current level.
|
||||||
|
#update_listing = False, #if True, Kodi won’t open a sub-listing but refresh the current one.
|
||||||
|
cache_to_disk = True, #cache this view to disk.
|
||||||
|
sort_methods = sortable_by, #he list of integer constants representing virtual folder sort methods.
|
||||||
|
#view_mode = None, #a numeric code for a skin view mode. View mode codes are different in different skins except for 50 (basic listing).
|
||||||
|
content = 'artists' #string - current plugin content, e.g. ‘movies’ or ‘episodes’.
|
||||||
|
)
|
||||||
|
|
||||||
|
@plugin.cached(cache_minutes) #if cache is enabled, cache data for the following function
|
||||||
|
def list_albums(params):
|
||||||
|
|
||||||
|
listing = []
|
||||||
|
|
||||||
|
# get connection
|
||||||
|
connection = get_connection()
|
||||||
|
|
||||||
|
query_args_json = params['query_args']
|
||||||
|
query_args = json.loads(query_args_json)
|
||||||
|
|
||||||
|
#size
|
||||||
|
albums_per_page = int(Addon().get_setting('albums_per_page'))
|
||||||
|
query_args["size"] = albums_per_page
|
||||||
|
|
||||||
|
#offset
|
||||||
|
offset = int(params.get('page')) - 1;
|
||||||
|
if offset > 0:
|
||||||
|
query_args["offset"] = offset * albums_per_page
|
||||||
|
|
||||||
|
#TO FIX this test is for pagination
|
||||||
|
#query_args["fromYear"] = 2016
|
||||||
|
#query_args["toYear"] = 2016
|
||||||
|
#query_args["ltype"] = 'byYear'
|
||||||
|
|
||||||
|
|
||||||
|
#debug
|
||||||
|
query_args_json = json.dumps(query_args)
|
||||||
|
plugin.log('list_albums with args:' + query_args_json);
|
||||||
|
#popup(json.dumps(query_args_json))
|
||||||
|
|
||||||
|
#Get items
|
||||||
|
items = connection.walk_albums(**query_args)
|
||||||
|
|
||||||
|
# Iterate through items
|
||||||
|
for item in items:
|
||||||
|
album = get_album_entry(item, params)
|
||||||
|
listing.append(album)
|
||||||
|
|
||||||
|
# Root menu
|
||||||
|
link_root = navigate_root()
|
||||||
|
listing.append(link_root)
|
||||||
|
|
||||||
|
# Pagination if we've not reached the end of the lsit
|
||||||
|
# if type(items) != type(True): TO FIX
|
||||||
|
link_next = navigate_next(params)
|
||||||
|
listing.append(link_next)
|
||||||
|
|
||||||
|
# Sort methods - List of integer constants representing virtual folder sort methods. - see SortFileItem.h from Kodi core
|
||||||
|
sortable_by = (
|
||||||
|
0, #SORT_METHOD_NONE
|
||||||
|
1, #SORT_METHOD_LABEL
|
||||||
|
#3, #SORT_METHOD_DATE
|
||||||
|
11, #SORT_METHOD_ARTIST
|
||||||
|
#14, #SORT_METHOD_ALBUM
|
||||||
|
18, #SORT_METHOD_YEAR
|
||||||
|
#21 #SORT_METHOD_DATEADDED
|
||||||
|
40 #SORT_METHOD_UNSORTED
|
||||||
|
)
|
||||||
|
|
||||||
|
return plugin.create_listing(
|
||||||
|
listing,
|
||||||
|
#succeeded = True, #if False Kodi won’t open a new listing and stays on the current level.
|
||||||
|
#update_listing = False, #if True, Kodi won’t open a sub-listing but refresh the current one.
|
||||||
|
cache_to_disk = True, #cache this view to disk.
|
||||||
|
sort_methods = sortable_by,
|
||||||
|
#view_mode = None, #a numeric code for a skin view mode. View mode codes are different in different skins except for 50 (basic listing).
|
||||||
|
content = 'albums' #string - current plugin content, e.g. ‘movies’ or ‘episodes’.
|
||||||
|
)
|
||||||
|
|
||||||
|
@plugin.cached(cache_minutes) #if cache is enabled, cache data for the following function
|
||||||
|
def list_artist_albums(params):
|
||||||
|
|
||||||
|
# get connection
|
||||||
|
connection = get_connection()
|
||||||
|
|
||||||
|
listing = []
|
||||||
|
|
||||||
|
# Get items
|
||||||
|
artist_id = params['artist_id']
|
||||||
|
params['hide_artist'] = True
|
||||||
|
items = connection.walk_artist(artist_id)
|
||||||
|
|
||||||
|
# Iterate through items
|
||||||
|
for item in items:
|
||||||
|
album = get_album_entry(item, params)
|
||||||
|
listing.append(album)
|
||||||
|
|
||||||
|
return plugin.create_listing(
|
||||||
|
listing,
|
||||||
|
#succeeded = True, #if False Kodi won’t open a new listing and stays on the current level.
|
||||||
|
#update_listing = False, #if True, Kodi won’t open a sub-listing but refresh the current one.
|
||||||
|
cache_to_disk = True, #cache this view to disk.
|
||||||
|
#sort_methods = None, #he list of integer constants representing virtual folder sort methods.
|
||||||
|
#view_mode = None, #a numeric code for a skin view mode. View mode codes are different in different skins except for 50 (basic listing).
|
||||||
|
content = 'albums' #string - current plugin content, e.g. ‘movies’ or ‘episodes’.
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_album_entry(item, params):
|
||||||
|
|
||||||
|
# name
|
||||||
|
|
||||||
|
if 'hide_artist' in params:
|
||||||
|
title = item.get('name', '<Unknown>')
|
||||||
|
else:
|
||||||
|
title = '%s - %s' % (item.get('artist', '<Unknown>'),
|
||||||
|
item.get('name', '<Unknown>'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'label': title,
|
||||||
|
'thumb': item.get('coverArt'),
|
||||||
|
'fanart': item.get('coverArt'),
|
||||||
|
'url': plugin.get_url(
|
||||||
|
action= 'list_tracks',
|
||||||
|
album_id= item.get('id'),
|
||||||
|
hide_artist= item.get('hide_artist')
|
||||||
|
),
|
||||||
|
'info': {
|
||||||
|
'music': { ##http://romanvm.github.io/Kodistubs/_autosummary/xbmcgui.html#xbmcgui.ListItem.setInfo
|
||||||
|
'count': item.get('songCount'),
|
||||||
|
'date': convert_date_from_iso8601(item.get('created')), #date added
|
||||||
|
'duration': item.get('duration'),
|
||||||
|
'artist': item.get('artist'),
|
||||||
|
'album': item.get('name'),
|
||||||
|
'year': item.get('year')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@plugin.cached(cache_minutes) #if cache is enabled, cache data for the following function
|
||||||
|
def list_tracks(params):
|
||||||
|
|
||||||
|
listing = []
|
||||||
|
|
||||||
|
# get connection
|
||||||
|
connection = get_connection()
|
||||||
|
|
||||||
|
# Album
|
||||||
|
if 'album_id' in params:
|
||||||
|
items = connection.walk_album(params['album_id'])
|
||||||
|
|
||||||
|
# Playlist
|
||||||
|
if 'playlist_id' in params:
|
||||||
|
items = connection.walk_playlist(params['playlist_id'])
|
||||||
|
|
||||||
|
#TO FIX
|
||||||
|
#tracknumber = 0
|
||||||
|
#for item in items:
|
||||||
|
# tracknumber += 1
|
||||||
|
# items[item]['tracknumber'] = tracknumber
|
||||||
|
|
||||||
|
# Starred
|
||||||
|
if 'starred' in params:
|
||||||
|
items = connection.walk_tracks_starred()
|
||||||
|
|
||||||
|
|
||||||
|
# Iterate through items
|
||||||
|
key = 0;
|
||||||
|
for item in items:
|
||||||
|
track = get_track_entry(item,params)
|
||||||
|
listing.append(track)
|
||||||
|
key +=1
|
||||||
|
|
||||||
|
# Root menu
|
||||||
|
link_root = navigate_root()
|
||||||
|
listing.append(link_root)
|
||||||
|
|
||||||
|
# Pagination if we've not reached the end of the lsit
|
||||||
|
# if type(items) != type(True): TO FIX
|
||||||
|
#link_next = navigate_next(params)
|
||||||
|
#listing.append(link_next)
|
||||||
|
|
||||||
|
# Sort methods - List of integer constants representing virtual folder sort methods. - see SortFileItem.h from Kodi core
|
||||||
|
sortable_by = (
|
||||||
|
0, #SORT_METHOD_NONE
|
||||||
|
1, #SORT_METHOD_LABEL
|
||||||
|
#3, #SORT_METHOD_DATE
|
||||||
|
7, #SORT_METHOD_TRACKNUM
|
||||||
|
11, #SORT_METHOD_ARTIST
|
||||||
|
#14,#SORT_METHOD_ALBUM
|
||||||
|
18, #SORT_METHOD_YEAR
|
||||||
|
#21 #SORT_METHOD_DATEADDED
|
||||||
|
40 #SORT_METHOD_UNSORTED
|
||||||
|
)
|
||||||
|
|
||||||
|
return plugin.create_listing(
|
||||||
|
listing,
|
||||||
|
#succeeded = True, #if False Kodi won’t open a new listing and stays on the current level.
|
||||||
|
#update_listing = False, #if True, Kodi won’t open a sub-listing but refresh the current one.
|
||||||
|
#cache_to_disk = True, #cache this view to disk.
|
||||||
|
sort_methods = sortable_by, #he list of integer constants representing virtual folder sort methods.
|
||||||
|
#view_mode = None, #a numeric code for a skin view mode. View mode codes are different in different skins except for 50 (basic listing).
|
||||||
|
content = 'songs' #string - current plugin content, e.g. ‘movies’ or ‘episodes’.
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_track_entry(item,params):
|
||||||
|
|
||||||
|
# name
|
||||||
|
if 'hide_artist' in params:
|
||||||
|
title = item.get('title', '<Unknown>')
|
||||||
|
else:
|
||||||
|
title = '%s - %s' % (
|
||||||
|
item.get('artist', '<Unknown>'),
|
||||||
|
item.get('title', '<Unknown>')
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'label': title,
|
||||||
|
'thumb': item.get('coverArt'),
|
||||||
|
'fanart': item.get('coverArt'),
|
||||||
|
'url': plugin.get_url(
|
||||||
|
action='play_track',
|
||||||
|
id=item.get('id')
|
||||||
|
),
|
||||||
|
'is_playable': True,
|
||||||
|
'mime': item.get("contentType"),
|
||||||
|
'info': {'music': { #http://romanvm.github.io/Kodistubs/_autosummary/xbmcgui.html#xbmcgui.ListItem.setInfo
|
||||||
|
'title': item.get('title'),
|
||||||
|
'album': item.get('album'),
|
||||||
|
'artist': item.get('artist'),
|
||||||
|
'tracknumber': item.get('tracknumber'),
|
||||||
|
'year': item.get('year'),
|
||||||
|
'genre': item.get('genre'),
|
||||||
|
'size': item.get('size'),
|
||||||
|
'duration': item.get('duration')
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def play_track(params):
|
||||||
|
|
||||||
|
id = params['id']
|
||||||
|
plugin.log('play_track #' + id);
|
||||||
|
|
||||||
|
# get connection
|
||||||
|
connection = get_connection()
|
||||||
|
|
||||||
|
url = connection.streamUrl(sid=id,
|
||||||
|
maxBitRate=Addon().get_setting('bitrate_streaming'),
|
||||||
|
tformat=Addon().get_setting('transcode_format_streaming')
|
||||||
|
)
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
|
def navigate_next(params):
|
||||||
|
|
||||||
|
page = int(params['page'])
|
||||||
|
page += 1
|
||||||
|
|
||||||
|
title = "Next page (%d)" % (page)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'label': title,
|
||||||
|
'url': plugin.get_url(
|
||||||
|
action= params['action'],
|
||||||
|
page= page,
|
||||||
|
query_args= params['query_args']
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def navigate_root():
|
||||||
|
return {
|
||||||
|
'label': "Back to menu",
|
||||||
|
'url': plugin.get_url(action='root')
|
||||||
|
}
|
||||||
|
|
||||||
|
#converts a date string from eg. '2012-04-17T19:53:44' to eg. '17.04.2012'
|
||||||
|
def convert_date_from_iso8601(iso8601):
|
||||||
|
date_obj = dateutil.parser.parse(iso8601)
|
||||||
|
return date_obj.strftime('%d.%m.%Y')
|
||||||
|
|
||||||
|
|
||||||
|
@plugin.cached(cache_minutes) #if cache is enabled, cache data for the following function
|
||||||
|
def list_playlists(params):
|
||||||
|
|
||||||
|
# get connection
|
||||||
|
connection = get_connection()
|
||||||
|
|
||||||
|
listing = []
|
||||||
|
|
||||||
|
# Get items
|
||||||
|
items = connection.walk_playlists()
|
||||||
|
|
||||||
|
# Iterate through items
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
|
||||||
|
listing.append({
|
||||||
|
'label': item['name'],
|
||||||
|
'thumb': connection.getCoverArtUrl(item.get('id')),
|
||||||
|
'fanart': connection.getCoverArtUrl(item.get('id')),
|
||||||
|
'url': plugin.get_url(action='list_tracks',
|
||||||
|
playlist_id=item.get('id'),
|
||||||
|
|
||||||
|
),
|
||||||
|
'info': {'music': { ##http://romanvm.github.io/Kodistubs/_autosummary/xbmcgui.html#xbmcgui.ListItem.setInfo
|
||||||
|
'title': item.get('name'),
|
||||||
|
'count': item.get('songCount'),
|
||||||
|
'duration': item.get('duration'),
|
||||||
|
'date': convert_date_from_iso8601(item.get('created'))
|
||||||
|
}}
|
||||||
|
})
|
||||||
|
return plugin.create_listing(
|
||||||
|
listing,
|
||||||
|
#succeeded = True, #if False Kodi won’t open a new listing and stays on the current level.
|
||||||
|
#update_listing = False, #if True, Kodi won’t open a sub-listing but refresh the current one.
|
||||||
|
#cache_to_disk = True, #cache this view to disk.
|
||||||
|
#sort_methods = None, #he list of integer constants representing virtual folder sort methods.
|
||||||
|
#view_mode = None, #a numeric code for a skin view mode. View mode codes are different in different skins except for 50 (basic listing).
|
||||||
|
#content = None #string - current plugin content, e.g. ‘movies’ or ‘episodes’.
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Start plugin from within Kodi.
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Map actions
|
||||||
|
# Note that we map callable objects without brackets ()
|
||||||
|
plugin.actions['root'] = menu_root # - optional if we have a function named 'root'
|
||||||
|
plugin.actions['menu_albums'] = menu_albums
|
||||||
|
plugin.actions['list_artists'] = list_artists
|
||||||
|
plugin.actions['list_artist_albums'] = list_artist_albums
|
||||||
|
plugin.actions['list_albums'] = list_albums
|
||||||
|
plugin.actions['menu_tracks'] = menu_tracks
|
||||||
|
plugin.actions['list_tracks'] = list_tracks
|
||||||
|
plugin.actions['play_track'] = play_track
|
||||||
|
plugin.actions['list_playlists'] = list_playlists
|
||||||
|
plugin.run()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,27 @@
|
||||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
<settings>
|
<settings>
|
||||||
<category label="Server">
|
<!-- GENERAL -->
|
||||||
|
<category label="General">
|
||||||
|
<setting label="Server" type="lsep" />
|
||||||
<setting id="subsonic_url" type="text" label="Server URL" default="http://demo.subsonic.org"/>
|
<setting id="subsonic_url" type="text" label="Server URL" default="http://demo.subsonic.org"/>
|
||||||
<setting id="username" type="text" label="Username" default="guest3"/>
|
<setting id="username" type="text" label="Username" default="guest3"/>
|
||||||
<setting id="password" type="text" option="hidden" label="Password" default="guest"/>
|
<setting id="password" type="text" option="hidden" label="Password" default="guest"/>
|
||||||
<setting id="apiversion" type="labelenum" label="API version" values="1.11.0|1.12.0|1.13.0|1.14.0" default="1.13.0"/>
|
<setting label="Display" type="lsep" />
|
||||||
<setting id="insecure" type="bool" label="Allow self signed certificates" default="false" />
|
|
||||||
<setting id="legacyauth" type="bool" label="Use pre-1.13.0 API version authentication" default="false"/>
|
|
||||||
<setting type="sep" />
|
|
||||||
<setting id="albums_per_page" type="labelenum" label="Albums per page" default="50" values="10|25|50|100|250|500"/>
|
<setting id="albums_per_page" type="labelenum" label="Albums per page" default="50" values="10|25|50|100|250|500"/>
|
||||||
<setting id="tracks_per_page" type="labelenum" label="Tracks per page (ignored in albums & playlists)" default="100" values="10|25|50|100|250|500"/>
|
<setting id="tracks_per_page" type="labelenum" label="Tracks per page (ignored in albums & playlists)" default="100" values="10|25|50|100|250|500"/>
|
||||||
<setting type="sep" />
|
<setting label="Streaming" type="lsep" />
|
||||||
<setting id="transcode_format" type="labelenum" label="Transcode format" values="mp3|raw|flv|ogg"/>
|
<setting id="transcode_format_streaming" type="labelenum" label="Transcode format" values="mp3|raw|flv|ogg"/>
|
||||||
<setting id="bitrate" type="labelenum" label="Bitrate" values="320|256|224|192|160|128|112|96|80|64|56|48|40|32"/>
|
<setting id="bitrate_streaming" type="labelenum" label="Bitrate" values="320|256|224|192|160|128|112|96|80|64|56|48|40|32"/>
|
||||||
|
</category>
|
||||||
|
|
||||||
|
<!-- ADVANCED -->
|
||||||
|
<category label="Advanced Settings">
|
||||||
|
<setting label="Server" type="lsep" />
|
||||||
|
<setting id="apiversion" type="labelenum" label="API version" values="1.11.0|1.12.0|1.13.0|1.14.0" default="1.13.0"/>
|
||||||
|
<setting id="insecure" type="bool" label="Allow self signed certificates" default="false" />
|
||||||
|
<setting label="Cache" type="lsep" />
|
||||||
|
<setting id="cache_minutes" type="labelenum" label="Cache datas time (in minutes)" default="30" values="1|15|30|60|120|180|720|1440"/>
|
||||||
|
<setting label="Debug" type="lsep" />
|
||||||
|
<setting label="Enable Debug mode" type="bool" id="debug" default="false" />
|
||||||
</category>
|
</category>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
Loading…
Reference in New Issue