Merge branch 'hotfixes' of https://github.com/croneter/PlexKodiConnect into hotfixes

This commit is contained in:
croneter 2018-02-28 07:02:09 +01:00
commit 6c851bd3a6
12 changed files with 672 additions and 802 deletions

View file

@ -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) [![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.3-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.4-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) [![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) [![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.0.3" provider-name="croneter"> <addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.0.4" provider-name="croneter">
<requires> <requires>
<import addon="xbmc.python" version="2.1.0"/> <import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.9.1" /> <import addon="script.module.requests" version="2.9.1" />
@ -61,7 +61,22 @@
<summary lang="da_DK">Indbygget Integration af Plex i Kodi</summary> <summary lang="da_DK">Indbygget Integration af Plex i Kodi</summary>
<description lang="da_DK">Tilslut Kodi til din Plex Media Server. Dette plugin forudsætter, at du administrere alle dine videoer med Plex (og ikke med Kodi). Du kan miste data som allerede er gemt i Kodi video og musik-databaser (dette plugin ændrer direkte i dem). Brug på eget ansvar!</description> <description lang="da_DK">Tilslut Kodi til din Plex Media Server. Dette plugin forudsætter, at du administrere alle dine videoer med Plex (og ikke med Kodi). Du kan miste data som allerede er gemt i Kodi video og musik-databaser (dette plugin ændrer direkte i dem). Brug på eget ansvar!</description>
<disclaimer lang="da_DK">Brug på eget ansvar</disclaimer> <disclaimer lang="da_DK">Brug på eget ansvar</disclaimer>
<news>version 2.0.3 (beta only): <news>version 2.0.4 (beta only):
- WARNING: You will need to reset the Kodi database!
- Many improvements to the Kodi database handling which should get rid of some weird bugs
- Many improvements to playback startup
- Fix info screen and actors not working
- Fix Companion displaying and selecting wrong subtitle
- Don't cache subtitles if direct playing
- Wipe all existing resume point, e.g. on user switch
- Don't mess with Kodi's screensaver settings
- Inhibit idle shutdown only during initial sync
- Fix KeyError for server discovery
- Increase Python requests dependency to version 2.9.1
- Re-introduce PlexKodiConnect dependency add-ons for movies and tv shows
- And a lot of other stuff
version 2.0.3 (beta only):
- Fix Alexa playback - Fix Alexa playback
- Fix Kodi boot loop - Fix Kodi boot loop
- Fix playback being reported to the wrong Plex user - Fix playback being reported to the wrong Plex user

View file

@ -1,3 +1,18 @@
version 2.0.4 (beta only):
- WARNING: You will need to reset the Kodi database!
- Many improvements to the Kodi database handling which should get rid of some weird bugs
- Many improvements to playback startup
- Fix info screen and actors not working
- Fix Companion displaying and selecting wrong subtitle
- Don't cache subtitles if direct playing
- Wipe all existing resume point, e.g. on user switch
- Don't mess with Kodi's screensaver settings
- Inhibit idle shutdown only during initial sync
- Fix KeyError for server discovery
- Increase Python requests dependency to version 2.9.1
- Re-introduce PlexKodiConnect dependency add-ons for movies and tv shows
- And a lot of other stuff
version 2.0.3 (beta only): version 2.0.3 (beta only):
- Fix Alexa playback - Fix Alexa playback
- Fix Kodi boot loop - Fix Kodi boot loop

View file

@ -1670,10 +1670,6 @@ msgctxt "#39213"
msgid "is offline" msgid "is offline"
msgstr "" msgstr ""
msgctxt "#39214"
msgid "Even though we signed in to plex.tv, we could not authorize for PMS"
msgstr ""
msgctxt "#39215" msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:" msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "" msgstr ""

View file

@ -41,7 +41,7 @@ from xbmcvfs import exists
import clientinfo as client import clientinfo as client
from downloadutils import DownloadUtils as DU from downloadutils import DownloadUtils as DU
from utils import window, settings, language as lang, try_decode, try_encode, \ from utils import window, settings, language as lang, try_decode, try_encode, \
unix_date_to_kodi, exists_dir, slugify, dialog unix_date_to_kodi, exists_dir, slugify, dialog, escape_html
import PlexFunctions as PF import PlexFunctions as PF
import plexdb_functions as plexdb import plexdb_functions as plexdb
import variables as v import variables as v
@ -337,22 +337,14 @@ class API(object):
""" """
people = [] people = []
for child in self.item: for child in self.item:
if child.tag in PEOPLE_OF_INTEREST.keys(): if child.tag in PEOPLE_OF_INTEREST:
name = child.attrib['tag']
name_id = child.attrib['id']
typus = PEOPLE_OF_INTEREST[child.tag]
url = child.get('thumb')
role = child.get('role')
people.append({ people.append({
'Name': name, 'Name': child.attrib['tag'],
'Type': typus, 'Type': PEOPLE_OF_INTEREST[child.tag],
'Id': name_id, 'Id': child.attrib['id'],
'imageurl': url 'imageurl': child.get('thumb'),
'Role': child.get('role')
}) })
if url:
people[-1].update({'imageurl': url})
if role:
people[-1].update({'Role': role})
return people return people
def genre_list(self): def genre_list(self):
@ -365,6 +357,17 @@ class API(object):
genre.append(child.attrib['tag']) genre.append(child.attrib['tag'])
return genre return genre
def guid_html_escaped(self):
"""
Returns the 'guid' attribute, e.g.
'com.plexapp.agents.thetvdb://76648/2/4?lang=en'
as an HTML-escaped string or None
"""
answ = self.item.get('guid')
if answ is not None:
answ = escape_html(answ)
return answ
def provider(self, providername=None): def provider(self, providername=None):
""" """
providername: e.g. 'imdb', 'tvdb' providername: e.g. 'imdb', 'tvdb'
@ -642,14 +645,15 @@ class API(object):
""" """
Returns the ratingKey (plex_id) of the trailer or None Returns the ratingKey (plex_id) of the trailer or None
""" """
for extra in self.item.iterfind('Extras'): for extras in self.item.iterfind('Extras'):
try: for extra in extras:
typus = int(extra.attrib['extraType']) try:
except (KeyError, TypeError): typus = int(extra.attrib['extraType'])
typus = None except (KeyError, TypeError):
if typus != 1: typus = None
continue if typus != 1:
return extra.get('ratingKey') continue
return extra.get('ratingKey')
def mediastreams(self): def mediastreams(self):
""" """

View file

@ -310,15 +310,9 @@ class Artwork():
self.cacheTexture(imageUrl) self.cacheTexture(imageUrl)
def deleteArtwork(self, kodiId, mediaType, cursor): def deleteArtwork(self, kodiId, mediaType, cursor):
query = ' '.join(( query = 'SELECT url FROM art WHERE media_id = ? AND media_type = ?'
"SELECT url",
"FROM art",
"WHERE media_id = ?",
"AND media_type = ?"
))
cursor.execute(query, (kodiId, mediaType,)) cursor.execute(query, (kodiId, mediaType,))
rows = cursor.fetchall() for row in cursor.fetchall():
for row in rows:
self.deleteCachedArtwork(row[0]) self.deleteCachedArtwork(row[0])
def deleteCachedArtwork(self, url): def deleteCachedArtwork(self, url):
@ -330,7 +324,7 @@ class Artwork():
(url,)) (url,))
cachedurl = cursor.fetchone()[0] cachedurl = cursor.fetchone()[0]
except TypeError: except TypeError:
LOG.info("Could not find cached url.") LOG.debug("Could not find cached url.")
else: else:
# Delete thumbnail as well as the entry # Delete thumbnail as well as the entry
path = translatePath("special://thumbnails/%s" % cachedurl) path = translatePath("special://thumbnails/%s" % cachedurl)

View file

@ -317,7 +317,6 @@ class InitialSetup(object):
Returns server or None if unsuccessful Returns server or None if unsuccessful
""" """
https_updated = False https_updated = False
checked_plex_tv = False
server = None server = None
while True: while True:
if https_updated is False: if https_updated is False:
@ -340,25 +339,6 @@ class InitialSetup(object):
server['scheme'] = 'https' server['scheme'] = 'https'
https_updated = True https_updated = True
continue continue
if chk == 401:
LOG.warn('Not yet authorized for Plex server %s',
server['name'])
if self.check_plex_tv_sign_in() is True:
if checked_plex_tv is False:
# Try again
checked_plex_tv = True
https_updated = False
continue
else:
LOG.warn('Not authorized even though we are signed '
' in to plex.tv correctly')
dialog('ok',
lang(29999),
'%s %s' % (lang(39214),
try_encode(server['name'])))
return
else:
return
# Problems connecting # Problems connecting
elif chk >= 400 or chk is False: elif chk >= 400 or chk is False:
LOG.warn('Problems connecting to server %s. chk is %s', LOG.warn('Problems connecting to server %s. chk is %s',

View file

@ -48,7 +48,7 @@ class Items(object):
self.kodiconn = kodi_sql('video') self.kodiconn = kodi_sql('video')
self.kodicursor = self.kodiconn.cursor() self.kodicursor = self.kodiconn.cursor()
self.plex_db = plexdb.Plex_DB_Functions(self.plexcursor) self.plex_db = plexdb.Plex_DB_Functions(self.plexcursor)
self.kodi_db = kodidb.Kodidb_Functions(self.kodicursor) self.kodi_db = kodidb.KodiDBMethods(self.kodicursor)
return self return self
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
@ -326,6 +326,7 @@ class Movies(Items):
"imdb", "imdb",
uniqueid) uniqueid)
else: else:
self.kodi_db.remove_uniqueid(movieid, v.KODI_TYPE_MOVIE)
uniqueid = -1 uniqueid = -1
query = ''' query = '''
UPDATE movie UPDATE movie
@ -360,7 +361,8 @@ class Movies(Items):
LOG.info("ADD movie itemid: %s - Title: %s", itemid, title) LOG.info("ADD movie itemid: %s - Title: %s", itemid, title)
if v.KODIVERSION >= 17: if v.KODIVERSION >= 17:
# add new ratings Kodi 17 # add new ratings Kodi 17
rating_id = self.kodi_db.create_entry_rating() rating_id = self.kodi_db.get_ratingid(movieid,
v.KODI_TYPE_MOVIE)
self.kodi_db.add_ratings(rating_id, self.kodi_db.add_ratings(rating_id,
movieid, movieid,
v.KODI_TYPE_MOVIE, v.KODI_TYPE_MOVIE,
@ -369,7 +371,8 @@ class Movies(Items):
votecount) votecount)
# add new uniqueid Kodi 17 # add new uniqueid Kodi 17
if imdb is not None: if imdb is not None:
uniqueid = self.kodi_db.create_entry_uniqueid() uniqueid = self.kodi_db.get_uniqueid(movieid,
v.KODI_TYPE_MOVIE)
self.kodi_db.add_uniqueid(uniqueid, self.kodi_db.add_uniqueid(uniqueid,
movieid, movieid,
v.KODI_TYPE_MOVIE, v.KODI_TYPE_MOVIE,
@ -434,23 +437,23 @@ class Movies(Items):
kodicursor.execute(query, (pathid, filename, dateadded, fileid)) kodicursor.execute(query, (pathid, filename, dateadded, fileid))
# Process countries # Process countries
self.kodi_db.addCountries(movieid, countries, "movie") self.kodi_db.modify_countries(movieid, v.KODI_TYPE_MOVIE, countries)
# Process cast # Process cast
self.kodi_db.addPeople(movieid, api.people_list(), "movie") self.kodi_db.addPeople(movieid, api.people_list(), "movie")
# Process genres # Process genres
self.kodi_db.addGenres(movieid, genres, "movie") self.kodi_db.modify_genres(movieid, v.KODI_TYPE_MOVIE, genres)
# Process artwork # Process artwork
artwork.addArtwork(api.artwork(), movieid, "movie", kodicursor) artwork.addArtwork(api.artwork(), movieid, "movie", kodicursor)
# Process stream details # Process stream details
self.kodi_db.addStreams(fileid, api.mediastreams(), runtime) self.kodi_db.modify_streams(fileid, api.mediastreams(), runtime)
# Process studios # Process studios
self.kodi_db.addStudios(movieid, studios, "movie") self.kodi_db.modify_studios(movieid, v.KODI_TYPE_MOVIE, studios)
# Process tags: view, Plex collection tags # Process tags: view, Plex collection tags
tags = [viewtag] tags = [viewtag]
tags.extend(collections) tags.extend(collections)
if userdata['Favorite']: if userdata['Favorite']:
tags.append("Favorite movies") tags.append("Favorite movies")
self.kodi_db.addTags(movieid, tags, "movie") self.kodi_db.modify_tags(movieid, v.KODI_TYPE_MOVIE, tags)
# Add any sets from Plex collection tags # Add any sets from Plex collection tags
self.kodi_db.addSets(movieid, collections, kodicursor) self.kodi_db.addSets(movieid, collections, kodicursor)
# Process playstates # Process playstates
@ -469,7 +472,7 @@ class Movies(Items):
kodi_id = plex_dbitem[0] kodi_id = plex_dbitem[0]
file_id = plex_dbitem[1] file_id = plex_dbitem[1]
kodi_type = plex_dbitem[4] kodi_type = plex_dbitem[4]
LOG.info("Removing %sid: %s file_id: %s", LOG.debug("Removing %sid: %s file_id: %s",
kodi_type, kodi_id, file_id) kodi_type, kodi_id, file_id)
except TypeError: except TypeError:
return return
@ -480,11 +483,21 @@ class Movies(Items):
artwork.deleteArtwork(kodi_id, kodi_type, kodicursor) artwork.deleteArtwork(kodi_id, kodi_type, kodicursor)
if kodi_type == v.KODI_TYPE_MOVIE: if kodi_type == v.KODI_TYPE_MOVIE:
set_id = self.kodi_db.get_set_id(kodi_id)
self.kodi_db.delete_countries(kodi_id, kodi_type)
self.kodi_db.delete_people(kodi_id, kodi_type)
self.kodi_db.delete_genre(kodi_id, kodi_type)
self.kodi_db.delete_studios(kodi_id, kodi_type)
self.kodi_db.delete_tags(kodi_id, kodi_type)
self.kodi_db.modify_streams(file_id)
self.kodi_db.delete_playstate(file_id)
# Delete kodi movie and file # Delete kodi movie and file
kodicursor.execute("DELETE FROM movie WHERE idMovie = ?", kodicursor.execute("DELETE FROM movie WHERE idMovie = ?",
(kodi_id,)) (kodi_id,))
kodicursor.execute("DELETE FROM files WHERE idFile = ?", kodicursor.execute("DELETE FROM files WHERE idFile = ?",
(file_id,)) (file_id,))
if set_id:
self.kodi_db.delete_possibly_empty_set(set_id)
if v.KODIVERSION >= 17: if v.KODIVERSION >= 17:
self.kodi_db.remove_uniqueid(kodi_id, kodi_type) self.kodi_db.remove_uniqueid(kodi_id, kodi_type)
self.kodi_db.remove_ratings(kodi_id, kodi_type) self.kodi_db.remove_ratings(kodi_id, kodi_type)
@ -499,7 +512,7 @@ class Movies(Items):
# Update plex reference # Update plex reference
plex_db.updateParentId(plexid, None) plex_db.updateParentId(plexid, None)
kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodi_id,)) kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodi_id,))
LOG.info("Deleted %s %s from kodi database", kodi_type, itemid) LOG.debug("Deleted %s %s from kodi database", kodi_type, itemid)
class TVShows(Items): class TVShows(Items):
@ -627,6 +640,7 @@ class TVShows(Items):
"unknown", "unknown",
uniqueid) uniqueid)
else: else:
self.kodi_db.remove_uniqueid(showid, v.KODI_TYPE_SHOW)
uniqueid = -1 uniqueid = -1
# Update the tvshow entry # Update the tvshow entry
query = ''' query = '''
@ -677,7 +691,7 @@ class TVShows(Items):
view_id=viewid) view_id=viewid)
if v.KODIVERSION >= 17: if v.KODIVERSION >= 17:
# add new ratings Kodi 17 # add new ratings Kodi 17
rating_id = self.kodi_db.create_entry_rating() rating_id = self.kodi_db.get_ratingid(showid, v.KODI_TYPE_SHOW)
self.kodi_db.add_ratings(rating_id, self.kodi_db.add_ratings(rating_id,
showid, showid,
v.KODI_TYPE_SHOW, v.KODI_TYPE_SHOW,
@ -686,7 +700,8 @@ class TVShows(Items):
votecount) votecount)
# add new uniqueid Kodi 17 # add new uniqueid Kodi 17
if tvdb is not None: if tvdb is not None:
uniqueid = self.kodi_db.create_entry_uniqueid() uniqueid = self.kodi_db.get_uniqueid(showid,
v.KODI_TYPE_SHOW)
self.kodi_db.add_uniqueid(uniqueid, self.kodi_db.add_uniqueid(uniqueid,
showid, showid,
v.KODI_TYPE_SHOW, v.KODI_TYPE_SHOW,
@ -985,7 +1000,8 @@ class TVShows(Items):
# Create the episode entry # Create the episode entry
if v.KODIVERSION >= 17: if v.KODIVERSION >= 17:
# add new ratings Kodi 17 # add new ratings Kodi 17
rating_id = self.kodi_db.create_entry_rating() rating_id = self.kodi_db.get_ratingid(episodeid,
v.KODI_TYPE_EPISODE)
self.kodi_db.add_ratings(rating_id, self.kodi_db.add_ratings(rating_id,
episodeid, episodeid,
v.KODI_TYPE_EPISODE, v.KODI_TYPE_EPISODE,
@ -993,7 +1009,9 @@ class TVShows(Items):
rating, rating,
votecount) votecount)
# add new uniqueid Kodi 17 # add new uniqueid Kodi 17
self.kodi_db.add_uniqueid(self.kodi_db.create_entry_uniqueid(), uniqueid = self.kodi_db.get_uniqueid(episodeid,
v.KODI_TYPE_EPISODE)
self.kodi_db.add_uniqueid(uniqueid,
episodeid, episodeid,
v.KODI_TYPE_EPISODE, v.KODI_TYPE_EPISODE,
tvdb, tvdb,
@ -1083,7 +1101,7 @@ class TVShows(Items):
# Process stream details # Process stream details
streams = api.mediastreams() streams = api.mediastreams()
self.kodi_db.addStreams(fileid, streams, runtime) self.kodi_db.modify_streams(fileid, streams, runtime)
# Process playstates # Process playstates
self.kodi_db.addPlaystate(fileid, self.kodi_db.addPlaystate(fileid,
resume, resume,
@ -1203,6 +1221,9 @@ class TVShows(Items):
Remove a TV show, and only the show, no seasons or episodes Remove a TV show, and only the show, no seasons or episodes
""" """
kodicursor = self.kodicursor kodicursor = self.kodicursor
self.kodi_db.delete_genre(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.delete_studios(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.delete_tags(kodi_id, v.KODI_TYPE_SHOW)
self.artwork.deleteArtwork(kodi_id, v.KODI_TYPE_SHOW, kodicursor) self.artwork.deleteArtwork(kodi_id, v.KODI_TYPE_SHOW, kodicursor)
kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodi_id,)) kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodi_id,))
if v.KODIVERSION >= 17: if v.KODIVERSION >= 17:
@ -1220,15 +1241,18 @@ class TVShows(Items):
(kodi_id,)) (kodi_id,))
LOG.info("Removed season: %s.", kodi_id) LOG.info("Removed season: %s.", kodi_id)
def removeEpisode(self, kodi_id, fileid): def removeEpisode(self, kodi_id, file_id):
""" """
Remove an episode, and episode only Remove an episode, and episode only
""" """
kodicursor = self.kodicursor kodicursor = self.kodicursor
self.kodi_db.delete_people(kodi_id, v.KODI_TYPE_EPISODE)
self.kodi_db.modify_streams(file_id)
self.kodi_db.delete_playstate(file_id)
self.artwork.deleteArtwork(kodi_id, "episode", kodicursor) self.artwork.deleteArtwork(kodi_id, "episode", kodicursor)
kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?",
(kodi_id,)) (kodi_id,))
kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) kodicursor.execute("DELETE FROM files WHERE idFile = ?", (file_id,))
if v.KODIVERSION >= 17: if v.KODIVERSION >= 17:
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE) self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE)
self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_EPISODE) self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_EPISODE)
@ -1250,7 +1274,7 @@ class Music(Items):
self.kodiconn = kodi_sql('music') self.kodiconn = kodi_sql('music')
self.kodicursor = self.kodiconn.cursor() self.kodicursor = self.kodiconn.cursor()
self.plex_db = plexdb.Plex_DB_Functions(self.plexcursor) self.plex_db = plexdb.Plex_DB_Functions(self.plexcursor)
self.kodi_db = kodidb.Kodidb_Functions(self.kodicursor) self.kodi_db = kodidb.KodiDBMethods(self.kodicursor)
return self return self
@catch_exceptions(warnuser=True) @catch_exceptions(warnuser=True)

