6 Commits

Author SHA1 Message Date
f6792e02aa Include feature from GioF71 [Feature] Search Album 2023-02-03 09:30:07 +11:00
2ae13c655e Include feature from GioF71 [Feature] Search Album 2023-02-03 09:29:24 +11:00
fc49f29fae Include feature from GioF71 [Feature] Search Album 2023-02-03 09:28:35 +11:00
670bf0107b Update main.py
Include feature from GioF71 [Feature] Search Album
2023-02-03 09:26:33 +11:00
18f736d60d Upgrade - Match to Matrix version 2022-09-18 17:35:49 +10:00
f62f2b2236 Update installation details in readme 2021-09-04 10:24:12 +10:00
10 changed files with 62 additions and 109 deletions

View File

@ -1,5 +1,8 @@
# Changelog
## v2.1.0
Backport v3.0.2 to Kodi Leia for testing
## v3.0.2
Released 29th September 2021 (by warwickh)
* Removed dependency on future and dateutil

View File

@ -27,6 +27,7 @@ From GitHub
* Enable unknown sources and install from zip in Kodi
or
* Navigate to your `.kodi/addons/` folder
* Clone this repository: `git clone https://github.com/warwickh/plugin.audio.subsonic.git`
* (Re)start Kodi.

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.audio.subsonic" name="Subsonic" version="3.0.2" provider-name="BasilFX,warwickh">
<addon id="plugin.audio.subsonic" name="Subsonic" version="2.1.0" provider-name="BasilFX,warwickh">
<requires>
<import addon="xbmc.python" version="3.0.0"/>
<import addon="xbmc.python" version="2.7.0"/>
</requires>
<extension point="xbmc.python.pluginsource" library="main.py">
<provides>audio</provides>

View File

