@ -1,11 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Module: main
# Author: G.Breant
# Created on: 14 January 2017
# License: GPL v.3 https://www.gnu.org/copyleft/gpl.html
import xbmcvfs
import os
import xbmcaddon
@ -13,15 +8,16 @@ import xbmcplugin
import xbmcgui
import json
import shutil
import time
import dateutil . parser
from datetime import datetime
from collections import MutableMapping , namedtuple
from collections . abc import MutableMapping
from collections import namedtuple
# Add the /lib folder to sys
sys . path . append ( xbmcvfs . translatePath ( os . path . join ( xbmcaddon . Addon ( " plugin.audio.subsonic " ) . getAddonInfo ( " path " ) , " lib " ) ) )
import libsonic #Removed libsonic_extra
import libsonic
from simpleplugin import Plugin
from simpleplugin import Addon
@ -29,13 +25,10 @@ from simpleplugin import Addon
# Create plugin instance
plugin = Plugin ( )
# initialize_gettext
#_ = plugin.initialize_gettext()
connection = None
cachetime = int ( Addon ( ) . get_setting ( ' cachetime ' ) )
local_starred = set ( { } )
local_starred = set ( { } )
ListContext = namedtuple ( ' ListContext ' , [ ' listing ' , ' succeeded ' , ' update_listing ' , ' cache_to_disk ' , ' sort_methods ' , ' view_mode ' , ' content ' , ' category ' ] )
PlayContext = namedtuple ( ' PlayContext ' , [ ' path ' , ' play_item ' , ' succeeded ' ] )
def popup ( text , time = 5000 , image = None ) :
@ -135,12 +128,7 @@ def root(params):
add_directory_items ( 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’ .
sort_methods = None ,
) )
@plugin.action ( )
@ -177,7 +165,7 @@ def menu_albums(params):
}
}
# Iterate through categorie s
# Iterate through album s
for menu_id in menus :
@ -201,12 +189,6 @@ def menu_albums(params):
add_directory_items ( 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.action ( )
@ -253,12 +235,6 @@ def menu_tracks(params):
add_directory_items ( 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.action ( )
@ -295,7 +271,6 @@ def browse_folders(params):
add_directory_items ( create_listing ( listing ) )
@plugin.action ( )
#@plugin.cached(cachetime) # cache (in minutes)
def browse_indexes ( params ) :
# get connection
connection = get_connection ( )
@ -327,7 +302,6 @@ def browse_indexes(params):
) )
@plugin.action ( )
#@plugin.cached(cachetime) # cache (in minutes)
def list_directory ( params ) :
# get connection
connection = get_connection ( )
@ -365,7 +339,6 @@ def list_directory(params):
) )
@plugin.action ( )
#@plugin.cached(cachetime) # cache (in minutes)
def browse_library ( params ) :
"""
List artists from the library (ID3 tags)
@ -400,11 +373,8 @@ def browse_library(params):
add_directory_items ( 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 = get_sort_methods ( ' artists ' , params ) , #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’ .
) )
@ -477,11 +447,8 @@ def list_albums(params):
add_directory_items ( 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 = get_sort_methods ( ' albums ' , params ) ,
#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’ .
) )
@ -557,7 +524,8 @@ def list_tracks(params):
#update stars
if menu_id == ' tracks_starred ' :
ids_list = [ item . get ( ' id ' ) for item in items ]
stars_cache _update( ids_list )
# stars_local _update( ids_list)
cache_refresh ( True )
# Iterate through items
key = 0 ;
@ -575,15 +543,9 @@ def list_tracks(params):
#link_next = navigate_next(params)
#listing.append(link_next)
add_directory_items ( 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 = get_sort_methods ( ' tracks ' , params ) ,
#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’ .
) )
@ -591,8 +553,8 @@ def list_tracks(params):
#run this function every time we get starred items.
#ids can be a single ID or a list
#using a set makes sure that IDs will be unique.
@plugin.action ( )
#@plugin.cached(cachetime) #cache (in minutes)
def list_playlists ( params ) :
# get connection
@ -614,12 +576,7 @@ def list_playlists(params):
add_directory_items ( 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 = get_sort_methods ( ' playlists ' , params ) , #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.action ( )
#@plugin.cached(cachetime) #cache (in minutes)
@ -728,7 +685,8 @@ def star_item(params):
message = Addon ( ) . get_localized_string ( 30032 )
plugin . log ( ' Starred %s # %s ' % ( type , json . dumps ( ids ) ) )
stars_cache _update( ids , unstar )
# stars_local _update(ids, unstar)
cache_refresh ( True )
popup ( message )
@ -801,8 +759,13 @@ def get_entry_playlist(item,params):
#@plugin.cached(cachetime) #cache (in minutes)
def get_entry_artist ( item , params ) :
image = connection . getCoverArtUrl ( item . get ( ' coverArt ' ) )
#artist_info = connection.getArtistInfo(item.get('id')).get('artistInfo')
#artist_bio = artist_info.get('biography')
#xbmc.log("Artist info: %s"%artist_info.get('biography'),xbmc.LOGINFO)
return {
' label ' : get_starred_label ( item . get ( ' id ' ) , item . get ( ' name ' ) ) ,
' label2 ' : " test label " ,
' offscreen ' : True ,
' thumb ' : image ,
' fanart ' : image ,
' url ' : plugin . get_url (
@ -813,7 +776,11 @@ def get_entry_artist(item,params):
' info ' : {
' music ' : { ##http://romanvm.github.io/Kodistubs/_autosummary/xbmcgui.html#xbmcgui.ListItem.setInfo
' count ' : item . get ( ' albumCount ' ) ,
' artist ' : item . get ( ' name ' )
' artist ' : item . get ( ' name ' ) #,
#'title': "testtitle",
#'album': "testalbum",
#'comment': "testcomment"
# 'title': artist_info.get('biography')
}
}
}
@ -861,7 +828,6 @@ def get_entry_album(item, params):
return entry
#@plugin.cached(cachetime) #cache (in minutes)
def get_entry_track ( item , params ) :
menu_id = params . get ( ' menu_id ' )
@ -909,7 +875,6 @@ def get_entry_track(item,params):
return entry
#@plugin.cached(cachetime) #cache (in minutes)
def get_starred_label ( id , label ) :
if is_starred ( id ) :
label = ' [COLOR=FF00FF00] %s [/COLOR] ' % label
@ -917,13 +882,12 @@ def get_starred_label(id,label):
def is_starred ( id ) :
starred = stars_cache_get ( )
id = int ( id )
#id = int(id )
if id in starred :
return True
else :
return False
#@plugin.cached(cachetime) #cache (in minutes)
def get_entry_track_label ( item , hide_artist = False ) :
if hide_artist :
label = item . get ( ' title ' , ' <Unknown> ' )
@ -935,7 +899,6 @@ def get_entry_track_label(item,hide_artist = False):
return get_starred_label ( item . get ( ' id ' ) , label )
#@plugin.cached(cachetime) #cache (in minutes)
def get_entry_album_label ( item , hide_artist = False ) :
if hide_artist :
label = item . get ( ' name ' , ' <Unknown> ' )
@ -944,7 +907,6 @@ def get_entry_album_label(item,hide_artist = False):
item . get ( ' name ' , ' <Unknown> ' ) )
return get_starred_label ( item . get ( ' id ' ) , label )
#@plugin.cached(cachetime) #cache (in minutes)
def get_sort_methods ( type , params ) :
#sort method for list types
#https://github.com/xbmc/xbmc/blob/master/xbmc/SortFileItem.h
@ -1020,48 +982,37 @@ def get_sort_methods(type,params):
return sortable
def stars_cache_update ( ids , remove = False ) :
#get existing cache set
starred = stars_cache_ get ( )
#make sure this==a list
if not isinstance ( ids , list ) :
ids = [ ids ]
#abord if empty
if len ( ids ) == 0 :
def cache_refresh ( forced = False ) :
global local_starred
#cachetime = 5
last_update = 0
with plugin . get_stora ge( ) as storage :
#storage['starred_ids'] = starred
try :
last_update = storage [ ' updated ' ]
except KeyError as e :
plugin . log ( " keyerror, is this a new cache file? " )
if ( time . time ( ) - ( cachetime * 60 ) > last_update ) or forced :
plugin . log ( " Cache expired, updating %s %s %s forced %s " % ( time . time ( ) , cachetime * 60 , last_update , forced ) )
generator = walk_tracks_starred ( )
items = list ( generator )
ids_list = [ item . get ( ' id ' ) for item in items ]
#plugin.log("Retreived from server: %s"%ids_list)
storage [ ' starred_ids ' ] = ids_list
storage [ ' updated ' ] = time . time ( )
plugin . log ( " cache_refresh checking length of load to local %s items " % len ( ids_list ) )
local_starred = ids_list
else :
#plugin.log("Cache fresh %s %s %s forced %s remaining %s"%(time.time(),cachetime*60,last_update, forced, time.time()-(cachetime*60)-last_update))
pass
if ( len ( local_starred ) == 0 ) :
local_starred = storage [ ' starred_ids ' ]
#plugin.log("cache_refresh returning %s items"%len(local_starred))
return
#parse items
for item_id in ids :
item_id = int ( item_id )
if not remove :
starred . add ( item_id )
else :
starred . remove ( item_id )
#store them
with plugin . get_storage ( ) as storage :
storage [ ' starred_ids ' ] = starred
plugin . log ( ' stars_cache_update: ' )
plugin . log ( starred )
def stars_cache_get ( ) : #Retrieving stars from cache is too slow, so load to local variable
def stars_cache_get ( ) :
global local_starred
plugin . log ( len ( local_starred ) )
if ( len ( local_starred ) > 0 ) :
plugin . log ( ' stars already loaded: ' )
plugin . log ( local_starred )
return ( local_starred )
else :
with plugin . get_storage ( ) as storage :
local_starred = storage . get ( ' starred_ids ' , set ( ) )
plugin . log ( ' stars_cache_get: ' )
plugin . log ( local_starred )
cache_refresh ( )
return local_starred
def navigate_next ( params ) :
@ -1270,11 +1221,9 @@ def download_album(id):
download_tracks ( ids )
#@plugin.cached(cachetime) #cache (in minutes)
def create_listing ( listing , succeeded = True , update_listing = False , cache_to_disk = False , sort_methods = None , view_mode = None , content = None , category = None ) :
return ListContext ( listing , succeeded , update_listing , cache_to_disk , sort_methods , view_mode , content , category )
def resolve_url ( path = ' ' , play_item = None , succeeded = True ) :
"""
Create and return a context dict to resolve a playable URL
@ -1293,7 +1242,6 @@ def resolve_url(path='', play_item=None, succeeded=True):
"""
return PlayContext ( path , play_item , succeeded )
#@plugin.cached(cachetime) #cache (in minutes)
def create_list_item ( item ) :
"""
Create an :class:`xbmcgui.ListItem` instance from an item dict
@ -1358,7 +1306,6 @@ def _set_resolved_url(context):
xbmcplugin . setResolvedUrl ( plugin . handle , context . succeeded , list_item )
#@plugin.cached(cachetime) #cache (in minutes)
def add_directory_items ( context ) :
plugin . log_debug ( ' Creating listing from {0} ' . format ( str ( context ) ) )
if context . category is not None :
@ -1403,38 +1350,43 @@ def walk_index(folder_id=None):
"""
Request Subsonic ' s index and iterate each item.
"""
response = connection . getIndexes ( folder_id )
try :
for index in response [ " indexes " ] [ " index " ] :
for artist in index [ " artist " ] :
yield artist
except KeyError :
yield from ( )
def walk_playlists ( ) :
"""
Request Subsonic ' s playlists and iterate over each item.
"""
response = connection . getPlaylists ( )
try :
for child in response [ " playlists " ] [ " playlist " ] :
yield child
except KeyError :
yield from ( )
def walk_playlist ( playlist_id ) :
"""
Request Subsonic ' s playlist items and iterate over each item.
"""
response = connection . getPlaylist ( playlist_id )
try :
for child in response [ " playlist " ] [ " entry " ] :
yield child
except KeyError :
yield from ( )
def walk_folders ( ) :
response = connection . getMusicFolders ( )
try :
for child in response [ " musicFolders " ] [ " musicFolder " ] :
yield child
except KeyError :
yield from ( )
def walk_directory ( directory_id ) :
"""
@ -1458,32 +1410,36 @@ def walk_artist(artist_id):
"""
response = connection . getArtist ( artist_id )
try :
for child in response [ " artist " ] [ " album " ] :
yield child
except KeyError :
yield from ( )
def walk_artists ( ) :
"""
(ID3 tags)
Request all artists and iterate over each item.
"""
response = connection . getArtists ( )
try :
for index in response [ " artists " ] [ " index " ] :
for artist in index [ " artist " ] :
yield artist
except KeyError :
yield from ( )
def walk_genres ( ) :
"""
(ID3 tags)
Request all genres and iterate over each item.
"""
response = connection . getGenres ( )
try :
for genre in response [ " genres " ] [ " genre " ] :
yield genre
except KeyError :
yield from ( )
def walk_albums ( ltype , size = None , fromYear = None , toYear = None , genre = None , offset = None ) :
"""
@ -1512,34 +1468,35 @@ def walk_album(album_id):
(ID3 tags)
Request an album and iterate over each item.
"""
response = connection . getAlbum ( album_id )
try :
for song in response [ " album " ] [ " song " ] :
yield song
except KeyError :
yield from ( )
def walk_tracks_random ( size = None , genre = None , fromYear = None , toYear = None ) :
"""
Request random songs by genre and/or year and iterate over each song.
"""
response = connection . getRandomSongs (
size = size , genre = genre , fromYear = fromYear , toYear = toYear )
try :
for song in response [ " randomSongs " ] [ " song " ] :
yield song
except KeyError :
yield from ( )
def walk_tracks_starred ( ) :
"""
Request Subsonic ' s starred songs and iterate over each item.
"""
response = connection . getStarred ( )
try :
for song in response [ " starred " ] [ " song " ] :
yield song
except KeyError :
yield from ( )
# Start plugin from within Kodi.
if __name__ == " __main__ " :