File diff suppressed because it is too large Load diff

View file

@ -24,9 +24,9 @@ import variables as v
import state import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger("PLEX." + __name__)
# Do we need to return ultimately with a setResolvedUrl?
RESOLVE = True
############################################################################### ###############################################################################
@ -49,13 +49,13 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
""" """
LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s', LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s',
plex_id, plex_type, path) plex_id, plex_type, path)
global RESOLVE
RESOLVE = resolve
if not state.AUTHENTICATED: if not state.AUTHENTICATED:
LOG.error('Not yet authenticated for PMS, abort starting playback') LOG.error('Not yet authenticated for PMS, abort starting playback')
if resolve is True:
# Release default.py
pickle_me(Playback_Successful())
# "Unauthorized for PMS" # "Unauthorized for PMS"
dialog('notification', lang(29999), lang(30017)) dialog('notification', lang(29999), lang(30017))
_ensure_resolve()
return return
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type])
@ -67,19 +67,13 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
try: try:
playqueue.items[pos] playqueue.items[pos]
except IndexError: except IndexError:
# Release our default.py before starting our own Kodi player instance _playback_init(plex_id, plex_type, playqueue, pos)
if resolve is True:
state.PKC_CAUSED_STOP = True
result = Playback_Successful()
result.listitem = PKC_ListItem(path='PKC_Dummy_Path_Which_Fails')
pickle_me(result)
playback_init(plex_id, plex_type, playqueue)
else: else:
# kick off playback on second pass # kick off playback on second pass
conclude_playback(playqueue, pos) _conclude_playback(playqueue, pos)
def playback_init(plex_id, plex_type, playqueue): def _playback_init(plex_id, plex_type, playqueue, pos):
""" """
Playback setup if Kodi starts playing an item for the first time. Playback setup if Kodi starts playing an item for the first time.
""" """
@ -91,9 +85,25 @@ def playback_init(plex_id, plex_type, playqueue):
LOG.error('Could not get a PMS xml for plex id %s', plex_id) LOG.error('Could not get a PMS xml for plex id %s', plex_id)
# "Play error" # "Play error"
dialog('notification', lang(29999), lang(30128), icon='{error}') dialog('notification', lang(29999), lang(30128), icon='{error}')
_ensure_resolve()
return return
trailers = False if playqueue.kodi_pl.size() > 1:
# Special case - we already got a filled Kodi playqueue
try:
_init_existing_kodi_playlist(playqueue)
except PL.PlaylistError:
LOG.error('Aborting playback_init for longer Kodi playlist')
_ensure_resolve()
return
# Now we need to use setResolvedUrl for the item at position pos
_conclude_playback(playqueue, pos)
return
# "Usual" case - consider trailers and parts and build both Kodi and Plex
# playqueues
# Fail the item we're trying to play now so we can restart the player
_ensure_resolve()
api = API(xml[0]) api = API(xml[0])
trailers = False
if (plex_type == v.PLEX_TYPE_MOVIE and not api.resume_point() and if (plex_type == v.PLEX_TYPE_MOVIE and not api.resume_point() and
settings('enableCinema') == "true"): settings('enableCinema') == "true"):
if settings('askCinema') == "true": if settings('askCinema') == "true":
@ -115,6 +125,7 @@ def playback_init(plex_id, plex_type, playqueue):
plex_id, xml.attrib.get('librarySectionUUID')) plex_id, xml.attrib.get('librarySectionUUID'))
# "Play error" # "Play error"
dialog('notification', lang(29999), lang(30128), icon='{error}') dialog('notification', lang(29999), lang(30128), icon='{error}')
_ensure_resolve()
return return
# Should already be empty, but just in case # Should already be empty, but just in case
PL.get_playlist_details_from_xml(playqueue, xml) PL.get_playlist_details_from_xml(playqueue, xml)
@ -139,6 +150,39 @@ def playback_init(plex_id, plex_type, playqueue):
thread.start() thread.start()
def _ensure_resolve():
"""
Will check whether RESOLVE=True and if so, fail Kodi playback startup
with the path 'PKC_Dummy_Path_Which_Fails' using setResolvedUrl (and some
pickling)
This way we're making sure that other Python instances (calling default.py)
will be destroyed.
"""
if RESOLVE is True:
state.PKC_CAUSED_STOP = True
result = Playback_Successful()
result.listitem = PKC_ListItem(path='PKC_Dummy_Path_Which_Fails')
pickle_me(result)
def _init_existing_kodi_playlist(playqueue):
"""
Will take the playqueue's kodi_pl with MORE than 1 element and initiate
playback (without adding trailers)
"""
LOG.debug('Kodi playlist size: %s', playqueue.kodi_pl.size())
for i, kodi_item in enumerate(js.playlist_get_items(playqueue.playlistid)):
if i == 0:
item = PL.init_Plex_playlist(playqueue, kodi_item=kodi_item)
else:
item = PL.add_item_to_PMS_playlist(playqueue,
i,
kodi_item=kodi_item)
item.force_transcode = state.FORCE_TRANSCODE
LOG.debug('Done building Plex playlist from Kodi playlist')
def _prep_playlist_stack(xml): def _prep_playlist_stack(xml):
stack = [] stack = []
for item in xml: for item in xml:
@ -152,7 +196,9 @@ def _prep_playlist_stack(xml):
kodi_id = plex_dbitem[0] if plex_dbitem else None kodi_id = plex_dbitem[0] if plex_dbitem else None
kodi_type = plex_dbitem[4] if plex_dbitem else None kodi_type = plex_dbitem[4] if plex_dbitem else None
else: else:
# We will never store clips (trailers) in the Kodi DB # We will never store clips (trailers) in the Kodi DB.
# Also set kodi_id to None for playback via PMS, so that we're
# using add-on paths.
kodi_id = None kodi_id = None
kodi_type = None kodi_type = None
for part, _ in enumerate(item[0]): for part, _ in enumerate(item[0]):
@ -165,8 +211,7 @@ def _prep_playlist_stack(xml):
'plex_type': api.plex_type() 'plex_type': api.plex_type()
} }
path = ('plugin://%s/?%s' path = ('plugin://%s/?%s'
% (v.ADDON_TYPE[api.plex_type()], % (v.ADDON_TYPE[api.plex_type()], urlencode(params)))
urlencode(params)))
listitem = api.create_listitem() listitem = api.create_listitem()
listitem.setPath(try_encode(path)) listitem.setPath(try_encode(path))
else: else:
@ -217,7 +262,7 @@ def _process_stack(playqueue, stack):
pos += 1 pos += 1
def conclude_playback(playqueue, pos): def _conclude_playback(playqueue, pos):
""" """
ONLY if actually being played (e.g. at 5th position of a playqueue). ONLY if actually being played (e.g. at 5th position of a playqueue).
@ -246,9 +291,9 @@ def conclude_playback(playqueue, pos):
else: else:
playurl = item.file playurl = item.file
listitem.setPath(try_encode(playurl)) listitem.setPath(try_encode(playurl))
if item.playmethod in ('DirectStream', 'DirectPlay'): if item.playmethod == 'DirectStream':
listitem.setSubtitles(api.cache_external_subs()) listitem.setSubtitles(api.cache_external_subs())
else: elif item.playmethod == 'Transcode':
playutils.audio_subtitle_prefs(listitem) playutils.audio_subtitle_prefs(listitem)
if state.RESUME_PLAYBACK is True: if state.RESUME_PLAYBACK is True:
state.RESUME_PLAYBACK = False state.RESUME_PLAYBACK = False
@ -281,6 +326,8 @@ def process_indirect(key, offset, resolve=True):
setResolvedUrl setResolvedUrl
""" """
LOG.info('process_indirect called with key: %s, offset: %s', key, offset) LOG.info('process_indirect called with key: %s, offset: %s', key, offset)
global RESOLVE
RESOLVE = resolve
result = Playback_Successful() result = Playback_Successful()
if key.startswith('http') or key.startswith('{server}'): if key.startswith('http') or key.startswith('{server}'):
xml = DU().downloadUrl(key) xml = DU().downloadUrl(key)
@ -292,9 +339,7 @@ def process_indirect(key, offset, resolve=True):
xml[0].attrib xml[0].attrib
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):
LOG.error('Could not download PMS metadata') LOG.error('Could not download PMS metadata')
if resolve is True: _ensure_resolve()
# Release default.py
pickle_me(result)
return return
if offset != '0': if offset != '0':
offset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(offset)) offset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(offset))
@ -317,9 +362,7 @@ def process_indirect(key, offset, resolve=True):
xml[0].attrib xml[0].attrib
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):
LOG.error('Could not download last xml for playurl') LOG.error('Could not download last xml for playurl')
if resolve is True: _ensure_resolve()
# Release default.py
pickle_me(result)
return return
playurl = xml[0].attrib['key'] playurl = xml[0].attrib['key']
item.file = playurl item.file = playurl

