Kodi.plugin.audio.subsonic/main.py

614 lines
20 KiB
Python
Raw Normal View History

2016-10-02 20:10:40 +02:00
#!/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 wont open a new listing and stays on the current level.
#update_listing = False, #if True, Kodi wont 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 wont open a new listing and stays on the current level.
#update_listing = False, #if True, Kodi wont 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 wont open a new listing and stays on the current level.
#update_listing = False, #if True, Kodi wont 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 wont open a new listing and stays on the current level.
#update_listing = False, #if True, Kodi wont 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 wont open a new listing and stays on the current level.
#update_listing = False, #if True, Kodi wont 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 wont open a new listing and stays on the current level.
#update_listing = False, #if True, Kodi wont 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 wont open a new listing and stays on the current level.
#update_listing = False, #if True, Kodi wont 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 wont open a new listing and stays on the current level.
#update_listing = False, #if True, Kodi wont 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()