@ -18,11 +18,10 @@ along with py-sonic. If not, see <http://www.gnu.org/licenses/>
from libsonic.errors import *
from netrc import netrc
from hashlib import md5
import urllib.request
import urllib.error
import urllib.parse
from http import client as http_client
from urllib.parse import urlencode
import urllib2
import httplib
import urlparse
from urllib import urlencode
from io import StringIO
import json
@ -37,7 +36,7 @@ API_VERSION = '1.16.1'
logger = logging.getLogger(__name__)
class HTTPSConnectionChain(http_client.HTTPSConnection):
class HTTPSConnectionChain(httplib.HTTPSConnection):
def _create_sock(self):
sock = socket.create_connection((self.host, self.port), self.timeout)
if self._tunnel_host:
@ -53,14 +52,14 @@ class HTTPSConnectionChain(http_client.HTTPSConnection):
except:
sock.close()
class HTTPSHandlerChain(urllib.request.HTTPSHandler):
class HTTPSHandlerChain(urllib2.HTTPSHandler):
def https_open(self, req):
return self.do_open(HTTPSConnectionChain, req, context=self._context)
# install opener
urllib.request.install_opener(urllib.request.build_opener(HTTPSHandlerChain()))
urllib2.install_opener(urllib2.build_opener(HTTPSHandlerChain()))
class PysHTTPRedirectHandler(urllib.request.HTTPRedirectHandler):
class PysHTTPRedirectHandler(urllib2.HTTPRedirectHandler):
"""
This class is used to override the default behavior of the
HTTPRedirectHandler, which does *not* redirect POST data
@ -76,7 +75,7 @@ class PysHTTPRedirectHandler(urllib.request.HTTPRedirectHandler):
data = None
if req.data:
data = req.data
return urllib.request.Request(newurl,
return urllib2.Request(newurl,
data=data,
headers=newheaders,
origin_req_host=req.origin_req_host,
@ -161,7 +160,7 @@ class Connection(object):
if len(self._hostname.split('/'))>1:
print(len(self._hostname.split('/')))
xbmc.log("Got a folder %s"%(self._hostname.split('/')[1]),xbmc.LOGDEBUG)
parts = urllib.parse.urlparse(self._baseUrl)
parts = urlparse.urlparse(self._baseUrl)
self._baseUrl = "%s://%s" % (parts.scheme, parts.hostname)
self._hostname = parts.hostname
self._serverPath = parts.path.strip('/') + '/rest'
@ -240,7 +239,8 @@ class Connection(object):
viewName = '%s.view' % methodName
req = self._getRequest(viewName)
xbmc.log("Pinging %s"%str(req.full_url),xbmc.LOGDEBUG)
xbmc.log("Pinging %s"%str(req.get_full_url()),xbmc.LOGDEBUG)
#res = self._doInfoReq(req)
try:
res = self._doInfoReq(req)
except Exception as e:
@ -906,8 +906,8 @@ class Connection(object):
'converted': converted})
req = self._getRequest(viewName, q)
#xbmc.log("Requesting %s"%str(req.full_url),xbmc.LOGDEBUG)
return_url = req.full_url
#xbmc.log("Requesting %s"%str(req.get_full_url()),xbmc.LOGDEBUG)
return_url = req.get_full_url()
if self._insecure:
return_url += '&verifypeer=false'
xbmc.log("Request is insecure %s"%return_url,level=xbmc.LOGDEBUG)
@ -955,8 +955,8 @@ class Connection(object):
q = self._getQueryDict({'id': aid, 'size': size})
req = self._getRequest(viewName, q)
#xbmc.log("Requesting %s"%str(req.full_url),xbmc.LOGDEBUG)
return_url = req.full_url
#xbmc.log("Requesting %s"%str(req.get_full_url()),xbmc.LOGDEBUG)
return_url = req.get_full_url()
if self._insecure:
return_url += '&verifypeer=false'
xbmc.log("Request is insecure %s"%return_url,level=xbmc.LOGDEBUG)
@ -2007,7 +2007,7 @@ class Connection(object):
q['musicFolderId'] = musicFolderId
req = self._getRequest(viewName, q)
xbmc.log("Requesting %s"%str(req.full_url),xbmc.LOGDEBUG)
xbmc.log("Requesting %s"%str(req.get_full_url()),xbmc.LOGDEBUG)
res = self._doInfoReq(req)
self._checkStatus(res)
return res
@ -2765,7 +2765,7 @@ class Connection(object):
url = '%s:%d/%s/%s?%s' % (self._baseUrl, self._port,
self._separateServerPath(), viewName, methodName)
req = urllib.request.Request(url)
req = urllib2.Request(url)
res = self._opener.open(req)
res_msg = res.msg.lower()
return res_msg == 'ok'
@ -2779,7 +2779,7 @@ class Connection(object):
if sys.version_info[:3] >= (2, 7, 9) and self._insecure:
https_chain = HTTPSHandlerChain(
context=ssl._create_unverified_context())
opener = urllib.request.build_opener(
opener = urllib2.build_opener(
PysHTTPRedirectHandler,
https_chain,
)
@ -2821,11 +2821,11 @@ class Connection(object):
viewName)
#xbmc.log("Standard URL %s"%url,level=xbmc.LOGDEBUG)
#xbmc.log("Qdict %s"%str(qdict),level=xbmc.LOGDEBUG)
req = urllib.request.Request(url, urlencode(qdict).encode('utf-8'))
req = urllib2.Request(url, urlencode(qdict).encode('utf-8'))
if(self._useGET or ('getCoverArt' in viewName) or ('stream' in viewName)):
url += '?%s' % urlencode(qdict)
#xbmc.log("UseGET URL %s"%(url),xbmc.LOGDEBUG)
req = urllib.request.Request(url)
req = urllib2.Request(url)
return req
def _getRequestWithList(self, viewName, listName, alist, query={}):
@ -2841,7 +2841,7 @@ class Connection(object):
data.write(urlencode(qdict))
for i in alist:
data.write('&%s' % urlencode({listName: i}))
req = urllib.request.Request(url, data.getvalue().encode('utf-8'))
req = urllib2.Request(url, data.getvalue().encode('utf-8'))
if self._useGET:
url += '?%s' % data.getvalue()
@ -2868,7 +2868,7 @@ class Connection(object):
for k, l in listMap.items():
for i in l:
data.write('&%s' % urlencode({k: i}))
req = urllib.request.Request(url, data.getvalue().encode('utf-8'))
req = urllib2.Request(url, data.getvalue().encode('utf-8'))
if self._useGET:
url += '?%s' % data.getvalue()
@ -2929,7 +2929,7 @@ class Connection(object):
"""
separate REST portion of URL from base server path.
"""
return urllib.parse.splithost(self._serverPath)[1].split('/')[0]
return urlparse.splithost(self._serverPath)[1].split('/')[0]
def _fixLastModified(self, data):
"""