View file

@ -8,7 +8,7 @@ from re import compile as re_compile
import plexdb_functions as plexdb import plexdb_functions as plexdb
from downloadutils import DownloadUtils as DU from downloadutils import DownloadUtils as DU
from utils import try_encode, escape_html from utils import try_encode
from PlexAPI import API from PlexAPI import API
from PlexFunctions import GetPlexMetadata from PlexFunctions import GetPlexMetadata
from kodidb_functions import kodiid_from_filename from kodidb_functions import kodiid_from_filename
@ -190,8 +190,16 @@ class Playlist_Item(object):
""" """
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type] stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
count = 0 count = 0
# Kodi indexes differently than Plex
for stream in self.xml[0][self.part]: for stream in self.xml[0][self.part]:
if stream.attrib['streamType'] == stream_type: if (stream.attrib['streamType'] == stream_type and
'key' in stream.attrib):
if count == kodi_stream_index:
return stream.attrib['id']
count += 1
for stream in self.xml[0][self.part]:
if (stream.attrib['streamType'] == stream_type and
'key' not in stream.attrib):
if count == kodi_stream_index: if count == kodi_stream_index:
return stream.attrib['id'] return stream.attrib['id']
count += 1 count += 1
@ -208,7 +216,14 @@ class Playlist_Item(object):
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type] stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
count = 0 count = 0
for stream in self.xml[0][self.part]: for stream in self.xml[0][self.part]:
if stream.attrib['streamType'] == stream_type: if (stream.attrib['streamType'] == stream_type and
'key' in stream.attrib):
if stream.attrib['id'] == plex_stream_index:
return count
count += 1
for stream in self.xml[0][self.part]:
if (stream.attrib['streamType'] == stream_type and
'key' not in stream.attrib):
if stream.attrib['id'] == plex_stream_index: if stream.attrib['id'] == plex_stream_index:
return count return count
count += 1 count += 1
@ -308,8 +323,7 @@ def playlist_item_from_plex(plex_id):
return item return item
def playlist_item_from_xml(playlist, xml_video_element, kodi_id=None, def playlist_item_from_xml(xml_video_element, kodi_id=None, kodi_type=None):
kodi_type=None):
""" """
Returns a playlist element for the playqueue using the Plex xml Returns a playlist element for the playqueue using the Plex xml
@ -319,13 +333,9 @@ def playlist_item_from_xml(playlist, xml_video_element, kodi_id=None,
api = API(xml_video_element) api = API(xml_video_element)
item.plex_id = api.plex_id() item.plex_id = api.plex_id()
item.plex_type = api.plex_type() item.plex_type = api.plex_type()
try: # item.id will only be set if you passed in an xml_video_element from e.g.
item.id = xml_video_element.attrib['%sItemID' % playlist.kind] # a playQueue
except KeyError: item.id = api.item_id()
pass
item.guid = xml_video_element.attrib.get('guid')
if item.guid is not None:
item.guid = escape_html(item.guid)
if kodi_id is not None: if kodi_id is not None:
item.kodi_id = kodi_id item.kodi_id = kodi_id
item.kodi_type = kodi_type item.kodi_type = kodi_type
@ -333,9 +343,12 @@ def playlist_item_from_xml(playlist, xml_video_element, kodi_id=None,
with plexdb.Get_Plex_DB() as plex_db: with plexdb.Get_Plex_DB() as plex_db:
db_element = plex_db.getItem_byId(item.plex_id) db_element = plex_db.getItem_byId(item.plex_id)
try: try:
item.kodi_id, item.kodi_type = int(db_element[0]), db_element[4] item.kodi_id, item.kodi_type = db_element[0], db_element[4]
except TypeError: except TypeError:
pass pass
item.guid = api.guid_html_escaped()
item.playcount = api.viewcount()
item.offset = api.resume_point()
item.xml = xml_video_element item.xml = xml_video_element
LOG.debug('Created new playlist item from xml: %s', item) LOG.debug('Created new playlist item from xml: %s', item)
return item return item
@ -420,7 +433,7 @@ def init_Plex_playlist(playlist, plex_id=None, kodi_item=None):
parameters=params) parameters=params)
get_playlist_details_from_xml(playlist, xml) get_playlist_details_from_xml(playlist, xml)
# Need to get the details for the playlist item # Need to get the details for the playlist item
item = playlist_item_from_xml(playlist, xml[0]) item = playlist_item_from_xml(xml[0])
except (KeyError, IndexError, TypeError): except (KeyError, IndexError, TypeError):
raise PlaylistError('Could not init Plex playlist with plex_id %s and ' raise PlaylistError('Could not init Plex playlist with plex_id %s and '
'kodi_item %s' % (plex_id, kodi_item)) 'kodi_item %s' % (plex_id, kodi_item))
@ -484,8 +497,8 @@ def add_item_to_playlist(playlist, pos, kodi_id=None, kodi_type=None,
params['item'] = {'file': item.file} params['item'] = {'file': item.file}
reply = js.playlist_insert(params) reply = js.playlist_insert(params)
if reply.get('error') is not None: if reply.get('error') is not None:
raise PlaylistError('Could not add item to playlist. Kodi reply. %s', raise PlaylistError('Could not add item to playlist. Kodi reply. %s'
reply) % reply)
return item return item
@ -506,17 +519,16 @@ def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None):
# Will always put the new item at the end of the Plex playlist # Will always put the new item at the end of the Plex playlist
xml = DU().downloadUrl(url, action_type="PUT") xml = DU().downloadUrl(url, action_type="PUT")
try: try:
item.xml = xml[-1] xml[-1].attrib
item.id = xml[-1].attrib['%sItemID' % playlist.kind] except (TypeError, AttributeError, KeyError, IndexError):
except IndexError: raise PlaylistError('Could not add item %s to playlist %s'
LOG.info('Could not get playlist children. Adding a dummy') % (kodi_item, playlist))
except (TypeError, AttributeError, KeyError): api = API(xml[-1])
raise PlaylistError('Could not add item %s to playlist %s', item.xml = xml[-1]
kodi_item, playlist) item.id = api.item_id()
# Get the guid for this item item.guid = api.guid_html_escaped()
for plex_item in xml: item.offset = api.resume_point()
if plex_item.attrib['%sItemID' % playlist.kind] == item.id: item.playcount = api.viewcount()
item.guid = escape_html(plex_item.attrib['guid'])
playlist.items.append(item) playlist.items.append(item)
if pos == len(playlist.items) - 1: if pos == len(playlist.items) - 1:
# Item was added at the end # Item was added at the end
@ -555,7 +567,7 @@ def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
raise PlaylistError('Could not add item to playlist. Kodi reply. %s', raise PlaylistError('Could not add item to playlist. Kodi reply. %s',
reply) reply)
if xml_video_element is not None: if xml_video_element is not None:
item = playlist_item_from_xml(playlist, xml_video_element) item = playlist_item_from_xml(xml_video_element)
item.kodi_id = kodi_id item.kodi_id = kodi_id
item.kodi_type = kodi_type item.kodi_type = kodi_type
item.file = file item.file = file
@ -646,7 +658,7 @@ def add_to_Kodi_playlist(playlist, xml_video_element):
Returns a Playlist_Item or raises PlaylistError Returns a Playlist_Item or raises PlaylistError
""" """
item = playlist_item_from_xml(playlist, xml_video_element) item = playlist_item_from_xml(xml_video_element)
if item.kodi_id: if item.kodi_id:
json_item = {'%sid' % item.kodi_type: item.kodi_id} json_item = {'%sid' % item.kodi_type: item.kodi_id}
else: else:
@ -673,7 +685,7 @@ def add_listitem_to_Kodi_playlist(playlist, pos, listitem, file,
playlist.kodi_pl.add(url=file, listitem=listitem, index=pos) playlist.kodi_pl.add(url=file, listitem=listitem, index=pos)
# We need to add this to our internal queue as well # We need to add this to our internal queue as well
if xml_video_element is not None: if xml_video_element is not None:
item = playlist_item_from_xml(playlist, xml_video_element) item = playlist_item_from_xml(xml_video_element)
else: else:
item = playlist_item_from_kodi(kodi_item) item = playlist_item_from_kodi(kodi_item)
if file is not None: if file is not None:

View file

@ -75,7 +75,7 @@ COMPANION_PORT = int(_ADDON.getSetting('companionPort'))
PKC_MACHINE_IDENTIFIER = None PKC_MACHINE_IDENTIFIER = None
# Minimal PKC version needed for the Kodi database - otherwise need to recreate # Minimal PKC version needed for the Kodi database - otherwise need to recreate
MIN_DB_VERSION = '2.0.0' MIN_DB_VERSION = '2.0.4'
# Database paths # Database paths
_DB_VIDEO_VERSION = { _DB_VIDEO_VERSION = {