Compare commits
6 Commits
v3.0.2
...
leia-compa
| Author | SHA1 | Date | |
|---|---|---|---|
| f6792e02aa | |||
| 2ae13c655e | |||
| fc49f29fae | |||
| 670bf0107b | |||
| 18f736d60d | |||
| f62f2b2236 |
@ -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
|
||||
|
||||
@ -20,13 +20,14 @@ Leia compatible version available in alternate branch
|
||||
|
||||
## Installation
|
||||
From repository
|
||||
[repository.warwickh-0.9.1.zip](https://github.com/warwickh/repository.warwickh/raw/master/matrix/zips/repository.warwickh/repository.warwickh-0.9.1.zip) (Please report any issues)
|
||||
[repository.warwickh](https://github.com/warwickh/repository.warwickh/raw/master/matrix/zips/repository.warwickh) (Please report any issues)
|
||||
|
||||
From GitHub
|
||||
* Click the code button and download
|
||||
* 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.
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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):
|
||||
"""
|
||||
|
||||
@ -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
|
||||
|
||||
91
main.py
91
main.py
@ -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
|
||||
@ -107,6 +108,11 @@ def root(params):
|
||||
'callback': 'search',
|
||||
'thumb': None
|
||||
},
|
||||
'searchalbum': {
|
||||
'name': Addon().get_localized_string(30045),
|
||||
'callback': 'search_album',
|
||||
'thumb': None
|
||||
},
|
||||
}
|
||||
|
||||
# Iterate through categories
|
||||
@ -578,27 +584,61 @@ def search(params):
|
||||
|
||||
dialog = xbmcgui.Dialog()
|
||||
d = dialog.input(Addon().get_localized_string(30039), type=xbmcgui.INPUT_ALPHANUM)
|
||||
if not d:
|
||||
d = " "
|
||||
|
||||
listing = []
|
||||
|
||||
if d:
|
||||
# get connection
|
||||
connection = get_connection()
|
||||
|
||||
if connection == False:
|
||||
return
|
||||
|
||||
listing = []
|
||||
|
||||
# Get items
|
||||
items = connection.search2(query=d)
|
||||
# Iterate through items
|
||||
for item in items.get('searchResult2').get('song'):
|
||||
songs = items.get('searchResult2').get('song')
|
||||
if songs:
|
||||
for item in songs:
|
||||
entry = get_entry_track( item, params)
|
||||
listing.append(entry)
|
||||
|
||||
if len(listing) == 1:
|
||||
plugin.log('One single Media Folder found; do return listing from browse_indexes()...')
|
||||
if len(listing) == 0:
|
||||
plugin.log('No songs found; do return listing from browse_indexes()...')
|
||||
return browse_indexes(params)
|
||||
else:
|
||||
add_directory_items(create_listing(listing))
|
||||
|
||||
|
||||
@plugin.action()
|
||||
def search_album(params):
|
||||
|
||||
dialog = xbmcgui.Dialog()
|
||||
d = dialog.input(Addon().get_localized_string(30045), type=xbmcgui.INPUT_ALPHANUM)
|
||||
|
||||
listing = []
|
||||
|
||||
if d:
|
||||
# get connection
|
||||
connection = get_connection()
|
||||
if connection==False:
|
||||
return
|
||||
# Get items, we are only looking for albums here
|
||||
# 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'):
|
||||
entry = get_entry_album( item, params)
|
||||
listing.append(entry)
|
||||
|
||||
# I believe it is ok to return an empty listing if
|
||||
# the search gave no result
|
||||
# maybe inform the user?
|
||||
if len(listing) == 0:
|
||||
plugin.log('No albums found; do return listing from browse_indexes()...')
|
||||
return browse_indexes(params)
|
||||
else:
|
||||
add_directory_items(create_listing(listing))
|
||||
@ -1373,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():
|
||||
"""
|
||||
@ -1384,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):
|
||||
"""
|
||||
@ -1395,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()
|
||||
@ -1403,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):
|
||||
"""
|
||||
@ -1419,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):
|
||||
"""
|
||||
@ -1431,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():
|
||||
"""
|
||||
@ -1444,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():
|
||||
"""
|
||||
@ -1456,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):
|
||||
"""
|
||||
@ -1490,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):
|
||||
"""
|
||||
@ -1502,7 +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 ()
|
||||
for emp in ():
|
||||
yield emp
|
||||
|
||||
def walk_tracks_starred():
|
||||
"""
|
||||
@ -1513,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__":
|
||||
|
||||
@ -154,7 +154,7 @@ msgid "Browse"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30039"
|
||||
msgid "Search"
|
||||
msgid "Search Songs"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30040"
|
||||
@ -176,3 +176,7 @@ msgstr ""
|
||||
msgctxt "#30044"
|
||||
msgid "Scrobble to Last.FM"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30045"
|
||||
msgid "Search Albums"
|
||||
msgstr ""
|
||||
|
||||
@ -153,8 +153,8 @@ msgid "Browse"
|
||||
msgstr "Parcourir"
|
||||
|
||||
msgctxt "#30039"
|
||||
msgid "Search"
|
||||
msgstr "Rechercher"
|
||||
msgid "Search Songs"
|
||||
msgstr "Rechercher Chansons"
|
||||
|
||||
|
||||
msgctxt "#30040"
|
||||
@ -176,3 +176,7 @@ msgstr ""
|
||||
msgctxt "#30044"
|
||||
msgid "Scrobble to Last.FM"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30045"
|
||||
msgid "Search Albums"
|
||||
msgstr "Rechercher Albums"
|
||||
|
||||
@ -153,8 +153,8 @@ msgid "Browse"
|
||||
msgstr "Durchsuchen"
|
||||
|
||||
msgctxt "#30039"
|
||||
msgid "Search"
|
||||
msgstr "Suche"
|
||||
msgid "Search Songs"
|
||||
msgstr "Suche Lieder"
|
||||
|
||||
msgctxt "#30040"
|
||||
msgid "useGET"
|
||||
@ -175,3 +175,7 @@ msgstr ""
|
||||
msgctxt "#30044"
|
||||
msgid "Scrobble to Last.FM"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30045"
|
||||
msgid "Search Albums"
|
||||
msgstr "Suche Albums"
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user