Compare commits
6 Commits
artist-inf
...
leia-compa
| Author | SHA1 | Date | |
|---|---|---|---|
| f6792e02aa | |||
| 2ae13c655e | |||
| fc49f29fae | |||
| 670bf0107b | |||
| 18f736d60d | |||
| f62f2b2236 |
@ -1,9 +1,7 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## v3.0.3
|
## v2.1.0
|
||||||
Released 29th September 2021 (by warwickh)
|
Backport v3.0.2 to Kodi Leia for testing
|
||||||
* Added enhanced data collection and display from musicbrainz and wikipedia
|
|
||||||
* Added aqlite storage of artist information to speed loading
|
|
||||||
|
|
||||||
## v3.0.2
|
## v3.0.2
|
||||||
Released 29th September 2021 (by warwickh)
|
Released 29th September 2021 (by warwickh)
|
||||||
|
|||||||
@ -17,17 +17,17 @@ Leia compatible version available in alternate branch
|
|||||||
* Star songs
|
* Star songs
|
||||||
* Navidrome compatibility added (please report any issues)
|
* Navidrome compatibility added (please report any issues)
|
||||||
* Scrobble to Last.FM
|
* Scrobble to Last.FM
|
||||||
* Enhanced data loading from Musicbrainz and wikipedia (switch on in settings and restart)
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
From repository
|
From repository
|
||||||
[repository.warwickh-0.9.0.zip](https://github.com/warwickh/repository.warwickh/raw/master/matrix/zips/repository.warwickh/repository.warwickh-0.9.0.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
|
From GitHub
|
||||||
* Click the code button and download
|
* Click the code button and download
|
||||||
* Enable unknown sources and install from zip in Kodi
|
* Enable unknown sources and install from zip in Kodi
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
* Navigate to your `.kodi/addons/` folder
|
* Navigate to your `.kodi/addons/` folder
|
||||||
* Clone this repository: `git clone https://github.com/warwickh/plugin.audio.subsonic.git`
|
* Clone this repository: `git clone https://github.com/warwickh/plugin.audio.subsonic.git`
|
||||||
* (Re)start Kodi.
|
* (Re)start Kodi.
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="plugin.audio.subsonic" name="Subsonic" version="3.0.3" provider-name="BasilFX,warwickh">
|
<addon id="plugin.audio.subsonic" name="Subsonic" version="2.1.0" provider-name="BasilFX,warwickh">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="3.0.0"/>
|
<import addon="xbmc.python" version="2.7.0"/>
|
||||||
</requires>
|
</requires>
|
||||||
<extension point="xbmc.python.pluginsource" library="main.py">
|
<extension point="xbmc.python.pluginsource" library="main.py">
|
||||||
<provides>audio</provides>
|
<provides>audio</provides>
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
"""
|
|
||||||
Databse utilities for plugin.audio.subsonic
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .dbutils import *
|
|
||||||
|
|
||||||
__version__ = '0.0.1'
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
import sqlite3 as sql
|
|
||||||
import time
|
|
||||||
import xbmc
|
|
||||||
|
|
||||||
tbl_artist_info_ddl = str('CREATE TABLE artist_info ('
|
|
||||||
'artist_id TEXT NOT NULL PRIMARY KEY,'
|
|
||||||
'artist_name TEXT,'
|
|
||||||
'artist_info TEXT,'
|
|
||||||
'mb_artist_id TEXT,'
|
|
||||||
'image_url TEXT,'
|
|
||||||
'wikidata_url TEXT,'
|
|
||||||
'wikipedia_url TEXT,'
|
|
||||||
'wikipedia_image TEXT,'
|
|
||||||
'wikipedia_extract TEXT,'
|
|
||||||
'last_update TEXT);')
|
|
||||||
|
|
||||||
class SQLiteDatabase(object):
|
|
||||||
def __init__(self, db_filename):
|
|
||||||
print("Init %s"%db_filename)
|
|
||||||
self.db_filename = db_filename
|
|
||||||
self.conn = None
|
|
||||||
|
|
||||||
self.connect()
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
try:
|
|
||||||
xbmc.log("Trying connection to the database %s"%self.db_filename, xbmc.LOGINFO)
|
|
||||||
#print("Trying connection to the database %s"%self.db_filename)
|
|
||||||
self.conn = sql.connect(self.db_filename)
|
|
||||||
cursor = self.conn.cursor()
|
|
||||||
cursor.execute(str('SELECT SQLITE_VERSION()'))
|
|
||||||
xbmc.log("Connection %s was successful %s"%(self.db_filename, cursor.fetchone()[0]), xbmc.LOGINFO)
|
|
||||||
#print("Connection %s was successful %s"%(self.db_filename, cursor.fetchone()[0]))
|
|
||||||
cursor.row_factory = lambda cursor, row: row[0]
|
|
||||||
cursor.execute(str('SELECT name FROM sqlite_master WHERE type=\'table\' ''AND name NOT LIKE \'sqlite_%\''))
|
|
||||||
list_tables = cursor.fetchall()
|
|
||||||
if not list_tables:
|
|
||||||
# If no tables exist create a new one
|
|
||||||
xbmc.log("Creating Subsonic local DB", xbmc.LOGINFO)
|
|
||||||
#print("Creating Subsonic local DB")
|
|
||||||
cursor.execute(tbl_artist_info_ddl)
|
|
||||||
except sql.Error as e:
|
|
||||||
xbmc.log("SQLite error %s"%e.args[0], xbmc.LOGINFO)
|
|
||||||
#print("SQLite error %s"%e.args[0])
|
|
||||||
|
|
||||||
def get_cursor(self):
|
|
||||||
return self.conn.cursor()
|
|
||||||
|
|
||||||
def run_query(self, query, params=None, cursor=None):
|
|
||||||
#print("Processing query %s params %s"%(str(query),str(params)))
|
|
||||||
try:
|
|
||||||
if cursor is None:
|
|
||||||
cursor = self.get_cursor()
|
|
||||||
if params is not None:
|
|
||||||
cursor.execute(query, params)
|
|
||||||
else:
|
|
||||||
cursor.execute(query)
|
|
||||||
#print("%s rows affected"%cursor.rowcount)
|
|
||||||
return cursor
|
|
||||||
except sql.Error as e:
|
|
||||||
print("SQLite error %s"%e.args[0])
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
#print("Error query %s"%str(query))
|
|
||||||
#print("Error query type %s"%type(query))
|
|
||||||
#print("Error params %s"%str(params))
|
|
||||||
#print("Error params type %s"%type(params))
|
|
||||||
|
|
||||||
def get_record_age(self, artist_id):
|
|
||||||
try:
|
|
||||||
last_update = self.get_value(artist_id, 'last_update')
|
|
||||||
record_age = round(time.time())-round(float(last_update[0][0]))
|
|
||||||
return record_age
|
|
||||||
except IndexError:
|
|
||||||
print("No existing record for artist %s" % artist_id)
|
|
||||||
except Exception as e:
|
|
||||||
print("get_record_age failed %s" % e)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_artist_info(self, artist_id):
|
|
||||||
query = 'SELECT * FROM artist_info WHERE artist_id = ?'# %str(artist_id)
|
|
||||||
params = [str(artist_id)]
|
|
||||||
cursor = self.run_query(query, params)
|
|
||||||
return cursor.fetchall()
|
|
||||||
|
|
||||||
def get_all(self):
|
|
||||||
query = 'SELECT * FROM artist_info'
|
|
||||||
#params = [str(artist_id)]
|
|
||||||
cursor = self.run_query(query)#, params)
|
|
||||||
return cursor.fetchall()
|
|
||||||
|
|
||||||
def update_value(self, artist_id, field_name, field_value):
|
|
||||||
success = False
|
|
||||||
query = 'UPDATE artist_info SET %s = ?, last_update = ? WHERE artist_id = ?' % str(field_name)
|
|
||||||
params = [str(field_value), str(time.time()), str(artist_id)]
|
|
||||||
cursor = self.run_query(query, params)
|
|
||||||
if (cursor.rowcount == 0):
|
|
||||||
query = 'INSERT OR IGNORE INTO artist_info (artist_id, %s, last_update) VALUES (?, ?, ?)' % str(field_name)
|
|
||||||
params = [str(artist_id), str(field_value), str(time.time())]
|
|
||||||
cursor = self.run_query(query, params)
|
|
||||||
try:
|
|
||||||
self.conn.commit()
|
|
||||||
success = True
|
|
||||||
except Exception as e:
|
|
||||||
print("Exception %s"%e)
|
|
||||||
pass
|
|
||||||
return success
|
|
||||||
|
|
||||||
def get_value(self, artist_id, field_name):
|
|
||||||
query = 'SELECT %s FROM artist_info WHERE artist_id = ?' % str(field_name)
|
|
||||||
params = [str(artist_id)]
|
|
||||||
cursor = self.run_query(query, params)
|
|
||||||
return cursor.fetchall()
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self.conn:
|
|
||||||
self.conn.close()
|
|
||||||
|
|
||||||
@ -18,10 +18,10 @@ along with py-sonic. If not, see <http://www.gnu.org/licenses/>
|
|||||||
from libsonic.errors import *
|
from libsonic.errors import *
|
||||||
from netrc import netrc
|
from netrc import netrc
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
import urllib.request
|
import urllib2
|
||||||
import urllib.error
|
import httplib
|
||||||
from http import client as http_client
|
import urlparse
|
||||||
from urllib.parse import urlencode
|
from urllib import urlencode
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@ -36,7 +36,7 @@ API_VERSION = '1.16.1'
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class HTTPSConnectionChain(http_client.HTTPSConnection):
|
class HTTPSConnectionChain(httplib.HTTPSConnection):
|
||||||
def _create_sock(self):
|
def _create_sock(self):
|
||||||
sock = socket.create_connection((self.host, self.port), self.timeout)
|
sock = socket.create_connection((self.host, self.port), self.timeout)
|
||||||
if self._tunnel_host:
|
if self._tunnel_host:
|
||||||
@ -52,14 +52,14 @@ class HTTPSConnectionChain(http_client.HTTPSConnection):
|
|||||||
except:
|
except:
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|
||||||
class HTTPSHandlerChain(urllib.request.HTTPSHandler):
|
class HTTPSHandlerChain(urllib2.HTTPSHandler):
|
||||||
def https_open(self, req):
|
def https_open(self, req):
|
||||||
return self.do_open(HTTPSConnectionChain, req, context=self._context)
|
return self.do_open(HTTPSConnectionChain, req, context=self._context)
|
||||||
|
|
||||||
# install opener
|
# 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
|
This class is used to override the default behavior of the
|
||||||
HTTPRedirectHandler, which does *not* redirect POST data
|
HTTPRedirectHandler, which does *not* redirect POST data
|
||||||
@ -75,7 +75,7 @@ class PysHTTPRedirectHandler(urllib.request.HTTPRedirectHandler):
|
|||||||
data = None
|
data = None
|
||||||
if req.data:
|
if req.data:
|
||||||
data = req.data
|
data = req.data
|
||||||
return urllib.request.Request(newurl,
|
return urllib2.Request(newurl,
|
||||||
data=data,
|
data=data,
|
||||||
headers=newheaders,
|
headers=newheaders,
|
||||||
origin_req_host=req.origin_req_host,
|
origin_req_host=req.origin_req_host,
|
||||||
@ -154,8 +154,18 @@ class Connection(object):
|
|||||||
request. This is not recommended as request
|
request. This is not recommended as request
|
||||||
URLs can get very long with some API calls
|
URLs can get very long with some API calls
|
||||||
"""
|
"""
|
||||||
self._baseUrl = baseUrl
|
|
||||||
self._hostname = baseUrl.split('://')[1].strip()
|
self._baseUrl = baseUrl.rstrip('/')
|
||||||
|
self._hostname = self._baseUrl.split('://')[1]
|
||||||
|
if len(self._hostname.split('/'))>1:
|
||||||
|
print(len(self._hostname.split('/')))
|
||||||
|
xbmc.log("Got a folder %s"%(self._hostname.split('/')[1]),xbmc.LOGDEBUG)
|
||||||
|
parts = urlparse.urlparse(self._baseUrl)
|
||||||
|
self._baseUrl = "%s://%s" % (parts.scheme, parts.hostname)
|
||||||
|
self._hostname = parts.hostname
|
||||||
|
self._serverPath = parts.path.strip('/') + '/rest'
|
||||||
|
else:
|
||||||
|
self._serverPath = serverPath.strip('/')
|
||||||
self._username = username
|
self._username = username
|
||||||
self._rawPass = password
|
self._rawPass = password
|
||||||
self._legacyAuth = legacyAuth
|
self._legacyAuth = legacyAuth
|
||||||
@ -172,7 +182,6 @@ class Connection(object):
|
|||||||
self._port = int(port)
|
self._port = int(port)
|
||||||
self._apiVersion = apiVersion
|
self._apiVersion = apiVersion
|
||||||
self._appName = appName
|
self._appName = appName
|
||||||
self._serverPath = serverPath.strip('/')
|
|
||||||
self._insecure = insecure
|
self._insecure = insecure
|
||||||
self._opener = self._getOpener(self._username, self._rawPass)
|
self._opener = self._getOpener(self._username, self._rawPass)
|
||||||
|
|
||||||
@ -228,15 +237,14 @@ class Connection(object):
|
|||||||
"""
|
"""
|
||||||
methodName = 'ping'
|
methodName = 'ping'
|
||||||
viewName = '%s.view' % methodName
|
viewName = '%s.view' % methodName
|
||||||
|
|
||||||
req = self._getRequest(viewName)
|
req = self._getRequest(viewName)
|
||||||
#print("Pinging %s"%str(req.full_url)),level=xbmc.LOGDEBUG)
|
xbmc.log("Pinging %s"%str(req.get_full_url()),xbmc.LOGDEBUG)
|
||||||
#xbmc.log("Pinging %s"%str(req.full_url),level=xbmc.LOGDEBUG)
|
#res = self._doInfoReq(req)
|
||||||
try:
|
try:
|
||||||
res = self._doInfoReq(req)
|
res = self._doInfoReq(req)
|
||||||
#print(res)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
#print("Ping failed %s"%e)
|
print("Ping failed %s"%e)
|
||||||
xbmc.log("Ping failed %s"%e,level=xbmc.LOGDEBUG)
|
|
||||||
return False
|
return False
|
||||||
if res['status'] == 'ok':
|
if res['status'] == 'ok':
|
||||||
return True
|
return True
|
||||||
@ -898,11 +906,11 @@ class Connection(object):
|
|||||||
'converted': converted})
|
'converted': converted})
|
||||||
|
|
||||||
req = self._getRequest(viewName, q)
|
req = self._getRequest(viewName, q)
|
||||||
##xbmc.log("Requesting %s"%str(req.full_url),level=xbmc.LOGDEBUG)
|
#xbmc.log("Requesting %s"%str(req.get_full_url()),xbmc.LOGDEBUG)
|
||||||
return_url = req.full_url
|
return_url = req.get_full_url()
|
||||||
if self._insecure:
|
if self._insecure:
|
||||||
return_url += '|verifypeer=false'
|
return_url += '&verifypeer=false'
|
||||||
#xbmc.log("Request is insecure %s"%return_url,level=xbmc.LOGDEBUG)
|
xbmc.log("Request is insecure %s"%return_url,level=xbmc.LOGDEBUG)
|
||||||
return return_url
|
return return_url
|
||||||
|
|
||||||
|
|
||||||
@ -947,11 +955,11 @@ class Connection(object):
|
|||||||
q = self._getQueryDict({'id': aid, 'size': size})
|
q = self._getQueryDict({'id': aid, 'size': size})
|
||||||
|
|
||||||
req = self._getRequest(viewName, q)
|
req = self._getRequest(viewName, q)
|
||||||
##xbmc.log("Requesting %s"%str(req.full_url),level=xbmc.LOGDEBUG)
|
#xbmc.log("Requesting %s"%str(req.get_full_url()),xbmc.LOGDEBUG)
|
||||||
return_url = req.full_url
|
return_url = req.get_full_url()
|
||||||
if self._insecure:
|
if self._insecure:
|
||||||
return_url += '|verifypeer=false'
|
return_url += '&verifypeer=false'
|
||||||
#xbmc.log("Request is insecure %s"%return_url,level=xbmc.LOGDEBUG)
|
xbmc.log("Request is insecure %s"%return_url,level=xbmc.LOGDEBUG)
|
||||||
return return_url
|
return return_url
|
||||||
|
|
||||||
|
|
||||||
@ -1999,7 +2007,7 @@ class Connection(object):
|
|||||||
q['musicFolderId'] = musicFolderId
|
q['musicFolderId'] = musicFolderId
|
||||||
|
|
||||||
req = self._getRequest(viewName, q)
|
req = self._getRequest(viewName, q)
|
||||||
#xbmc.log("Requesting %s"%str(req.full_url),level=xbmc.LOGDEBUG)
|
xbmc.log("Requesting %s"%str(req.get_full_url()),xbmc.LOGDEBUG)
|
||||||
res = self._doInfoReq(req)
|
res = self._doInfoReq(req)
|
||||||
self._checkStatus(res)
|
self._checkStatus(res)
|
||||||
return res
|
return res
|
||||||
@ -2491,8 +2499,8 @@ class Connection(object):
|
|||||||
|
|
||||||
req = self._getRequest(viewName, q)
|
req = self._getRequest(viewName, q)
|
||||||
res = self._doInfoReq(req)
|
res = self._doInfoReq(req)
|
||||||
#print(req.get_full_url())
|
print(req.get_full_url())
|
||||||
#print(res)
|
print(res)
|
||||||
self._checkStatus(res)
|
self._checkStatus(res)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@ -2757,7 +2765,7 @@ class Connection(object):
|
|||||||
|
|
||||||
url = '%s:%d/%s/%s?%s' % (self._baseUrl, self._port,
|
url = '%s:%d/%s/%s?%s' % (self._baseUrl, self._port,
|
||||||
self._separateServerPath(), viewName, methodName)
|
self._separateServerPath(), viewName, methodName)
|
||||||
req = urllib.request.Request(url)
|
req = urllib2.Request(url)
|
||||||
res = self._opener.open(req)
|
res = self._opener.open(req)
|
||||||
res_msg = res.msg.lower()
|
res_msg = res.msg.lower()
|
||||||
return res_msg == 'ok'
|
return res_msg == 'ok'
|
||||||
@ -2771,7 +2779,7 @@ class Connection(object):
|
|||||||
if sys.version_info[:3] >= (2, 7, 9) and self._insecure:
|
if sys.version_info[:3] >= (2, 7, 9) and self._insecure:
|
||||||
https_chain = HTTPSHandlerChain(
|
https_chain = HTTPSHandlerChain(
|
||||||
context=ssl._create_unverified_context())
|
context=ssl._create_unverified_context())
|
||||||
opener = urllib.request.build_opener(
|
opener = urllib2.build_opener(
|
||||||
PysHTTPRedirectHandler,
|
PysHTTPRedirectHandler,
|
||||||
https_chain,
|
https_chain,
|
||||||
)
|
)
|
||||||
@ -2813,12 +2821,11 @@ class Connection(object):
|
|||||||
viewName)
|
viewName)
|
||||||
#xbmc.log("Standard URL %s"%url,level=xbmc.LOGDEBUG)
|
#xbmc.log("Standard URL %s"%url,level=xbmc.LOGDEBUG)
|
||||||
#xbmc.log("Qdict %s"%str(qdict),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)):
|
if(self._useGET or ('getCoverArt' in viewName) or ('stream' in viewName)):
|
||||||
url += '?%s' % urlencode(qdict)
|
url += '?%s' % urlencode(qdict)
|
||||||
xbmc.log("UseGET URL %s"%(url), xbmc.LOGDEBUG)
|
#xbmc.log("UseGET URL %s"%(url),xbmc.LOGDEBUG)
|
||||||
#print(url)
|
req = urllib2.Request(url)
|
||||||
req = urllib.request.Request(url)
|
|
||||||
return req
|
return req
|
||||||
|
|
||||||
def _getRequestWithList(self, viewName, listName, alist, query={}):
|
def _getRequestWithList(self, viewName, listName, alist, query={}):
|
||||||
@ -2834,7 +2841,7 @@ class Connection(object):
|
|||||||
data.write(urlencode(qdict))
|
data.write(urlencode(qdict))
|
||||||
for i in alist:
|
for i in alist:
|
||||||
data.write('&%s' % urlencode({listName: i}))
|
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:
|
if self._useGET:
|
||||||
url += '?%s' % data.getvalue()
|
url += '?%s' % data.getvalue()
|
||||||
@ -2861,7 +2868,7 @@ class Connection(object):
|
|||||||
for k, l in listMap.items():
|
for k, l in listMap.items():
|
||||||
for i in l:
|
for i in l:
|
||||||
data.write('&%s' % urlencode({k: i}))
|
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:
|
if self._useGET:
|
||||||
url += '?%s' % data.getvalue()
|
url += '?%s' % data.getvalue()
|
||||||
@ -2922,7 +2929,7 @@ class Connection(object):
|
|||||||
"""
|
"""
|
||||||
separate REST portion of URL from base server path.
|
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):
|
def _fixLastModified(self, data):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
"""
|
|
||||||
Musicbrainz utilities for plugin.audio.subsonic
|
|
||||||
"""
|
|
||||||
from .mbconnection import *
|
|
||||||
|
|
||||||
__version__ = '0.0.1'
|
|
||||||
@ -1,146 +0,0 @@
|
|||||||
import os
|
|
||||||
import json
|
|
||||||
import urllib.request
|
|
||||||
from urllib.parse import urlencode
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
import re
|
|
||||||
|
|
||||||
urllib.request.install_opener(urllib.request.build_opener(urllib.request.HTTPSHandler))
|
|
||||||
|
|
||||||
class MBConnection(object):
|
|
||||||
def __init__(self, lang = "en"):
|
|
||||||
self._lang = lang
|
|
||||||
self._baseUrl = "https://musicbrainz.org/ws/2"
|
|
||||||
self._wikipediaBaseUrl = "https://{}.wikipedia.org/wiki".format(self._lang)
|
|
||||||
self._wikidataBaseUrl = "https://www.wikidata.org/wiki/Special:EntityData"
|
|
||||||
self._opener = self._getOpener()
|
|
||||||
|
|
||||||
def _getOpener(self):
|
|
||||||
opener = urllib.request.build_opener(urllib.request.HTTPSHandler)
|
|
||||||
return opener
|
|
||||||
|
|
||||||
def search(self, entity, query, limit=None, offset=None):
|
|
||||||
viewName = '%s' % entity
|
|
||||||
q = self._getQueryDict({'query': query, 'limit': limit, 'offset': offset})
|
|
||||||
req = self._getRequest(self._baseUrl, viewName, q)
|
|
||||||
res = self._doInfoReqXml(req)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def get_wiki_extract(self, title):
|
|
||||||
try:
|
|
||||||
if('http' in title):
|
|
||||||
#accepts search text or full url https://en.wikipedia.org/wiki/Alex_Lloyd
|
|
||||||
pattern = 'wikipedia.org/wiki/(.+)'
|
|
||||||
title = re.search(pattern, title).group(1)
|
|
||||||
viewName = 'api.php'
|
|
||||||
q = self._getQueryDict({'format' : 'json', 'action' : 'query', 'prop' : 'extracts', 'redirects' : 1, 'titles' : title})
|
|
||||||
req = self._getRequest(self._wikipediaBaseUrl[:-3], viewName, q, 'exintro&explaintext&')
|
|
||||||
res = self._doInfoReqJson(req)
|
|
||||||
pages = res['query']['pages']
|
|
||||||
extract = list(pages.values())[0]['extract']
|
|
||||||
return extract
|
|
||||||
except Exception as e:
|
|
||||||
print("get_artist_wikpedia failed %s"%e)
|
|
||||||
return
|
|
||||||
|
|
||||||
#https://en.wikipedia.org/w/api.php?exintro&explaintext&format=json&action=query&prop=extracts&redirects=1&titles=%C3%89milie_Simon
|
|
||||||
def get_wiki_image(self, title):
|
|
||||||
try:
|
|
||||||
if('http' in title):
|
|
||||||
#accepts search text or full url https://en.wikipedia.org/wiki/Alex_Lloyd
|
|
||||||
pattern = 'wikipedia.org/wiki/(.+)'
|
|
||||||
title = re.search(pattern, title).group(1)
|
|
||||||
viewName = 'api.php'
|
|
||||||
q = self._getQueryDict({'format' : 'json', 'action' : 'query', 'prop' : 'pageimages', 'pithumbsize' : 800, 'titles' : title})
|
|
||||||
req = self._getRequest(self._wikipediaBaseUrl[:-3], viewName, q)
|
|
||||||
res = self._doInfoReqJson(req)
|
|
||||||
pages = res['query']['pages']
|
|
||||||
print(res['query']['pages'])
|
|
||||||
image_url = list(pages.values())[0]['thumbnail']['source']
|
|
||||||
return image_url
|
|
||||||
except Exception as e:
|
|
||||||
print("get_wiki_image failed %s"%e)
|
|
||||||
return
|
|
||||||
|
|
||||||
def get_artist_id(self, query):
|
|
||||||
try:
|
|
||||||
dres = self.search('artist', query)
|
|
||||||
artist_list = dres.find('{http://musicbrainz.org/ns/mmd-2.0#}artist-list')
|
|
||||||
artist = artist_list.find('{http://musicbrainz.org/ns/mmd-2.0#}artist')
|
|
||||||
return artist.attrib['id']
|
|
||||||
except Exception as e:
|
|
||||||
print("get_artist_id failed %s"%e)
|
|
||||||
return
|
|
||||||
|
|
||||||
def get_relation(self, artist_id, rel_type):
|
|
||||||
try:
|
|
||||||
viewName = 'artist/%s' % artist_id
|
|
||||||
q = self._getQueryDict({'inc': "url-rels"})
|
|
||||||
req = self._getRequest(self._baseUrl, viewName, q)
|
|
||||||
res = self._doInfoReqXml(req)
|
|
||||||
for relation in res.iter('{http://musicbrainz.org/ns/mmd-2.0#}relation'):
|
|
||||||
if relation.attrib['type'] == rel_type:
|
|
||||||
return relation.find('{http://musicbrainz.org/ns/mmd-2.0#}target').text
|
|
||||||
except Exception as e:
|
|
||||||
print("get_artist_image failed %s"%e)
|
|
||||||
return
|
|
||||||
|
|
||||||
def get_artist_image(self, artist_id):
|
|
||||||
try:
|
|
||||||
image = self.get_relation(artist_id, 'image')
|
|
||||||
return image
|
|
||||||
except Exception as e:
|
|
||||||
print("get_artist_image failed %s"%e)
|
|
||||||
return
|
|
||||||
|
|
||||||
def get_artist_wikpedia(self, artist_id):
|
|
||||||
wikidata_url = self.get_relation(artist_id, 'wikidata')
|
|
||||||
pattern = 'www.wikidata.org/wiki/(Q\d+)'
|
|
||||||
try:
|
|
||||||
wikidata_ref = re.search(pattern, wikidata_url).group(1)
|
|
||||||
viewName = '%s.rdf' % wikidata_ref
|
|
||||||
q = self._getQueryDict({})
|
|
||||||
req = self._getRequest(self._wikidataBaseUrl, viewName, q )
|
|
||||||
res = self._doInfoReqXml(req)
|
|
||||||
for item in res.iter('{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description'):
|
|
||||||
try:
|
|
||||||
url = item.attrib['{http://www.w3.org/1999/02/22-rdf-syntax-ns#}about']
|
|
||||||
if self._wikipediaBaseUrl in url:
|
|
||||||
#print(urlencode(url))
|
|
||||||
#print((url.encode().decode('unicode-escape')))
|
|
||||||
return urllib.parse.unquote(url)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
print("get_artist_wikpedia failed %s"%e)
|
|
||||||
return
|
|
||||||
|
|
||||||
def _getQueryDict(self, d):
|
|
||||||
"""
|
|
||||||
Given a dictionary, it cleans out all the values set to None
|
|
||||||
"""
|
|
||||||
for k, v in list(d.items()):
|
|
||||||
if v is None:
|
|
||||||
del d[k]
|
|
||||||
return d
|
|
||||||
|
|
||||||
def _getRequest(self, baseUrl, viewName, query={}, prefix=""):
|
|
||||||
qdict = {}
|
|
||||||
qdict.update(query)
|
|
||||||
url = '%s/%s' % (baseUrl, viewName)
|
|
||||||
if(prefix!='' or qdict!={}):
|
|
||||||
url += "?%s%s" % (prefix, urlencode(qdict))
|
|
||||||
print("UseGET URL %s" % (url))
|
|
||||||
req = urllib.request.Request(url)
|
|
||||||
return req
|
|
||||||
|
|
||||||
def _doInfoReqXml(self, req):
|
|
||||||
res = urllib.request.urlopen(req)
|
|
||||||
data = res.read().decode('utf-8')
|
|
||||||
dres = ET.fromstring(data)
|
|
||||||
return dres
|
|
||||||
|
|
||||||
def _doInfoReqJson(self, req):
|
|
||||||
res = urllib.request.urlopen(req)
|
|
||||||
dres = json.loads(res.read().decode('utf-8'))
|
|
||||||
return dres
|
|
||||||
@ -19,14 +19,17 @@ import inspect
|
|||||||
import time
|
import time
|
||||||
import hashlib
|
import hashlib
|
||||||
import pickle
|
import pickle
|
||||||
from collections import MutableMapping, namedtuple
|
from collections import MutableMapping
|
||||||
|
from collections import namedtuple
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from platform import uname
|
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 xbmcaddon
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
@ -35,7 +38,7 @@ import xbmcvfs
|
|||||||
__all__ = ['SimplePluginError', 'Storage', 'MemStorage', 'Addon', 'Plugin',
|
__all__ = ['SimplePluginError', 'Storage', 'MemStorage', 'Addon', 'Plugin',
|
||||||
'RoutedPlugin', 'Params', 'log_exception', 'translate_path']
|
'RoutedPlugin', 'Params', 'log_exception', 'translate_path']
|
||||||
|
|
||||||
getargspec = inspect.getfullargspec
|
getargspec = inspect.getargspec
|
||||||
|
|
||||||
Route = namedtuple('Route', ['pattern', 'func'])
|
Route = namedtuple('Route', ['pattern', 'func'])
|
||||||
|
|
||||||
@ -320,7 +323,7 @@ class MemStorage(MutableMapping):
|
|||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
lines = []
|
lines = []
|
||||||
for key, val in iter(self.items()):
|
for key, val in iteritems(self):
|
||||||
lines.append('{0}: {1}'.format(repr(key), repr(val)))
|
lines.append('{0}: {1}'.format(repr(key), repr(val)))
|
||||||
return ', '.join(lines)
|
return ', '.join(lines)
|
||||||
|
|
||||||
@ -1085,7 +1088,6 @@ class Plugin(Addon):
|
|||||||
return action_callable(self._params)
|
return action_callable(self._params)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RoutedPlugin(Plugin):
|
class RoutedPlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
Plugin class that implements "pretty URL" routing similar to Flask and Bottle
|
Plugin class that implements "pretty URL" routing similar to Flask and Bottle
|
||||||
@ -1187,7 +1189,7 @@ class RoutedPlugin(Plugin):
|
|||||||
quote_plus(str(arg))
|
quote_plus(str(arg))
|
||||||
)
|
)
|
||||||
# list allows to manipulate the dict during iteration
|
# list allows to manipulate the dict during iteration
|
||||||
for key, value in list(iter(kwargs.items())):
|
for key, value in list(iteritems(kwargs)):
|
||||||
for match in matches[len(args):]:
|
for match in matches[len(args):]:
|
||||||
|
|
||||||
match_string = match[1:-1]
|
match_string = match[1:-1]
|
||||||
@ -1327,7 +1329,7 @@ class RoutedPlugin(Plugin):
|
|||||||
if match is not None:
|
if match is not None:
|
||||||
kwargs = match.groupdict()
|
kwargs = match.groupdict()
|
||||||
# list allows to manipulate the dict during iteration
|
# list allows to manipulate the dict during iteration
|
||||||
for key, value in list(iter(kwargs.items())):
|
for key, value in list(iteritems(kwargs)):
|
||||||
if key.startswith('int__') or key.startswith('float__'):
|
if key.startswith('int__') or key.startswith('float__'):
|
||||||
del kwargs[key]
|
del kwargs[key]
|
||||||
if key.startswith('int__'):
|
if key.startswith('int__'):
|
||||||
|
|||||||
223
main.py
223
main.py
@ -12,13 +12,12 @@ import time
|
|||||||
import hashlib
|
import hashlib
|
||||||
import random
|
import random
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from collections.abc import MutableMapping
|
from collections import MutableMapping
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
# Add the /lib folder to sys
|
# 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 dbutils
|
|
||||||
import libsonic
|
import libsonic
|
||||||
|
|
||||||
from simpleplugin import Plugin
|
from simpleplugin import Plugin
|
||||||
@ -28,16 +27,7 @@ from simpleplugin import Addon
|
|||||||
plugin = Plugin()
|
plugin = Plugin()
|
||||||
|
|
||||||
connection = None
|
connection = None
|
||||||
db = None
|
|
||||||
|
|
||||||
cachetime = int(Addon().get_setting('cachetime'))
|
cachetime = int(Addon().get_setting('cachetime'))
|
||||||
try:
|
|
||||||
enhancedInfo = Addon().get_setting('enhanced_info')
|
|
||||||
except:
|
|
||||||
enhancedInfo = False
|
|
||||||
|
|
||||||
db_filename = "subsonic_sqlite.db"
|
|
||||||
|
|
||||||
|
|
||||||
local_starred = set({})
|
local_starred = set({})
|
||||||
ListContext = namedtuple('ListContext', ['listing', 'succeeded','update_listing', 'cache_to_disk','sort_methods', 'view_mode','content', 'category'])
|
ListContext = namedtuple('ListContext', ['listing', 'succeeded','update_listing', 'cache_to_disk','sort_methods', 'view_mode','content', 'category'])
|
||||||
@ -50,6 +40,7 @@ def popup(text, time=5000, image=None):
|
|||||||
|
|
||||||
def get_connection():
|
def get_connection():
|
||||||
global connection
|
global connection
|
||||||
|
|
||||||
if connection==None:
|
if connection==None:
|
||||||
connected = False
|
connected = False
|
||||||
# Create connection
|
# Create connection
|
||||||
@ -63,16 +54,14 @@ def get_connection():
|
|||||||
insecure=Addon().get_setting('insecure'),
|
insecure=Addon().get_setting('insecure'),
|
||||||
legacyAuth=Addon().get_setting('legacyauth'),
|
legacyAuth=Addon().get_setting('legacyauth'),
|
||||||
useGET=Addon().get_setting('useget'),
|
useGET=Addon().get_setting('useget'),
|
||||||
appName="Kodi-Subsonic",
|
|
||||||
)
|
)
|
||||||
connected = connection.ping()
|
connected = connection.ping()
|
||||||
except Exception as e:
|
except:
|
||||||
plugin.log("Exception: %s"%e)
|
pass
|
||||||
#except:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
if connected==False:
|
if connected==False:
|
||||||
popup('Connection error')
|
popup('Connection error')
|
||||||
|
plugin.log('Connection error')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return connection
|
return connection
|
||||||
@ -119,6 +108,11 @@ def root(params):
|
|||||||
'callback': 'search',
|
'callback': 'search',
|
||||||
'thumb': None
|
'thumb': None
|
||||||
},
|
},
|
||||||
|
'searchalbum': {
|
||||||
|
'name': Addon().get_localized_string(30045),
|
||||||
|
'callback': 'search_album',
|
||||||
|
'thumb': None
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Iterate through categories
|
# Iterate through categories
|
||||||
@ -590,27 +584,61 @@ def search(params):
|
|||||||
|
|
||||||
dialog = xbmcgui.Dialog()
|
dialog = xbmcgui.Dialog()
|
||||||
d = dialog.input(Addon().get_localized_string(30039), type=xbmcgui.INPUT_ALPHANUM)
|
d = dialog.input(Addon().get_localized_string(30039), type=xbmcgui.INPUT_ALPHANUM)
|
||||||
if not d:
|
|
||||||
d = " "
|
|
||||||
|
|
||||||
|
|
||||||
# get connection
|
|
||||||
connection = get_connection()
|
|
||||||
|
|
||||||
if connection==False:
|
|
||||||
return
|
|
||||||
|
|
||||||
listing = []
|
listing = []
|
||||||
|
|
||||||
# Get items
|
if d:
|
||||||
items = connection.search2(query=d)
|
# get connection
|
||||||
# Iterate through items
|
connection = get_connection()
|
||||||
for item in items.get('searchResult2').get('song'):
|
|
||||||
entry = get_entry_track( item, params)
|
|
||||||
listing.append(entry)
|
|
||||||
|
|
||||||
if len(listing) == 1:
|
if connection == False:
|
||||||
plugin.log('One single Media Folder found; do return listing from browse_indexes()...')
|
return
|
||||||
|
|
||||||
|
# Get items
|
||||||
|
items = connection.search2(query=d)
|
||||||
|
# Iterate through items
|
||||||
|
songs = items.get('searchResult2').get('song')
|
||||||
|
if songs:
|
||||||
|
for item in songs:
|
||||||
|
entry = get_entry_track( item, params)
|
||||||
|
listing.append(entry)
|
||||||
|
|
||||||
|
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)
|
return browse_indexes(params)
|
||||||
else:
|
else:
|
||||||
add_directory_items(create_listing(listing))
|
add_directory_items(create_listing(listing))
|
||||||
@ -750,7 +778,7 @@ def get_entry_playlist(item,params):
|
|||||||
menu_id= params.get('menu_id')
|
menu_id= params.get('menu_id')
|
||||||
|
|
||||||
),
|
),
|
||||||
'info': {'music': {
|
'info': {'music': { ##http://romanvm.github.io/Kodistubs/_autosummary/xbmcgui.html#xbmcgui.ListItem.setInfo
|
||||||
'title': item.get('name'),
|
'title': item.get('name'),
|
||||||
'count': item.get('songCount'),
|
'count': item.get('songCount'),
|
||||||
'duration': item.get('duration'),
|
'duration': item.get('duration'),
|
||||||
@ -758,56 +786,35 @@ def get_entry_playlist(item,params):
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_image(item):
|
|
||||||
db = get_db()
|
|
||||||
image = None
|
|
||||||
try:
|
|
||||||
if Addon().get_setting('enhanced_info'):
|
|
||||||
image = db.get_value(item.get('id'), 'wikipedia_image')[0][0]
|
|
||||||
#print("Checking image type %s %s %s"%(item.get('id'), image, type(image)))
|
|
||||||
except IndexError:
|
|
||||||
print("Wiki image not available for artist %s" % item.get('name'))
|
|
||||||
except Exception as e:
|
|
||||||
print("Error getting image for %s %s"%(item.get('name'),e))
|
|
||||||
return image
|
|
||||||
if (image is None) or (image =='') or (image =='None'):
|
|
||||||
connection = get_connection()
|
|
||||||
#print("Using coverart tag from item %s is it same as %s ?"%(item.get('coverArt'),item.get('id')))
|
|
||||||
image = connection.getCoverArtUrl(item.get('coverArt'))
|
|
||||||
return image
|
|
||||||
|
|
||||||
def get_artist_info(artist_id, forced=False):
|
def get_artist_info(artist_id, forced=False):
|
||||||
db = get_db()
|
print("Updating artist info for id: %s"%(artist_id))
|
||||||
artist_info = ""
|
popup("Updating artist info\nplease wait")
|
||||||
print("Retreiving artist info for id: %s"%(artist_id))
|
last_update = 0
|
||||||
#popup("Updating artist info\nplease wait")
|
artist_info = {}
|
||||||
try:
|
cache_file = 'ar-%s'%hashlib.md5(artist_id.encode('utf-8')).hexdigest()
|
||||||
if enhancedInfo:
|
with plugin.get_storage(cache_file) as storage:
|
||||||
artist_info = db.get_value(artist_id, 'artist_info')[0][0]
|
try:
|
||||||
artist_wiki = db.get_value(artist_id, 'wikipedia_extract')[0][0]
|
last_update = storage['updated']
|
||||||
if(len(artist_info)<10):
|
except KeyError as e:
|
||||||
print("Using wiki data")
|
plugin.log("Artist keyerror, is this a new cache file? %s"%cache_file)
|
||||||
artist_info = artist_wiki
|
if(time.time()-last_update>(random.randint(1,111)*360) or forced):
|
||||||
|
plugin.log("Artist cache expired, updating %s elapsed vs random %s forced %s"%(int(time.time()-last_update),(random.randint(1,111)*3600), forced))
|
||||||
|
try:
|
||||||
|
artist_info = connection.getArtistInfo2(artist_id).get('artistInfo2')
|
||||||
|
storage['artist_info'] = artist_info
|
||||||
|
storage['updated']=time.time()
|
||||||
|
except AttributeError as e:
|
||||||
|
plugin.log("Attribute error, probably couldn't find any info")
|
||||||
else:
|
else:
|
||||||
return ""
|
print("Cache ok for %s retrieving"%artist_id)
|
||||||
if(artist_info is None):
|
artist_info = storage['artist_info']
|
||||||
print("artist_info is None making empty string")
|
|
||||||
artist_info = ""
|
|
||||||
except IndexError:
|
|
||||||
print("Enhanced info not available for artist %s" % artist_id)
|
|
||||||
except Exception as e:
|
|
||||||
print("Error getting artist info from DB %s"%e)
|
|
||||||
return artist_info
|
return artist_info
|
||||||
|
|
||||||
def get_entry_artist(item,params):
|
def get_entry_artist(item,params):
|
||||||
image = get_image(item)
|
image = connection.getCoverArtUrl(item.get('coverArt'))
|
||||||
artist_info = get_artist_info(item.get('id'))
|
#artist_info = get_artist_info(item.get('id'))
|
||||||
#print("Checking for value %s %s"%(artist_info, type(artist_info)))
|
#artist_bio = artist_info.get('biography')
|
||||||
if(artist_info is None or artist_info == 'None' or artist_info == ''):
|
#fanart = artist_info.get('largeImageUrl')
|
||||||
artist_lbl = '%s' % (item.get('name'))
|
|
||||||
else:
|
|
||||||
artist_lbl = '%s - %s' % (item.get('name'),artist_info)
|
|
||||||
#print("Using label %s"%artist_lbl)
|
|
||||||
fanart = image
|
fanart = image
|
||||||
return {
|
return {
|
||||||
'label': get_starred_label(item.get('id'),item.get('name')),
|
'label': get_starred_label(item.get('id'),item.get('name')),
|
||||||
@ -821,9 +828,13 @@ def get_entry_artist(item,params):
|
|||||||
menu_id= params.get('menu_id')
|
menu_id= params.get('menu_id')
|
||||||
),
|
),
|
||||||
'info': {
|
'info': {
|
||||||
'music': {
|
'music': { ##http://romanvm.github.io/Kodistubs/_autosummary/xbmcgui.html#xbmcgui.ListItem.setInfo
|
||||||
'count': item.get('albumCount'),
|
'count': item.get('albumCount'),
|
||||||
'artist': artist_lbl,
|
'artist': item.get('name'),
|
||||||
|
#'title': "testtitle",
|
||||||
|
#'album': "testalbum",
|
||||||
|
#'comment': "testcomment"
|
||||||
|
#'title': artist_bio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -843,7 +854,7 @@ def get_entry_album(item, params):
|
|||||||
menu_id= params.get('menu_id')
|
menu_id= params.get('menu_id')
|
||||||
),
|
),
|
||||||
'info': {
|
'info': {
|
||||||
'music': {
|
'music': { ##http://romanvm.github.io/Kodistubs/_autosummary/xbmcgui.html#xbmcgui.ListItem.setInfo
|
||||||
'count': item.get('songCount'),
|
'count': item.get('songCount'),
|
||||||
'date': convert_date_from_iso8601(item.get('created')), #date added
|
'date': convert_date_from_iso8601(item.get('created')), #date added
|
||||||
'duration': item.get('duration'),
|
'duration': item.get('duration'),
|
||||||
@ -885,7 +896,7 @@ def get_entry_track(item,params):
|
|||||||
),
|
),
|
||||||
'is_playable': True,
|
'is_playable': True,
|
||||||
'mime': item.get("contentType"),
|
'mime': item.get("contentType"),
|
||||||
'info': {'music': {
|
'info': {'music': { #http://romanvm.github.io/Kodistubs/_autosummary/xbmcgui.html#xbmcgui.ListItem.setInfo
|
||||||
'title': item.get('title'),
|
'title': item.get('title'),
|
||||||
'album': item.get('album'),
|
'album': item.get('album'),
|
||||||
'artist': item.get('artist'),
|
'artist': item.get('artist'),
|
||||||
@ -1402,7 +1413,8 @@ def walk_index(folder_id=None):
|
|||||||
plugin.log("artist: %s"%artist)
|
plugin.log("artist: %s"%artist)
|
||||||
yield artist
|
yield artist
|
||||||
except KeyError:
|
except KeyError:
|
||||||
yield from ()
|
for emp in ():
|
||||||
|
yield emp
|
||||||
|
|
||||||
def walk_playlists():
|
def walk_playlists():
|
||||||
"""
|
"""
|
||||||
@ -1413,7 +1425,8 @@ def walk_playlists():
|
|||||||
for child in response["playlists"]["playlist"]:
|
for child in response["playlists"]["playlist"]:
|
||||||
yield child
|
yield child
|
||||||
except KeyError:
|
except KeyError:
|
||||||
yield from ()
|
for emp in ():
|
||||||
|
yield emp
|
||||||
|
|
||||||
def walk_playlist(playlist_id):
|
def walk_playlist(playlist_id):
|
||||||
"""
|
"""
|
||||||
@ -1424,7 +1437,8 @@ def walk_playlist(playlist_id):
|
|||||||
for child in response["playlist"]["entry"]:
|
for child in response["playlist"]["entry"]:
|
||||||
yield child
|
yield child
|
||||||
except KeyError:
|
except KeyError:
|
||||||
yield from ()
|
for emp in ():
|
||||||
|
yield emp
|
||||||
|
|
||||||
def walk_folders():
|
def walk_folders():
|
||||||
response = connection.getMusicFolders()
|
response = connection.getMusicFolders()
|
||||||
@ -1432,7 +1446,8 @@ def walk_folders():
|
|||||||
for child in response["musicFolders"]["musicFolder"]:
|
for child in response["musicFolders"]["musicFolder"]:
|
||||||
yield child
|
yield child
|
||||||
except KeyError:
|
except KeyError:
|
||||||
yield from ()
|
for emp in ():
|
||||||
|
yield emp
|
||||||
|
|
||||||
def walk_directory(directory_id, merge_artist = True):
|
def walk_directory(directory_id, merge_artist = True):
|
||||||
"""
|
"""
|
||||||
@ -1448,7 +1463,8 @@ def walk_directory(directory_id, merge_artist = True):
|
|||||||
else:
|
else:
|
||||||
yield child
|
yield child
|
||||||
except KeyError:
|
except KeyError:
|
||||||
yield from ()
|
for emp in ():
|
||||||
|
yield emp
|
||||||
|
|
||||||
def walk_artist(artist_id):
|
def walk_artist(artist_id):
|
||||||
"""
|
"""
|
||||||
@ -1460,7 +1476,8 @@ def walk_artist(artist_id):
|
|||||||
for child in response["artist"]["album"]:
|
for child in response["artist"]["album"]:
|
||||||
yield child
|
yield child
|
||||||
except KeyError:
|
except KeyError:
|
||||||
yield from ()
|
for emp in ():
|
||||||
|
yield emp
|
||||||
|
|
||||||
def walk_artists():
|
def walk_artists():
|
||||||
"""
|
"""
|
||||||
@ -1473,7 +1490,8 @@ def walk_artists():
|
|||||||
for artist in index["artist"]:
|
for artist in index["artist"]:
|
||||||
yield artist
|
yield artist
|
||||||
except KeyError:
|
except KeyError:
|
||||||
yield from ()
|
for emp in ():
|
||||||
|
yield emp
|
||||||
|
|
||||||
def walk_genres():
|
def walk_genres():
|
||||||
"""
|
"""
|
||||||
@ -1485,7 +1503,8 @@ def walk_genres():
|
|||||||
for genre in response["genres"]["genre"]:
|
for genre in response["genres"]["genre"]:
|
||||||
yield genre
|
yield genre
|
||||||
except KeyError:
|
except KeyError:
|
||||||
yield from ()
|
for emp in ():
|
||||||
|
yield emp
|
||||||
|
|
||||||
def walk_albums(ltype, size=None, fromYear=None,toYear=None, genre=None, offset=None):
|
def walk_albums(ltype, size=None, fromYear=None,toYear=None, genre=None, offset=None):
|
||||||
"""
|
"""
|
||||||
@ -1519,7 +1538,8 @@ def walk_album(album_id):
|
|||||||
for song in response["album"]["song"]:
|
for song in response["album"]["song"]:
|
||||||
yield song
|
yield song
|
||||||
except KeyError:
|
except KeyError:
|
||||||
yield from ()
|
for emp in ():
|
||||||
|
yield emp
|
||||||
|
|
||||||
def walk_tracks_random(size=None, genre=None, fromYear=None,toYear=None):
|
def walk_tracks_random(size=None, genre=None, fromYear=None,toYear=None):
|
||||||
"""
|
"""
|
||||||
@ -1531,7 +1551,8 @@ def walk_tracks_random(size=None, genre=None, fromYear=None,toYear=None):
|
|||||||
for song in response["randomSongs"]["song"]:
|
for song in response["randomSongs"]["song"]:
|
||||||
yield song
|
yield song
|
||||||
except KeyError:
|
except KeyError:
|
||||||
yield from ()
|
for emp in ():
|
||||||
|
yield emp
|
||||||
|
|
||||||
def walk_tracks_starred():
|
def walk_tracks_starred():
|
||||||
"""
|
"""
|
||||||
@ -1542,18 +1563,8 @@ def walk_tracks_starred():
|
|||||||
for song in response["starred"]["song"]:
|
for song in response["starred"]["song"]:
|
||||||
yield song
|
yield song
|
||||||
except KeyError:
|
except KeyError:
|
||||||
yield from ()
|
for emp in ():
|
||||||
|
yield emp
|
||||||
def get_db():
|
|
||||||
global db
|
|
||||||
global db_filename
|
|
||||||
db_path = os.path.join(plugin.profile_dir, db_filename)
|
|
||||||
plugin.log("Getting DB %s"%db_path)
|
|
||||||
try:
|
|
||||||
db = dbutils.SQLiteDatabase(db_path)
|
|
||||||
except Exception as e:
|
|
||||||
plugin.log("Connecting to DB failed: %s"%e)
|
|
||||||
return db
|
|
||||||
|
|
||||||
# Start plugin from within Kodi.
|
# Start plugin from within Kodi.
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -154,7 +154,7 @@ msgid "Browse"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30039"
|
msgctxt "#30039"
|
||||||
msgid "Search"
|
msgid "Search Songs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30040"
|
msgctxt "#30040"
|
||||||
@ -178,5 +178,5 @@ msgid "Scrobble to Last.FM"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30045"
|
msgctxt "#30045"
|
||||||
msgid "Enhanced Information"
|
msgid "Search Albums"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -153,8 +153,8 @@ msgid "Browse"
|
|||||||
msgstr "Parcourir"
|
msgstr "Parcourir"
|
||||||
|
|
||||||
msgctxt "#30039"
|
msgctxt "#30039"
|
||||||
msgid "Search"
|
msgid "Search Songs"
|
||||||
msgstr "Rechercher"
|
msgstr "Rechercher Chansons"
|
||||||
|
|
||||||
|
|
||||||
msgctxt "#30040"
|
msgctxt "#30040"
|
||||||
@ -178,5 +178,5 @@ msgid "Scrobble to Last.FM"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30045"
|
msgctxt "#30045"
|
||||||
msgid "Enhanced Information"
|
msgid "Search Albums"
|
||||||
msgstr ""
|
msgstr "Rechercher Albums"
|
||||||
|
|||||||
@ -153,8 +153,8 @@ msgid "Browse"
|
|||||||
msgstr "Durchsuchen"
|
msgstr "Durchsuchen"
|
||||||
|
|
||||||
msgctxt "#30039"
|
msgctxt "#30039"
|
||||||
msgid "Search"
|
msgid "Search Songs"
|
||||||
msgstr "Suche"
|
msgstr "Suche Lieder"
|
||||||
|
|
||||||
msgctxt "#30040"
|
msgctxt "#30040"
|
||||||
msgid "useGET"
|
msgid "useGET"
|
||||||
@ -177,5 +177,5 @@ msgid "Scrobble to Last.FM"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30045"
|
msgctxt "#30045"
|
||||||
msgid "Enhanced Information"
|
msgid "Search Albums"
|
||||||
msgstr ""
|
msgstr "Suche Albums"
|
||||||
|
|||||||
@ -28,7 +28,6 @@
|
|||||||
<setting label="30018" id="cachetime" type="labelenum" default="3600" values="1|5|15|30|60|120|180|720|1440|3600"/>
|
<setting label="30018" id="cachetime" type="labelenum" default="3600" values="1|5|15|30|60|120|180|720|1440|3600"/>
|
||||||
<setting label="30043" id="merge" type="bool" default="false" />
|
<setting label="30043" id="merge" type="bool" default="false" />
|
||||||
<setting label="30044" id="scrobble" type="bool" default="false" />
|
<setting label="30044" id="scrobble" type="bool" default="false" />
|
||||||
<setting label="30045" id="enhanced_info" type="bool" default="false" />
|
|
||||||
|
|
||||||
</category>
|
</category>
|
||||||
</settings>
|
</settings>
|
||||||
|
|||||||
187
service.py
187
service.py
@ -3,42 +3,17 @@ import xbmc
|
|||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
import os
|
import os
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
import time
|
|
||||||
import random
|
|
||||||
|
|
||||||
# Add the /lib folder to sys
|
# 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 dbutils
|
|
||||||
import libsonic
|
import libsonic
|
||||||
import musicbrainz
|
|
||||||
|
|
||||||
connection = None
|
|
||||||
db = None
|
|
||||||
mb = None
|
|
||||||
|
|
||||||
serviceEnabled = True
|
|
||||||
|
|
||||||
refresh_age = 86400#3600 #multiple of random to age info records - needs some validation
|
|
||||||
check_freq_init = 30 #How often to run a refresh cycle - needs some validation - updates afer first load to
|
|
||||||
check_freq_refresh = 86400
|
|
||||||
|
|
||||||
|
|
||||||
db_filename = "subsonic_sqlite.db"
|
|
||||||
|
|
||||||
last_db_check = 0
|
|
||||||
current_artist_index = 0
|
|
||||||
|
|
||||||
from simpleplugin import Plugin
|
from simpleplugin import Plugin
|
||||||
from simpleplugin import Addon
|
from simpleplugin import Addon
|
||||||
|
|
||||||
# Create plugin instance
|
# Create plugin instance
|
||||||
plugin = Plugin()
|
plugin = Plugin()
|
||||||
|
connection = None
|
||||||
try:
|
|
||||||
enhancedInfo = Addon().get_setting('enhanced_info')
|
|
||||||
except:
|
|
||||||
enhancedInfo = False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
scrobbleEnabled = Addon().get_setting('scrobble')
|
scrobbleEnabled = Addon().get_setting('scrobble')
|
||||||
@ -47,25 +22,17 @@ except:
|
|||||||
|
|
||||||
scrobbled = False
|
scrobbled = False
|
||||||
|
|
||||||
def check_address_format():
|
|
||||||
address = Addon().get_setting('subsonic_url')
|
|
||||||
port = Addon().get_setting('port')
|
|
||||||
if len(address.split(":"))>2:
|
|
||||||
found_port = address.split(":")[2]
|
|
||||||
plugin.log("Found port %s in address %s, splitting"%(found_port, address))
|
|
||||||
plugin.log("Changing port from %s to %s"%(port, found_port))
|
|
||||||
Addon().set_setting('port', int(found_port))
|
|
||||||
Addon().set_setting('subsonic_url', "%s:%s"%(address.split(":")[0],address.split(":")[1]))
|
|
||||||
|
|
||||||
def popup(text, time=5000, image=None):
|
def popup(text, time=5000, image=None):
|
||||||
title = plugin.addon.getAddonInfo('name')
|
title = plugin.addon.getAddonInfo('name')
|
||||||
icon = plugin.addon.getAddonInfo('icon')
|
icon = plugin.addon.getAddonInfo('icon')
|
||||||
xbmc.executebuiltin('Notification(%s, %s, %d, %s)' % (title, text, time, icon))
|
xbmc.executebuiltin('Notification(%s, %s, %d, %s)' % (title, text,
|
||||||
|
time, icon))
|
||||||
def get_connection():
|
def get_connection():
|
||||||
global connection
|
global connection
|
||||||
|
|
||||||
if connection==None:
|
if connection==None:
|
||||||
connected = False
|
connected = False
|
||||||
|
# Create connection
|
||||||
try:
|
try:
|
||||||
connection = libsonic.Connection(
|
connection = libsonic.Connection(
|
||||||
baseUrl=Addon().get_setting('subsonic_url'),
|
baseUrl=Addon().get_setting('subsonic_url'),
|
||||||
@ -76,7 +43,6 @@ def get_connection():
|
|||||||
insecure=Addon().get_setting('insecure'),
|
insecure=Addon().get_setting('insecure'),
|
||||||
legacyAuth=Addon().get_setting('legacyauth'),
|
legacyAuth=Addon().get_setting('legacyauth'),
|
||||||
useGET=Addon().get_setting('useget'),
|
useGET=Addon().get_setting('useget'),
|
||||||
appName="Kodi-Subsonic",
|
|
||||||
)
|
)
|
||||||
connected = connection.ping()
|
connected = connection.ping()
|
||||||
except:
|
except:
|
||||||
@ -88,110 +54,9 @@ def get_connection():
|
|||||||
|
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
def get_mb():
|
|
||||||
global mb
|
|
||||||
mb = musicbrainz.MBConnection()
|
|
||||||
return mb
|
|
||||||
|
|
||||||
def get_db():
|
|
||||||
global db
|
|
||||||
db_path = os.path.join(plugin.profile_dir, db_filename)
|
|
||||||
plugin.log("Getting DB %s"%db_path)
|
|
||||||
try:
|
|
||||||
db = dbutils.SQLiteDatabase(db_path)
|
|
||||||
except Exception as e:
|
|
||||||
plugin.log("Connecting to DB failed: %s"%e)
|
|
||||||
return db
|
|
||||||
|
|
||||||
def refresh_artist(artist_id):
|
|
||||||
db = get_db()
|
|
||||||
connection = get_connection()
|
|
||||||
artist = connection.getArtist(artist_id)#['subsonic-response']
|
|
||||||
artist_name = artist['artist']['name']
|
|
||||||
db.update_value(artist_id, 'artist_name', artist_name)
|
|
||||||
artist_info = connection.getArtistInfo2(artist_id)
|
|
||||||
try:
|
|
||||||
artist_info = artist_info['artistInfo2']['biography']
|
|
||||||
#pattern = '<a target=\'_blank\' href="https://www.last.fm/music/Afrojack">Read more on Last.fm</a>
|
|
||||||
artist_info = re.sub('<a.*?</a>', '', artist_info)
|
|
||||||
plugin.log("subbed: %s"%artist_info)
|
|
||||||
except:
|
|
||||||
artist_info = ""
|
|
||||||
if(enhancedInfo):
|
|
||||||
mb = get_mb()
|
|
||||||
mb_artist_id = mb.get_artist_id(artist_name)
|
|
||||||
artist_image_url = mb.get_artist_image(mb_artist_id)
|
|
||||||
wikipedia_url = mb.get_artist_wikpedia(mb_artist_id)
|
|
||||||
artist_wiki_extract = mb.get_wiki_extract(wikipedia_url)
|
|
||||||
wikipedia_image = mb.get_wiki_image(wikipedia_url)
|
|
||||||
db.update_value(artist_id, 'artist_info', artist_info)
|
|
||||||
db.update_value(artist_id, 'mb_artist_id', mb_artist_id)
|
|
||||||
db.update_value(artist_id, 'image_url', artist_image_url)
|
|
||||||
db.update_value(artist_id, 'wikipedia_url', wikipedia_url)
|
|
||||||
db.update_value(artist_id, 'wikipedia_extract', artist_wiki_extract)
|
|
||||||
db.update_value(artist_id, 'wikipedia_image', wikipedia_image)
|
|
||||||
|
|
||||||
def check_db_status():
|
|
||||||
global last_db_check
|
|
||||||
global check_freq_init
|
|
||||||
global current_artist_index
|
|
||||||
try:
|
|
||||||
if(time.time()-check_freq_init > last_db_check): #Won't check on every run uses check_freq_init
|
|
||||||
plugin.log("DB check starting %s %s" % (time.time(), last_db_check))
|
|
||||||
db = get_db()
|
|
||||||
connection = get_connection()
|
|
||||||
response = connection.getArtists()
|
|
||||||
current_index_content = response["artists"]["index"][current_artist_index] #Completes refresh for alpha index
|
|
||||||
plugin.log("Starting info load for index %s"%current_index_content['name'])
|
|
||||||
for artist in current_index_content["artist"]:
|
|
||||||
artist_id = artist['id']
|
|
||||||
record_age = db.get_record_age(artist_id)
|
|
||||||
rnd_age = random.randint(1,111)*refresh_age
|
|
||||||
#plugin.log("Record age %s vs %s for %s"%(record_age, rnd_age, artist_id))
|
|
||||||
if(not record_age or (record_age > rnd_age)):
|
|
||||||
#plugin.log("Refreshing %s" % (artist_id))
|
|
||||||
refresh_artist(artist_id)
|
|
||||||
#plugin.log("Refresh complete for %s" % (artist_id))
|
|
||||||
plugin.log("Finished info loading for index %s"%current_index_content['name'])
|
|
||||||
current_artist_index+=1
|
|
||||||
if(current_artist_index>=len(response["artists"]["index"])): #init load complete go to daily check freq
|
|
||||||
plugin.log("Finished info loading for all alpha index")
|
|
||||||
current_artist_index=0
|
|
||||||
check_freq_init = check_freq_refresh
|
|
||||||
plugin.log("check_freq_init is now %s"%check_freq_init)
|
|
||||||
last_db_check = time.time()
|
|
||||||
except Exception as e:
|
|
||||||
plugin.log("Refresh check failed %s"%e)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def check_player_status():
|
|
||||||
global scrobbled
|
|
||||||
if (scrobbleEnabled and xbmc.getCondVisibility("Player.HasMedia")):
|
|
||||||
try:
|
|
||||||
currentFileName = xbmc.getInfoLabel("Player.Filenameandpath")
|
|
||||||
currentFileProgress = xbmc.getInfoLabel("Player.Progress")
|
|
||||||
pattern = re.compile(r'plugin:\/\/plugin\.audio\.subsonic\/\?action=play_track&id=(.*?)&')
|
|
||||||
currentTrackId = re.findall(pattern, currentFileName)[0]
|
|
||||||
#xbmc.log("Name %s Id %s Progress %s"%(currentFileName,currentTrackId,currentFileProgress), xbmc.LOGDEBUG)
|
|
||||||
if (int(currentFileProgress)<50):
|
|
||||||
scrobbled = False
|
|
||||||
elif (int(currentFileProgress)>=50 and scrobbled == False):
|
|
||||||
xbmc.log("Scrobbling Track Id %s"%(currentTrackId), xbmc.LOGDEBUG)
|
|
||||||
success = scrobble_track(currentTrackId)
|
|
||||||
if success:
|
|
||||||
scrobbled = True
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
except IndexError:
|
|
||||||
plugin.log ("Not a Subsonic track")
|
|
||||||
scrobbled = True
|
|
||||||
except Exception as e:
|
|
||||||
xbmc.log("Subsonic scrobble check failed %e"%e, xbmc.LOGINFO)
|
|
||||||
return
|
|
||||||
|
|
||||||
def scrobble_track(track_id):
|
def scrobble_track(track_id):
|
||||||
connection = get_connection()
|
connection = get_connection()
|
||||||
|
|
||||||
if connection==False:
|
if connection==False:
|
||||||
return False
|
return False
|
||||||
res = connection.scrobble(track_id)
|
res = connection.scrobble(track_id)
|
||||||
@ -201,20 +66,40 @@ def scrobble_track(track_id):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
popup("Scrobble failed")
|
popup("Scrobble failed")
|
||||||
xbmc.log("Scrobble failed", xbmc.LOGERROR)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if serviceEnabled:
|
if(scrobbleEnabled):
|
||||||
check_address_format()
|
|
||||||
monitor = xbmc.Monitor()
|
monitor = xbmc.Monitor()
|
||||||
xbmc.log("Subsonic service started", xbmc.LOGINFO)
|
xbmc.log("Subsonic service started", xbmc.LOGINFO)
|
||||||
popup("Subsonic service started")
|
popup("Subsonic service started")
|
||||||
while not monitor.abortRequested():
|
while not monitor.abortRequested():
|
||||||
if monitor.waitForAbort(10):
|
if monitor.waitForAbort(10):
|
||||||
break
|
break
|
||||||
check_player_status()
|
if (xbmc.getCondVisibility("Player.HasMedia")):
|
||||||
check_db_status()
|
try:
|
||||||
|
|
||||||
|
currentFileName = xbmc.getInfoLabel("Player.Filenameandpath")
|
||||||
|
currentFileProgress = xbmc.getInfoLabel("Player.Progress")
|
||||||
|
pattern = re.compile(r'plugin:\/\/plugin\.audio\.subsonic\/\?action=play_track&id=(.*?)&')
|
||||||
|
currentTrackId = re.findall(pattern, currentFileName)[0]
|
||||||
|
#xbmc.log("Name %s Id %s Progress %s"%(currentFileName,currentTrackId,currentFileProgress), xbmc.LOGDEBUG)
|
||||||
|
if (int(currentFileProgress)<50):
|
||||||
|
scrobbled = False
|
||||||
|
elif (int(currentFileProgress)>=50 and scrobbled == False):
|
||||||
|
xbmc.log("Scrobbling Track Id %s"%(currentTrackId), xbmc.LOGDEBUG)
|
||||||
|
success = scrobble_track(currentTrackId)
|
||||||
|
if success:
|
||||||
|
scrobbled = True
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
except IndexError:
|
||||||
|
print ("Not a Subsonic track")
|
||||||
|
scrobbled = True
|
||||||
|
except Exception as e:
|
||||||
|
xbmc.log("Subsonic service failed %e"%e, xbmc.LOGINFO)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
#xbmc.log("Playing stopped", xbmc.LOGINFO)
|
||||||
else:
|
else:
|
||||||
plugin.log("Subsonic service not enabled")
|
xbmc.log("Subsonic service not started due to settings", xbmc.LOGINFO)
|
||||||
|
|||||||
Reference in New Issue
Block a user