diff --git a/CHANGELOG.md b/CHANGELOG.md index 97f3e6b..8716ed6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## v2.0.2 +* Use SimplePlugin framework (http://romanvm.github.io/script.module.simpleplugin/index.html) + ## v2.0.1 * New setting 'albums_per_page' * New menu structure diff --git a/README.md b/README.md index 3bbfcd7..e40ce17 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Kodi plugin to stream music from Subsonic. ## Installation * 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. ## License diff --git a/addon.py b/addon.py deleted file mode 100644 index 4074db4..0000000 --- a/addon.py +++ /dev/null @@ -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", ""), - track.get("title", "")) - else: - title = track.get("title", "") - - # 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", ""), - album.get("name", "")) - else: - title = album.get("name", "") - - # 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() diff --git a/addon.xml b/addon.xml index 03d9bae..73b503f 100644 --- a/addon.xml +++ b/addon.xml @@ -1,11 +1,14 @@ - - - - - - audio - + + + + + + + + + audio + Subsonic music addon for Kodi. Subsonic music addon for Kodi. Stream your tunes directly to Kodi. @@ -15,7 +18,7 @@ MIT http://www.subsonic.org - https://github.com/basilfx/plugin.audio.subsonic + https://github.com/gordielachance/plugin.audio.subsonic diff --git a/main.py b/main.py new file mode 100644 index 0000000..7c15a92 --- /dev/null +++ b/main.py @@ -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', '') + else: + title = '%s - %s' % (item.get('artist', ''), + item.get('name', '')) + + 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', '') + else: + title = '%s - %s' % ( + item.get('artist', ''), + item.get('title', '') + ) + + 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() + + + + + + + + + + diff --git a/resources/settings.xml b/resources/settings.xml index a82c522..d1a5d46 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -1,17 +1,27 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + +