Merge branch 'hotfixes' into translations
This commit is contained in:
commit
fae3647407
17 changed files with 308 additions and 195 deletions
|
@ -1,5 +1,5 @@
|
|||
[![stable version](https://img.shields.io/badge/stable_version-1.8.18-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
|
||||
[![beta version](https://img.shields.io/badge/beta_version-2.0.19-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
|
||||
[![beta version](https://img.shields.io/badge/beta_version-2.0.20-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
|
||||
|
||||
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
|
||||
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)
|
||||
|
|
15
addon.xml
15
addon.xml
|
@ -1,10 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.0.19" provider-name="croneter">
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.0.20" provider-name="croneter">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
<import addon="script.module.requests" version="2.9.1" />
|
||||
<import addon="plugin.video.plexkodiconnect.movies" version="2.0.2" />
|
||||
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.3" />
|
||||
<import addon="plugin.video.plexkodiconnect.movies" version="2.0.4" />
|
||||
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.4" />
|
||||
</requires>
|
||||
<extension point="xbmc.python.pluginsource" library="default.py">
|
||||
<provides>video audio image</provides>
|
||||
|
@ -67,7 +67,14 @@
|
|||
<summary lang="ru_RU">Нативная интеграция сервера Plex в Kodi</summary>
|
||||
<description lang="ru_RU">Подключите Kodi к своему серверу Plex. Плагин предполагает что вы управляете своими видео с помощью Plex (а не в Kodi). Вы можете потерять текущие базы данных музыки и видео в Kodi (так как плагин напрямую их изменяет). Используйте на свой страх и риск</description>
|
||||
<disclaimer lang="ru_RU">Используйте на свой страх и риск</disclaimer>
|
||||
<news>version 2.0.19 (beta only):
|
||||
<news>version 2.0.20 (beta only):
|
||||
- Fix missing episode poster in certain views. You will have to manually reset your Kodi database to benefit
|
||||
- Fix episode artwork sometimes not being complete
|
||||
- Fix IndexError for certain Plex channels
|
||||
- Kodi Leia: Fix playback failing
|
||||
- Fix TV On Deck direct paths asking to choose between different media
|
||||
|
||||
version 2.0.19 (beta only):
|
||||
- Fix PKC playback startup getting caught in infinity loop
|
||||
- Rewire library sync, suspend sync during playback
|
||||
- Fix playback failing in certain cases
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
version 2.0.20 (beta only):
|
||||
- Fix missing episode poster in certain views. You will have to manually reset your Kodi database to benefit
|
||||
- Fix episode artwork sometimes not being complete
|
||||
- Fix IndexError for certain Plex channels
|
||||
- Kodi Leia: Fix playback failing
|
||||
- Fix TV On Deck direct paths asking to choose between different media
|
||||
|
||||
version 2.0.19 (beta only):
|
||||
- Fix PKC playback startup getting caught in infinity loop
|
||||
- Rewire library sync, suspend sync during playback
|
||||
|
|
|
@ -41,6 +41,26 @@ msgctxt "#30005"
|
|||
msgid "Username: "
|
||||
msgstr ""
|
||||
|
||||
# Sync notification displayed if there is still artwork to be cached to Kodi
|
||||
msgctxt "#30006"
|
||||
msgid "Caching %s images"
|
||||
msgstr ""
|
||||
|
||||
# Sync notification displayed if syncing of major artwork is done
|
||||
msgctxt "#30007"
|
||||
msgid "Major image caching done"
|
||||
msgstr ""
|
||||
|
||||
# PKC settings artwork: Enable notifications for artwork image sync
|
||||
msgctxt "#30008"
|
||||
msgid "Enable notifications for image caching"
|
||||
msgstr ""
|
||||
|
||||
# PKC settings artwork: Enable image caching during Kodi playback
|
||||
msgctxt "#30009"
|
||||
msgid "Enable image caching during Kodi playback (restart Kodi!)"
|
||||
msgstr ""
|
||||
|
||||
# Button text
|
||||
msgctxt "#30012"
|
||||
msgid "OK"
|
||||
|
|
|
@ -124,8 +124,12 @@ class API(object):
|
|||
# Local path
|
||||
filename = ans.rsplit("\\", 1)[1]
|
||||
else:
|
||||
# Network share
|
||||
filename = ans.rsplit("/", 1)[1]
|
||||
try:
|
||||
# Network share
|
||||
filename = ans.rsplit("/", 1)[1]
|
||||
except IndexError:
|
||||
# E.g. certain Plex channels
|
||||
filename = None
|
||||
return filename
|
||||
|
||||
def file_path(self, force_first_media=False):
|
||||
|
@ -772,33 +776,41 @@ class API(object):
|
|||
# Artwork lookup for episodes is broken for addon paths
|
||||
# Episodes is a bit special, only get the thumb, because all
|
||||
# the other artwork will be saved under season and show
|
||||
art = self._one_artwork('thumb')
|
||||
if art:
|
||||
artworks['thumb'] = art
|
||||
if full_artwork:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
db_item = plex_db.getItem_byId(self.plex_id())
|
||||
# EXCEPT if you're constructing a listitem
|
||||
if not full_artwork:
|
||||
art = self._one_artwork('thumb')
|
||||
if art:
|
||||
artworks['thumb'] = art
|
||||
return artworks
|
||||
for kodi_artwork, plex_artwork in \
|
||||
v.KODI_TO_PLEX_ARTWORK_EPISODE.iteritems():
|
||||
art = self._one_artwork(plex_artwork)
|
||||
if art:
|
||||
artworks[kodi_artwork] = art
|
||||
if not full_artwork:
|
||||
return artworks
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
try:
|
||||
season_id = db_item[3]
|
||||
season_id = plex_db.getItem_byId(self.plex_id())[3]
|
||||
except TypeError:
|
||||
return artworks
|
||||
# Grab artwork from the season
|
||||
with kodidb.GetKodiDB('video') as kodi_db:
|
||||
season_art = kodi_db.get_art(season_id, v.KODI_TYPE_SEASON)
|
||||
for kodi_art in season_art:
|
||||
artworks['season.%s' % kodi_art] = season_art[kodi_art]
|
||||
# Get the show id
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
db_item = plex_db.getItem_byId(self.grandparent_id())
|
||||
# Grab artwork from the season
|
||||
with kodidb.GetKodiDB('video') as kodi_db:
|
||||
season_art = kodi_db.get_art(season_id, v.KODI_TYPE_SEASON)
|
||||
for kodi_art in season_art:
|
||||
artworks['season.%s' % kodi_art] = season_art[kodi_art]
|
||||
# Get the show id
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
try:
|
||||
show_id = db_item[0]
|
||||
show_id = plex_db.getItem_byKodiId(season_id,
|
||||
v.KODI_TYPE_SEASON)[1]
|
||||
except TypeError:
|
||||
return artworks
|
||||
# Grab more artwork from the show
|
||||
with kodidb.GetKodiDB('video') as kodi_db:
|
||||
show_art = kodi_db.get_art(show_id, v.KODI_TYPE_SHOW)
|
||||
for kodi_art in show_art:
|
||||
artworks['tvshow.%s' % kodi_art] = show_art[kodi_art]
|
||||
# Grab more artwork from the show
|
||||
with kodidb.GetKodiDB('video') as kodi_db:
|
||||
show_art = kodi_db.get_art(show_id, v.KODI_TYPE_SHOW)
|
||||
for kodi_art in show_art:
|
||||
artworks['tvshow.%s' % kodi_art] = show_art[kodi_art]
|
||||
return artworks
|
||||
|
||||
if kodi_id:
|
||||
|
@ -844,7 +856,6 @@ class API(object):
|
|||
external_id = self.retrieve_external_item_id()
|
||||
if external_id is not None:
|
||||
artworks = self.lookup_fanart_tv(external_id[0], artworks)
|
||||
LOG.debug('fanart artworks: %s', artworks)
|
||||
return artworks
|
||||
|
||||
def retrieve_external_item_id(self, collection=False):
|
||||
|
|
|
@ -22,6 +22,10 @@ LOG = getLogger("PLEX." + __name__)
|
|||
# Disable annoying requests warnings
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
ARTWORK_QUEUE = Queue()
|
||||
IMAGE_CACHING_SUSPENDS = ['SUSPEND_LIBRARY_THREAD', 'DB_SCAN', 'STOP_SYNC']
|
||||
if not settings('imageSyncDuringPlayback') == 'true':
|
||||
IMAGE_CACHING_SUSPENDS.append('SUSPEND_SYNC')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
|
@ -33,11 +37,9 @@ def double_urldecode(text):
|
|||
return unquote(unquote(text))
|
||||
|
||||
|
||||
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
|
||||
'DB_SCAN',
|
||||
'STOP_SYNC'])
|
||||
@thread_methods(add_suspends=IMAGE_CACHING_SUSPENDS)
|
||||
class Image_Cache_Thread(Thread):
|
||||
sleep_between = 50
|
||||
sleep_between = 200
|
||||
# Potentially issues with limited number of threads
|
||||
# Hence let Kodi wait till download is successful
|
||||
timeout = (35.1, 35.1)
|
||||
|
@ -47,6 +49,7 @@ class Image_Cache_Thread(Thread):
|
|||
Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
LOG.info("---===### Starting Image_Cache_Thread ###===---")
|
||||
stopped = self.stopped
|
||||
suspended = self.suspended
|
||||
queue = self.queue
|
||||
|
@ -65,6 +68,16 @@ class Image_Cache_Thread(Thread):
|
|||
except Empty:
|
||||
sleep(1000)
|
||||
continue
|
||||
if isinstance(url, ArtworkSyncMessage):
|
||||
if state.IMAGE_SYNC_NOTIFICATIONS:
|
||||
dialog('notification',
|
||||
heading=lang(29999),
|
||||
message=url.message,
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
queue.task_done()
|
||||
continue
|
||||
url = double_urlencode(try_encode(url))
|
||||
sleeptime = 0
|
||||
while True:
|
||||
try:
|
||||
|
@ -116,6 +129,44 @@ class Artwork():
|
|||
if enableTextureCache:
|
||||
queue = ARTWORK_QUEUE
|
||||
|
||||
def cache_major_artwork(self):
|
||||
"""
|
||||
Takes the existing Kodi library and caches posters and fanart.
|
||||
Necessary because otherwise PKC caches artwork e.g. from fanart.tv
|
||||
which basically blocks Kodi from getting needed artwork fast (e.g.
|
||||
while browsing the library)
|
||||
"""
|
||||
if not self.enableTextureCache:
|
||||
return
|
||||
artworks = list()
|
||||
# Get all posters and fanart/background for video and music
|
||||
for kind in ('video', 'music'):
|
||||
connection = kodi_sql(kind)
|
||||
cursor = connection.cursor()
|
||||
for typus in ('poster', 'fanart'):
|
||||
cursor.execute('SELECT url FROM art WHERE type == ?',
|
||||
(typus, ))
|
||||
artworks.extend(cursor.fetchall())
|
||||
connection.close()
|
||||
artworks_to_cache = list()
|
||||
connection = kodi_sql('texture')
|
||||
cursor = connection.cursor()
|
||||
for url in artworks:
|
||||
query = 'SELECT url FROM texture WHERE url == ? LIMIT 1'
|
||||
cursor.execute(query, (url[0], ))
|
||||
if not cursor.fetchone():
|
||||
artworks_to_cache.append(url)
|
||||
connection.close()
|
||||
if not artworks_to_cache:
|
||||
LOG.info('Caching of major images to Kodi texture cache done')
|
||||
return
|
||||
LOG.info('Caching has not been completed - caching %s major images',
|
||||
len(artworks_to_cache))
|
||||
self.queue.put(ArtworkSyncMessage(lang(30006) % len(artworks_to_cache)))
|
||||
for url in artworks_to_cache:
|
||||
self.queue.put(url[0])
|
||||
self.queue.put(ArtworkSyncMessage(lang(30007)))
|
||||
|
||||
def fullTextureCacheSync(self):
|
||||
"""
|
||||
This method will sync all Kodi artwork to textures13.db
|
||||
|
@ -174,10 +225,10 @@ class Artwork():
|
|||
|
||||
def cache_texture(self, url):
|
||||
'''
|
||||
Cache a single image url to the texture cache
|
||||
Cache a single image url to the texture cache. url: unicode
|
||||
'''
|
||||
if url and self.enableTextureCache:
|
||||
self.queue.put(double_urlencode(try_encode(url)))
|
||||
self.queue.put(url)
|
||||
|
||||
def modify_artwork(self, artworks, kodi_id, kodi_type, cursor):
|
||||
"""
|
||||
|
@ -266,3 +317,11 @@ class Artwork():
|
|||
for path in paths:
|
||||
makedirs(try_decode(translatePath("special://thumbnails/%s"
|
||||
% path)))
|
||||
|
||||
|
||||
class ArtworkSyncMessage(object):
|
||||
"""
|
||||
Put in artwork queue to display the message as a Kodi notification
|
||||
"""
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
|
|
@ -369,7 +369,7 @@ def getRecentEpisodes(viewid, mediatype, tagname, limit):
|
|||
append_show_title = settings('RecentTvAppendShow') == 'true'
|
||||
append_sxxexx = settings('RecentTvAppendSeason') == 'true'
|
||||
# First we get a list of all the TV shows - filtered by tag
|
||||
allshowsIds = set()
|
||||
allshowsIds = list()
|
||||
params = {
|
||||
'sort': {'order': "descending", 'method': "dateadded"},
|
||||
'filter': {'operator': "is", 'field': "tag", 'value': "%s" % tagname},
|
||||
|
@ -553,7 +553,7 @@ def getOnDeck(viewid, mediatype, tagname, limit):
|
|||
append_show_title=append_show_title,
|
||||
append_sxxexx=append_sxxexx)
|
||||
if directpaths:
|
||||
url = api.file_path()
|
||||
url = api.file_path(force_first_media=True)
|
||||
else:
|
||||
url = ('plugin://%s.tvshows/?plex_id=%s&plex_type=%s&mode=play&filename=%s'
|
||||
% (v.ADDON_ID,
|
||||
|
@ -561,8 +561,7 @@ def getOnDeck(viewid, mediatype, tagname, limit):
|
|||
api.plex_type(),
|
||||
api.file_name(force_first_media=True)))
|
||||
if api.resume_point():
|
||||
listitem.setProperty('resumetime',
|
||||
str(api.resume_point()))
|
||||
listitem.setProperty('resumetime', str(api.resume_point()))
|
||||
xbmcplugin.addDirectoryItem(
|
||||
handle=HANDLE,
|
||||
url=url,
|
||||
|
|
|
@ -141,7 +141,7 @@ class InitialSetup(object):
|
|||
"""
|
||||
def __init__(self):
|
||||
LOG.debug('Entering initialsetup class')
|
||||
self.server = UserClient().getServer()
|
||||
self.server = UserClient().get_server()
|
||||
self.serverid = settings('plex_machineIdentifier')
|
||||
# Get Plex credentials from settings file, if they exist
|
||||
plexdict = PF.GetPlexLoginFromSettings()
|
||||
|
|
|
@ -1217,6 +1217,7 @@ class Music(Items):
|
|||
"""
|
||||
Adds a single music album
|
||||
children: list of child xml's, so in this case songs
|
||||
scan_children: set to False if you don't want to add children
|
||||
"""
|
||||
kodicursor = self.kodicursor
|
||||
plex_db = self.plex_db
|
||||
|
@ -1224,20 +1225,18 @@ class Music(Items):
|
|||
api = API(item)
|
||||
|
||||
update_item = True
|
||||
itemid = api.plex_id()
|
||||
if not itemid:
|
||||
plex_id = api.plex_id()
|
||||
if not plex_id:
|
||||
LOG.error('Error processing Album, skipping')
|
||||
return
|
||||
plex_dbitem = plex_db.getItem_byId(itemid)
|
||||
plex_dbitem = plex_db.getItem_byId(plex_id)
|
||||
try:
|
||||
albumid = plex_dbitem[0]
|
||||
album_id = plex_dbitem[0]
|
||||
except TypeError:
|
||||
# Albumid not found
|
||||
update_item = False
|
||||
|
||||
# The album details #####
|
||||
lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
dateadded = api.date_created()
|
||||
userdata = api.userdata()
|
||||
checksum = api.checksum()
|
||||
|
||||
|
@ -1269,22 +1268,22 @@ class Music(Items):
|
|||
|
||||
# UPDATE THE ALBUM #####
|
||||
if update_item:
|
||||
LOG.info("UPDATE album itemid: %s - Name: %s", itemid, name)
|
||||
LOG.info("UPDATE album plex_id: %s - Name: %s", plex_id, name)
|
||||
# Update the checksum in plex table
|
||||
plex_db.updateReference(itemid, checksum)
|
||||
plex_db.updateReference(plex_id, checksum)
|
||||
|
||||
# OR ADD THE ALBUM #####
|
||||
else:
|
||||
LOG.info("ADD album itemid: %s - Name: %s", itemid, name)
|
||||
LOG.info("ADD album plex_id: %s - Name: %s", plex_id, name)
|
||||
# safety checks: It looks like plex supports the same artist
|
||||
# multiple times.
|
||||
# Kodi doesn't allow that. In case that happens we just merge the
|
||||
# artist entries.
|
||||
albumid = self.kodi_db.addAlbum(name, musicBrainzId)
|
||||
album_id = self.kodi_db.addAlbum(name, musicBrainzId)
|
||||
# Create the reference in plex table
|
||||
plex_db.addReference(itemid,
|
||||
plex_db.addReference(plex_id,
|
||||
v.PLEX_TYPE_ALBUM,
|
||||
albumid,
|
||||
album_id,
|
||||
v.KODI_TYPE_ALBUM,
|
||||
view_id=viewid,
|
||||
checksum=checksum)
|
||||
|
@ -1302,7 +1301,7 @@ class Music(Items):
|
|||
kodicursor.execute(query, (artistname, year, self.genre, bio,
|
||||
thumb, rating, lastScraped,
|
||||
v.KODI_TYPE_ALBUM, studio,
|
||||
self.compilation, albumid))
|
||||
self.compilation, album_id))
|
||||
elif v.KODIVERSION == 17:
|
||||
# Kodi Krypton
|
||||
query = '''
|
||||
|
@ -1315,7 +1314,7 @@ class Music(Items):
|
|||
kodicursor.execute(query, (artistname, year, self.genre, bio,
|
||||
thumb, rating, lastScraped,
|
||||
v.KODI_TYPE_ALBUM, studio,
|
||||
self.compilation, albumid))
|
||||
self.compilation, album_id))
|
||||
elif v.KODIVERSION == 16:
|
||||
# Kodi Jarvis
|
||||
query = '''
|
||||
|
@ -1328,71 +1327,46 @@ class Music(Items):
|
|||
kodicursor.execute(query, (artistname, year, self.genre, bio,
|
||||
thumb, rating, lastScraped,
|
||||
v.KODI_TYPE_ALBUM, studio,
|
||||
self.compilation, albumid))
|
||||
self.compilation, album_id))
|
||||
|
||||
# Associate the parentid for plex reference
|
||||
parent_id = api.parent_plex_id()
|
||||
artist_id = None
|
||||
if parent_id is not None:
|
||||
plex_dbartist = plex_db.getItem_byId(parent_id)
|
||||
try:
|
||||
artistid = plex_dbartist[0]
|
||||
artist_id = plex_db.getItem_byId(parent_id)[0]
|
||||
except TypeError:
|
||||
LOG.info('Artist %s does not exist in plex database',
|
||||
parent_id)
|
||||
LOG.info('Artist %s does not yet exist in Plex DB', parent_id)
|
||||
artist = GetPlexMetadata(parent_id)
|
||||
# Item may not be an artist, verification necessary.
|
||||
if artist is not None and artist != 401:
|
||||
if artist[0].attrib.get('type') == v.PLEX_TYPE_ARTIST:
|
||||
# Update with the parent_id, for remove reference
|
||||
plex_db.addReference(parent_id,
|
||||
v.PLEX_TYPE_ARTIST,
|
||||
parent_id,
|
||||
v.KODI_TYPE_ARTIST,
|
||||
view_id=viewid)
|
||||
plex_db.updateParentId(itemid, parent_id)
|
||||
else:
|
||||
# Update plex reference with the artistid
|
||||
plex_db.updateParentId(itemid, artistid)
|
||||
|
||||
# Assign main artists to album
|
||||
# Plex unfortunately only supports 1 artist :-(
|
||||
artist_id = parent_id
|
||||
plex_dbartist = plex_db.getItem_byId(artist_id)
|
||||
try:
|
||||
artistid = plex_dbartist[0]
|
||||
except TypeError:
|
||||
# Artist does not exist in plex database, create the reference
|
||||
LOG.info('Artist %s does not exist in Plex database', artist_id)
|
||||
artist = GetPlexMetadata(artist_id)
|
||||
if artist is not None and artist != 401:
|
||||
self.add_updateArtist(artist[0])
|
||||
plex_dbartist = plex_db.getItem_byId(artist_id)
|
||||
artistid = plex_dbartist[0]
|
||||
else:
|
||||
# Best take this name over anything else.
|
||||
query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?"
|
||||
kodicursor.execute(query, (artistname, artistid,))
|
||||
LOG.info("UPDATE artist: strArtist: %s, idArtist: %s",
|
||||
artistname, artistid)
|
||||
|
||||
try:
|
||||
artist[0].attrib
|
||||
except (TypeError, IndexError, AttributeError):
|
||||
LOG.error('Could not get artist xml for %s', parent_id)
|
||||
else:
|
||||
self.add_updateArtist(artist[0])
|
||||
plex_dbartist = plex_db.getItem_byId(parent_id)
|
||||
try:
|
||||
artist_id = plex_dbartist[0]
|
||||
except TypeError:
|
||||
LOG.error('Adding artist failed for %s', parent_id)
|
||||
# Update plex reference with the artist_id
|
||||
plex_db.updateParentId(plex_id, artist_id)
|
||||
# Add artist to album
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist)
|
||||
VALUES (?, ?, ?)
|
||||
'''
|
||||
kodicursor.execute(query, (artistid, albumid, artistname))
|
||||
kodicursor.execute(query, (artist_id, album_id, artistname))
|
||||
# Update discography
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear)
|
||||
VALUES (?, ?, ?)
|
||||
'''
|
||||
kodicursor.execute(query, (artistid, name, year))
|
||||
# Update plex reference with parentid
|
||||
plex_db.updateParentId(artist_id, albumid)
|
||||
kodicursor.execute(query, (artist_id, name, year))
|
||||
if v.KODIVERSION < 18:
|
||||
self.kodi_db.addMusicGenres(albumid, self.genres, v.KODI_TYPE_ALBUM)
|
||||
self.kodi_db.addMusicGenres(album_id, self.genres, v.KODI_TYPE_ALBUM)
|
||||
# Update artwork
|
||||
artwork.modify_artwork(artworks, albumid, v.KODI_TYPE_ALBUM, kodicursor)
|
||||
artwork.modify_artwork(artworks, album_id, v.KODI_TYPE_ALBUM, kodicursor)
|
||||
# Add all children - all tracks
|
||||
if scan_children:
|
||||
for child in children:
|
||||
|
|
|
@ -50,7 +50,8 @@ STATE_SETTINGS = {
|
|||
'remapSMBphotoNew': 'remapSMBphotoNew',
|
||||
'enableMusic': 'ENABLE_MUSIC',
|
||||
'forceReloadSkinOnPlaybackStop': 'FORCE_RELOAD_SKIN',
|
||||
'fetch_pms_item_number': 'FETCH_PMS_ITEM_NUMBER'
|
||||
'fetch_pms_item_number': 'FETCH_PMS_ITEM_NUMBER',
|
||||
'imageSyncNotifications': 'IMAGE_SYNC_NOTIFICATIONS'
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
|
|
|
@ -16,6 +16,7 @@ from downloadutils import DownloadUtils as DU
|
|||
import itemtypes
|
||||
import plexdb_functions as plexdb
|
||||
import kodidb_functions as kodidb
|
||||
import artwork
|
||||
import videonodes
|
||||
import variables as v
|
||||
|
||||
|
@ -1453,7 +1454,6 @@ class LibrarySync(Thread):
|
|||
elif state.RUN_LIB_SCAN == 'textures':
|
||||
state.DB_SCAN = True
|
||||
window('plex_dbScan', value="true")
|
||||
import artwork
|
||||
artwork.Artwork().fullTextureCacheSync()
|
||||
window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
|
@ -1517,8 +1517,6 @@ class LibrarySync(Thread):
|
|||
last_time_sync = utils.unix_timestamp()
|
||||
window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
# Start the fanart download thread
|
||||
self.fanartthread.start()
|
||||
|
||||
while not self.stopped():
|
||||
# In the event the server goes offline
|
||||
|
@ -1544,6 +1542,7 @@ class LibrarySync(Thread):
|
|||
initial_sync_done = True
|
||||
kodi_db_version_checked = True
|
||||
last_sync = utils.unix_timestamp()
|
||||
self.fanartthread.start()
|
||||
else:
|
||||
LOG.error('Initial start-up full sync unsuccessful')
|
||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
||||
|
@ -1587,6 +1586,8 @@ class LibrarySync(Thread):
|
|||
if settings('FanartTV') == 'true':
|
||||
self.sync_fanart()
|
||||
LOG.info('Done initial sync on Kodi startup')
|
||||
artwork.Artwork().cache_major_artwork()
|
||||
self.fanartthread.start()
|
||||
else:
|
||||
LOG.info('Startup sync has not yet been successful')
|
||||
window('plex_dbScan', clear=True)
|
||||
|
|
|
@ -4,12 +4,31 @@ from cPickle import dumps, loads
|
|||
|
||||
from xbmcgui import Window
|
||||
from xbmc import log, LOGDEBUG
|
||||
|
||||
###############################################################################
|
||||
WINDOW = Window(10000)
|
||||
PREFIX = 'PLEX.%s: ' % __name__
|
||||
###############################################################################
|
||||
|
||||
|
||||
def try_encode(input_str, encoding='utf-8'):
|
||||
"""
|
||||
Will try to encode input_str (in unicode) to encoding. This possibly
|
||||
fails with e.g. Android TV's Python, which does not accept arguments for
|
||||
string.encode()
|
||||
|
||||
COPY to avoid importing utils on calling default.py
|
||||
"""
|
||||
if isinstance(input_str, str):
|
||||
# already encoded
|
||||
return input_str
|
||||
try:
|
||||
input_str = input_str.encode(encoding, "ignore")
|
||||
except TypeError:
|
||||
input_str = input_str.encode()
|
||||
return input_str
|
||||
|
||||
|
||||
def pickl_window(property, value=None, clear=False):
|
||||
"""
|
||||
Get or set window property - thread safe! For use with Pickle
|
||||
|
@ -20,7 +39,7 @@ def pickl_window(property, value=None, clear=False):
|
|||
elif value is not None:
|
||||
WINDOW.setProperty(property, value)
|
||||
else:
|
||||
return WINDOW.getProperty(property)
|
||||
return try_encode(WINDOW.getProperty(property))
|
||||
|
||||
|
||||
def pickle_me(obj, window_var='plex_result'):
|
||||
|
|
|
@ -39,6 +39,8 @@ FORCE_RELOAD_SKIN = True
|
|||
# Stemming from the PKC settings.xml
|
||||
# Shall we show Kodi dialogs when synching?
|
||||
SYNC_DIALOG = True
|
||||
# Shall Kodi show dialogs for syncing/caching images? (e.g. images left to sync)
|
||||
IMAGE_SYNC_NOTIFICATIONS = True
|
||||
# Is synching of Plex music enabled?
|
||||
ENABLE_MUSIC = True
|
||||
# How often shall we sync?
|
||||
|
|
|
@ -22,7 +22,9 @@ LOG = getLogger("PLEX." + __name__)
|
|||
|
||||
@thread_methods(add_suspends=['SUSPEND_USER_CLIENT'])
|
||||
class UserClient(Thread):
|
||||
|
||||
"""
|
||||
Manage Plex users
|
||||
"""
|
||||
# Borg - multiple instances, shared state
|
||||
__shared_state = {}
|
||||
|
||||
|
@ -32,100 +34,98 @@ class UserClient(Thread):
|
|||
self.auth = True
|
||||
self.retry = 0
|
||||
|
||||
self.currUser = None
|
||||
self.currServer = None
|
||||
self.currToken = None
|
||||
self.HasAccess = True
|
||||
self.AdditionalUser = []
|
||||
self.user = None
|
||||
self.has_access = True
|
||||
|
||||
self.userSettings = None
|
||||
self.server = None
|
||||
self.server_name = None
|
||||
self.machine_identifier = None
|
||||
self.token = None
|
||||
self.ssl = None
|
||||
self.sslcert = None
|
||||
|
||||
self.addon = xbmcaddon.Addon()
|
||||
self.doUtils = DU()
|
||||
self.do_utils = None
|
||||
|
||||
Thread.__init__(self)
|
||||
|
||||
def getUsername(self):
|
||||
def get_server(self):
|
||||
"""
|
||||
Returns username as unicode
|
||||
Get the current PMS' URL
|
||||
"""
|
||||
username = settings('username')
|
||||
if not username:
|
||||
LOG.debug("No username saved, trying to get Plex username")
|
||||
username = settings('plexLogin')
|
||||
if not username:
|
||||
LOG.debug("Also no Plex username found")
|
||||
return ""
|
||||
return username
|
||||
|
||||
def getServer(self, prefix=True):
|
||||
# Original host
|
||||
self.servername = settings('plex_servername')
|
||||
HTTPS = settings('https') == "true"
|
||||
self.server_name = settings('plex_servername')
|
||||
https = settings('https') == "true"
|
||||
host = settings('ipaddress')
|
||||
port = settings('port')
|
||||
self.machineIdentifier = settings('plex_machineIdentifier')
|
||||
|
||||
server = host + ":" + port
|
||||
|
||||
self.machine_identifier = settings('plex_machineIdentifier')
|
||||
if not host:
|
||||
LOG.debug("No server information saved.")
|
||||
return False
|
||||
|
||||
server = host + ":" + port
|
||||
# If https is true
|
||||
if prefix and HTTPS:
|
||||
if https:
|
||||
server = "https://%s" % server
|
||||
# If https is false
|
||||
elif prefix and not HTTPS:
|
||||
else:
|
||||
server = "http://%s" % server
|
||||
# User entered IP; we need to get the machineIdentifier
|
||||
if self.machineIdentifier == '' and prefix is True:
|
||||
self.machineIdentifier = PF.GetMachineIdentifier(server)
|
||||
if self.machineIdentifier is None:
|
||||
self.machineIdentifier = ''
|
||||
settings('plex_machineIdentifier', value=self.machineIdentifier)
|
||||
if not self.machine_identifier:
|
||||
self.machine_identifier = PF.GetMachineIdentifier(server)
|
||||
if not self.machine_identifier:
|
||||
self.machine_identifier = ''
|
||||
settings('plex_machineIdentifier', value=self.machine_identifier)
|
||||
LOG.debug('Returning active server: %s', server)
|
||||
return server
|
||||
|
||||
def getSSLverify(self):
|
||||
# Verify host certificate
|
||||
@staticmethod
|
||||
def get_ssl_verify():
|
||||
"""
|
||||
Do we need to verify the SSL certificate? Return None if that is the
|
||||
case, else False
|
||||
"""
|
||||
return None if settings('sslverify') == 'true' else False
|
||||
|
||||
def getSSL(self):
|
||||
# Client side certificate
|
||||
@staticmethod
|
||||
def get_ssl_certificate():
|
||||
"""
|
||||
Client side certificate
|
||||
"""
|
||||
return None if settings('sslcert') == 'None' \
|
||||
else settings('sslcert')
|
||||
|
||||
def setUserPref(self):
|
||||
def set_user_prefs(self):
|
||||
"""
|
||||
Load a user's profile picture
|
||||
"""
|
||||
LOG.debug('Setting user preferences')
|
||||
# Only try to get user avatar if there is a token
|
||||
if self.currToken:
|
||||
url = PF.GetUserArtworkURL(self.currUser)
|
||||
if self.token:
|
||||
url = PF.GetUserArtworkURL(self.user)
|
||||
if url:
|
||||
window('PlexUserImage', value=url)
|
||||
# Set resume point max
|
||||
# url = "{server}/emby/System/Configuration?format=json"
|
||||
# result = doUtils.downloadUrl(url)
|
||||
|
||||
def hasAccess(self):
|
||||
@staticmethod
|
||||
def check_access():
|
||||
# Plex: always return True for now
|
||||
return True
|
||||
|
||||
def loadCurrUser(self, username, userId, usertoken, authenticated=False):
|
||||
def load_user(self, username, user_id, usertoken, authenticated=False):
|
||||
"""
|
||||
Load the current user's details for PKC
|
||||
"""
|
||||
LOG.debug('Loading current user')
|
||||
doUtils = self.doUtils
|
||||
|
||||
self.currToken = usertoken
|
||||
self.currServer = self.getServer()
|
||||
self.ssl = self.getSSLverify()
|
||||
self.sslcert = self.getSSL()
|
||||
self.token = usertoken
|
||||
self.server = self.get_server()
|
||||
self.ssl = self.get_ssl_verify()
|
||||
self.sslcert = self.get_ssl_certificate()
|
||||
|
||||
if authenticated is False:
|
||||
if self.currServer is None:
|
||||
if self.server is None:
|
||||
return False
|
||||
LOG.debug('Testing validity of current token')
|
||||
res = PF.check_connection(self.currServer,
|
||||
token=self.currToken,
|
||||
res = PF.check_connection(self.server,
|
||||
token=self.token,
|
||||
verifySSL=self.ssl)
|
||||
if res is False:
|
||||
# PMS probably offline
|
||||
|
@ -138,7 +138,7 @@ class UserClient(Thread):
|
|||
return False
|
||||
|
||||
# Set to windows property
|
||||
state.PLEX_USER_ID = userId or None
|
||||
state.PLEX_USER_ID = user_id or None
|
||||
state.PLEX_USERNAME = username
|
||||
# This is the token for the current PMS (might also be '')
|
||||
window('pms_token', value=usertoken)
|
||||
|
@ -150,9 +150,9 @@ class UserClient(Thread):
|
|||
window('plex_restricteduser', value=settings('plex_restricteduser'))
|
||||
state.RESTRICTED_USER = True \
|
||||
if settings('plex_restricteduser') == 'true' else False
|
||||
window('pms_server', value=self.currServer)
|
||||
window('plex_machineIdentifier', value=self.machineIdentifier)
|
||||
window('plex_servername', value=self.servername)
|
||||
window('pms_server', value=self.server)
|
||||
window('plex_machineIdentifier', value=self.machine_identifier)
|
||||
window('plex_servername', value=self.server_name)
|
||||
window('plex_authenticated', value='true')
|
||||
state.AUTHENTICATED = True
|
||||
|
||||
|
@ -166,19 +166,22 @@ class UserClient(Thread):
|
|||
if settings('force_transcode_pix') == "1" else 'false')
|
||||
|
||||
# Start DownloadUtils session
|
||||
doUtils.startSession(reset=True)
|
||||
# self.getAdditionalUsers()
|
||||
self.do_utils = DU()
|
||||
self.do_utils.startSession(reset=True)
|
||||
# Set user preferences in settings
|
||||
self.currUser = username
|
||||
self.setUserPref()
|
||||
self.user = username
|
||||
self.set_user_prefs()
|
||||
|
||||
# Writing values to settings file
|
||||
settings('username', value=username)
|
||||
settings('userid', value=userId)
|
||||
settings('userid', value=user_id)
|
||||
settings('accessToken', value=usertoken)
|
||||
return True
|
||||
|
||||
def authenticate(self):
|
||||
"""
|
||||
Authenticate the current user
|
||||
"""
|
||||
LOG.debug('Authenticating user')
|
||||
|
||||
# Give attempts at entering password / selecting user
|
||||
|
@ -198,7 +201,7 @@ class UserClient(Thread):
|
|||
LOG.error("Error, no settings.xml found.")
|
||||
self.auth = False
|
||||
return False
|
||||
server = self.getServer()
|
||||
server = self.get_server()
|
||||
# If there is no server we can connect to
|
||||
if not server:
|
||||
LOG.info("Missing server information.")
|
||||
|
@ -213,10 +216,10 @@ class UserClient(Thread):
|
|||
# Found a user in the settings, try to authenticate
|
||||
if username and enforceLogin == 'false':
|
||||
LOG.debug('Trying to authenticate with old settings')
|
||||
answ = self.loadCurrUser(username,
|
||||
userId,
|
||||
usertoken,
|
||||
authenticated=False)
|
||||
answ = self.load_user(username,
|
||||
userId,
|
||||
usertoken,
|
||||
authenticated=False)
|
||||
if answ is True:
|
||||
# SUCCESS: loaded a user from the settings
|
||||
return True
|
||||
|
@ -248,7 +251,7 @@ class UserClient(Thread):
|
|||
userId = ''
|
||||
usertoken = ''
|
||||
|
||||
if self.loadCurrUser(username, userId, usertoken, authenticated=False):
|
||||
if self.load_user(username, userId, usertoken, authenticated=False):
|
||||
# SUCCESS: loaded a user from the settings
|
||||
return True
|
||||
# Something went wrong, try again
|
||||
|
@ -256,9 +259,12 @@ class UserClient(Thread):
|
|||
self.retry += 1
|
||||
return False
|
||||
|
||||
def resetClient(self):
|
||||
def reset_client(self):
|
||||
"""
|
||||
Reset all user settings
|
||||
"""
|
||||
LOG.debug("Reset UserClient authentication.")
|
||||
self.doUtils.stopSession()
|
||||
self.do_utils.stopSession()
|
||||
|
||||
window('plex_authenticated', clear=True)
|
||||
state.AUTHENTICATED = False
|
||||
|
@ -279,13 +285,16 @@ class UserClient(Thread):
|
|||
settings('userid', value='')
|
||||
settings('accessToken', value='')
|
||||
|
||||
self.currToken = None
|
||||
self.token = None
|
||||
self.auth = True
|
||||
self.currUser = None
|
||||
self.user = None
|
||||
|
||||
self.retry = 0
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Do the work
|
||||
"""
|
||||
LOG.info("----===## Starting UserClient ##===----")
|
||||
stopped = self.stopped
|
||||
suspended = self.suspended
|
||||
|
@ -299,19 +308,14 @@ class UserClient(Thread):
|
|||
sleep(500)
|
||||
continue
|
||||
|
||||
# Verify the connection status to server
|
||||
elif state.PMS_STATUS == "restricted":
|
||||
# Parental control is restricting access
|
||||
self.HasAccess = False
|
||||
|
||||
elif state.PMS_STATUS == "401":
|
||||
# Unauthorized access, revoke token
|
||||
state.PMS_STATUS = 'Auth'
|
||||
window('plex_serverStatus', value='Auth')
|
||||
self.resetClient()
|
||||
self.reset_client()
|
||||
sleep(3000)
|
||||
|
||||
if self.auth and (self.currUser is None):
|
||||
if self.auth and (self.user is None):
|
||||
# Try to authenticate user
|
||||
if not state.PMS_STATUS or state.PMS_STATUS == "Auth":
|
||||
# Set auth flag because we no longer need
|
||||
|
@ -320,16 +324,16 @@ class UserClient(Thread):
|
|||
if self.authenticate():
|
||||
# Successfully authenticated and loaded a user
|
||||
LOG.info("Successfully authenticated!")
|
||||
LOG.info("Current user: %s", self.currUser)
|
||||
LOG.info("Current user: %s", self.user)
|
||||
LOG.info("Current userId: %s", state.PLEX_USER_ID)
|
||||
self.retry = 0
|
||||
state.SUSPEND_LIBRARY_THREAD = False
|
||||
window('plex_serverStatus', clear=True)
|
||||
state.PMS_STATUS = False
|
||||
|
||||
if not self.auth and (self.currUser is None):
|
||||
if not self.auth and (self.user is None):
|
||||
# Loop if no server found
|
||||
server = self.getServer()
|
||||
server = self.get_server()
|
||||
|
||||
# The status Stop is for when user cancelled password dialog.
|
||||
# Or retried too many times
|
||||
|
|
|
@ -325,6 +325,13 @@ KODI_TO_PLEX_ARTWORK = {
|
|||
'fanart': 'art'
|
||||
}
|
||||
|
||||
KODI_TO_PLEX_ARTWORK_EPISODE = {
|
||||
'thumb': 'thumb',
|
||||
'poster': 'grandparentThumb',
|
||||
'banner': 'banner',
|
||||
'fanart': 'art'
|
||||
}
|
||||
|
||||
# Might be implemented in the future: 'icon', 'landscape' (16:9)
|
||||
ALL_KODI_ARTWORK = (
|
||||
'thumb',
|
||||
|
|
|
@ -125,6 +125,8 @@
|
|||
<setting id="enableTextureCache" label="30512" type="bool" default="true" /> <!-- Force Artwork Caching -->
|
||||
<setting id="FanartTV" label="30539" type="bool" default="false" /><!-- Download additional art from FanArtTV -->
|
||||
<setting label="39222" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=fanart)" option="close" visible="eq(-1,true)" subsetting="true" /> <!-- Look for missing fanart on FanartTV now -->
|
||||
<setting id="imageSyncNotifications" label="30008" type="bool" default="true" /><!-- Enable notifications for image caching -->
|
||||
<setting id="imageSyncDuringPlayback" label="30009" type="bool" default="true" /><!-- Enable image caching during Kodi playback (restart Kodi!) -->
|
||||
<setting label="39020" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=texturecache)" option="close" /> <!-- Cache all images to Kodi texture cache now -->
|
||||
</category>
|
||||
<!--
|
||||
|
|
12
service.py
12
service.py
|
@ -135,7 +135,7 @@ class Service():
|
|||
if window('plex_online') == "true":
|
||||
# Plex server is online
|
||||
# Verify if user is set and has access to the server
|
||||
if (self.user.currUser is not None) and self.user.HasAccess:
|
||||
if (self.user.user is not None) and self.user.has_access:
|
||||
if not self.kodimonitor_running:
|
||||
# Start up events
|
||||
self.warn_auth = True
|
||||
|
@ -145,7 +145,7 @@ class Service():
|
|||
dialog('notification',
|
||||
lang(29999),
|
||||
"%s %s" % (lang(33000),
|
||||
self.user.currUser),
|
||||
self.user.user),
|
||||
icon='{plex}',
|
||||
time=2000,
|
||||
sound=False)
|
||||
|
@ -178,7 +178,7 @@ class Service():
|
|||
self.image_cache_thread_running = True
|
||||
self.image_cache_thread.start()
|
||||
else:
|
||||
if (self.user.currUser is None) and self.warn_auth:
|
||||
if (self.user.user is None) and self.warn_auth:
|
||||
# Alert user is not authenticated and suppress future
|
||||
# warning
|
||||
self.warn_auth = False
|
||||
|
@ -187,9 +187,9 @@ class Service():
|
|||
# User access is restricted.
|
||||
# Keep verifying until access is granted
|
||||
# unless server goes offline or Kodi is shut down.
|
||||
while self.user.HasAccess is False:
|
||||
while self.user.has_access is False:
|
||||
# Verify access with an API call
|
||||
self.user.hasAccess()
|
||||
self.user.check_access()
|
||||
|
||||
if window('plex_online') != "true":
|
||||
# Server went offline
|
||||
|
@ -202,7 +202,7 @@ class Service():
|
|||
# Wait until Plex server is online
|
||||
# or Kodi is shut down.
|
||||
while not self.__stop_PKC():
|
||||
server = self.user.getServer()
|
||||
server = self.user.get_server()
|
||||
if server is False:
|
||||
# No server info set in add-on settings
|
||||
pass
|
||||
|
|
Loading…
Reference in a new issue