View File

@ -19,7 +19,7 @@ import inspect
import time
import hashlib
import pickle
from collections.abc import MutableMapping
from collections import MutableMapping
from collections import namedtuple
from copy import deepcopy
from functools import wraps
@ -27,7 +27,9 @@ from shutil import copyfile
from contextlib import contextmanager
from pprint import pformat
from platform import uname
from urllib.parse import urlencode, quote_plus, urlparse, unquote_plus, parse_qs
#from urllib.parse import urlencode, quote_plus, urlparse, unquote_plus, parse_qs
from urlparse import parse_qs
from urllib import urlencode
import xbmcaddon
import xbmc
import xbmcgui
@ -36,7 +38,7 @@ import xbmcvfs
__all__ = ['SimplePluginError', 'Storage', 'MemStorage', 'Addon', 'Plugin',
'RoutedPlugin', 'Params', 'log_exception', 'translate_path']
getargspec = inspect.getfullargspec
getargspec = inspect.getargspec
Route = namedtuple('Route', ['pattern', 'func'])
@ -1086,7 +1088,6 @@ class Plugin(Addon):
return action_callable(self._params)
class RoutedPlugin(Plugin):
"""
Plugin class that implements "pretty URL" routing similar to Flask and Bottle

92
main.py
View File

@ -12,11 +12,11 @@ import time
import hashlib
import random
from datetime import datetime
from collections.abc import MutableMapping
from collections 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")))
sys.path.append(xbmc.translatePath(os.path.join(xbmcaddon.Addon("plugin.audio.subsonic").getAddonInfo("path"), "lib")))
import libsonic
@ -61,6 +61,7 @@ def get_connection():
if connected==False:
popup('Connection error')
plugin.log('Connection error')
return False
return connection
@ -111,11 +112,6 @@ def root(params):
'name': Addon().get_localized_string(30045),
'callback': 'search_album',
'thumb': None
},
'radio': {
'name': Addon().get_localized_string(30046),
'callback': 'browse_radio',
'thumb': None
},
}
@ -248,31 +244,6 @@ def menu_tracks(params):
listing,
))
@plugin.action()
def browse_radio(params):
# get connection
connection = get_connection()
if connection==False:
return
listing = []
# Get items
items = walk_radio()
# Iterate through items
for item in items:
print(item)
entry = get_entry_radio(item,params)
listing.append(entry)
add_directory_items(create_listing(
listing,
sort_methods = get_sort_methods('playlists',params), #he list of integer constants representing virtual folder sort methods.
))
@plugin.action()
def browse_folders(params):
# get connection
@ -656,7 +627,7 @@ def search_album(params):
# so artistCount and songCount is set to 0
items = connection.search2(query=d, artistCount=0, songCount=0)
# Iterate through items
album_list = items.get('searchResult2').get('album')
if album_list:
for item in items.get('searchResult2').get('album'):
@ -910,17 +881,6 @@ def get_entry_album(item, params):
return entry
def get_entry_radio(item,params):
menu_id = params.get('menu_id')
entry = {
'label': item.get('name'),
'url': item.get('streamUrl'),
'is_playable': True
}
return entry
def get_entry_track(item,params):
menu_id = params.get('menu_id')
image = connection.getCoverArtUrl(item.get('coverArt'))
@ -1453,7 +1413,8 @@ def walk_index(folder_id=None):
plugin.log("artist: %s"%artist)
yield artist
except KeyError:
yield from ()
for emp in ():
yield emp
def walk_playlists():
"""
@ -1464,7 +1425,8 @@ def walk_playlists():
for child in response["playlists"]["playlist"]:
yield child
except KeyError:
yield from ()
for emp in ():
yield emp
def walk_playlist(playlist_id):
"""
@ -1475,7 +1437,8 @@ def walk_playlist(playlist_id):
for child in response["playlist"]["entry"]:
yield child
except KeyError:
yield from ()
for emp in ():
yield emp
def walk_folders():
response = connection.getMusicFolders()
@ -1483,7 +1446,8 @@ def walk_folders():
for child in response["musicFolders"]["musicFolder"]:
yield child
except KeyError:
yield from ()
for emp in ():
yield emp
def walk_directory(directory_id, merge_artist = True):
"""
@ -1499,7 +1463,8 @@ def walk_directory(directory_id, merge_artist = True):
else:
yield child
except KeyError:
yield from ()
for emp in ():
yield emp
def walk_artist(artist_id):
"""
@ -1511,7 +1476,8 @@ def walk_artist(artist_id):
for child in response["artist"]["album"]:
yield child
except KeyError:
yield from ()
for emp in ():
yield emp
def walk_artists():
"""
@ -1524,7 +1490,8 @@ def walk_artists():
for artist in index["artist"]:
yield artist
except KeyError:
yield from ()
for emp in ():
yield emp
def walk_genres():
"""
@ -1536,7 +1503,8 @@ def walk_genres():
for genre in response["genres"]["genre"]:
yield genre
except KeyError:
yield from ()
for emp in ():
yield emp
def walk_albums(ltype, size=None, fromYear=None,toYear=None, genre=None, offset=None):
"""
@ -1570,7 +1538,8 @@ def walk_album(album_id):
for song in response["album"]["song"]:
yield song
except KeyError:
yield from ()
for emp in ():
yield emp
def walk_tracks_random(size=None, genre=None, fromYear=None,toYear=None):
"""
@ -1582,18 +1551,8 @@ def walk_tracks_random(size=None, genre=None, fromYear=None,toYear=None):
for song in response["randomSongs"]["song"]:
yield song
except KeyError:
yield from ()
def walk_radio():
"""
Request Subsonic's radio stations and iterate over each item.
"""
response = connection.getInternetRadioStations()
try:
for station in response["internetRadioStations"]["internetRadioStation"]:
yield station
except KeyError:
yield from ()
for emp in ():
yield emp
def walk_tracks_starred():
"""
@ -1604,7 +1563,8 @@ def walk_tracks_starred():
for song in response["starred"]["song"]:
yield song
except KeyError:
yield from ()
for emp in ():
yield emp
# Start plugin from within Kodi.
if __name__ == "__main__":

View File

@ -180,7 +180,3 @@ msgstr ""
msgctxt "#30045"
msgid "Search Albums"
msgstr ""
msgctxt "#30046"
msgid "Radio"
msgstr "Radio"

View File

@ -180,7 +180,3 @@ msgstr ""
msgctxt "#30045"
msgid "Search Albums"
msgstr "Rechercher Albums"
msgctxt "#30046"
msgid "Radio"
msgstr "Radio"

View File

@ -179,7 +179,3 @@ msgstr ""
msgctxt "#30045"
msgid "Search Albums"
msgstr "Suche Albums"
msgctxt "#30046"
msgid "Radio"
msgstr "Radio"

View File

@ -4,7 +4,7 @@ import xbmcvfs
import os
import xbmcaddon
# Add the /lib folder to sys
sys.path.append(xbmcvfs.translatePath(os.path.join(xbmcaddon.Addon("plugin.audio.subsonic").getAddonInfo("path"), "lib")))
sys.path.append(xbmc.translatePath(os.path.join(xbmcaddon.Addon("plugin.audio.subsonic").getAddonInfo("path"), "lib")))
import libsonic