#!/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()