parent
dea8e6d5f5
commit
6cb69ada3f
3 changed files with 92 additions and 97 deletions
|
@ -149,37 +149,26 @@ class Items(object):
|
||||||
db_item[4],
|
db_item[4],
|
||||||
userdata['UserRating'])
|
userdata['UserRating'])
|
||||||
|
|
||||||
def updatePlaystate(self, item):
|
def updatePlaystate(self, mark_played, view_count, resume, duration,
|
||||||
|
file_id, lastViewedAt):
|
||||||
"""
|
"""
|
||||||
Use with websockets, not xml
|
Use with websockets, not xml
|
||||||
"""
|
"""
|
||||||
# If the playback was stopped, check whether we need to increment the
|
# If the playback was stopped, check whether we need to increment the
|
||||||
# playcount. PMS won't tell us the playcount via websockets
|
# playcount. PMS won't tell us the playcount via websockets
|
||||||
if item['state'] in ('stopped', 'ended'):
|
if mark_played:
|
||||||
|
|
||||||
# If offset exceeds duration skip update
|
|
||||||
if item['viewOffset'] > item['duration']:
|
|
||||||
log.error("Error while updating play state, viewOffset "
|
|
||||||
"exceeded duration")
|
|
||||||
return
|
|
||||||
|
|
||||||
complete = float(item['viewOffset']) / float(item['duration'])
|
|
||||||
log.info('Item %s stopped with completion rate %s percent.'
|
|
||||||
'Mark item played at %s percent.'
|
|
||||||
% (item['ratingKey'], str(complete), v.MARK_PLAYED_AT), 1)
|
|
||||||
if complete >= v.MARK_PLAYED_AT:
|
|
||||||
log.info('Marking as completely watched in Kodi')
|
log.info('Marking as completely watched in Kodi')
|
||||||
try:
|
try:
|
||||||
item['viewCount'] += 1
|
view_count += 1
|
||||||
except TypeError:
|
except TypeError:
|
||||||
item['viewCount'] = 1
|
view_count = 1
|
||||||
item['viewOffset'] = 0
|
resume = 0
|
||||||
# Do the actual update
|
# Do the actual update
|
||||||
self.kodi_db.addPlaystate(item['file_id'],
|
self.kodi_db.addPlaystate(file_id,
|
||||||
item['viewOffset'],
|
resume,
|
||||||
item['duration'],
|
duration,
|
||||||
item['viewCount'],
|
view_count,
|
||||||
item['lastViewedAt'])
|
lastViewedAt)
|
||||||
|
|
||||||
|
|
||||||
class Movies(Items):
|
class Movies(Items):
|
||||||
|
|
|
@ -45,7 +45,7 @@ class LibrarySync(Thread):
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.itemsToProcess = []
|
self.itemsToProcess = []
|
||||||
self.sessionKeys = []
|
self.sessionKeys = {}
|
||||||
self.fanartqueue = Queue.Queue()
|
self.fanartqueue = Queue.Queue()
|
||||||
if settings('FanartTV') == 'true':
|
if settings('FanartTV') == 'true':
|
||||||
self.fanartthread = Process_Fanart_Thread(self.fanartqueue)
|
self.fanartthread = Process_Fanart_Thread(self.fanartqueue)
|
||||||
|
@ -223,6 +223,8 @@ class LibrarySync(Thread):
|
||||||
"""
|
"""
|
||||||
repair=True: force sync EVERY item
|
repair=True: force sync EVERY item
|
||||||
"""
|
"""
|
||||||
|
# Reset our keys
|
||||||
|
self.sessionKeys = {}
|
||||||
# self.compare == False: we're syncing EVERY item
|
# self.compare == False: we're syncing EVERY item
|
||||||
# True: we're syncing only the delta, e.g. different checksum
|
# True: we're syncing only the delta, e.g. different checksum
|
||||||
self.compare = not repair
|
self.compare = not repair
|
||||||
|
@ -1283,110 +1285,108 @@ class LibrarySync(Thread):
|
||||||
Someone (not necessarily the user signed in) is playing something some-
|
Someone (not necessarily the user signed in) is playing something some-
|
||||||
where
|
where
|
||||||
"""
|
"""
|
||||||
items = []
|
|
||||||
for item in data:
|
for item in data:
|
||||||
# Drop buffering messages immediately
|
|
||||||
status = item['state']
|
status = item['state']
|
||||||
if status == 'buffering':
|
if status == 'buffering':
|
||||||
|
# Drop buffering messages immediately
|
||||||
continue
|
continue
|
||||||
ratingKey = str(item['ratingKey'])
|
plex_id = str(item['ratingKey'])
|
||||||
for pid in (0, 1, 2):
|
for pid in (0, 1, 2):
|
||||||
if ratingKey == state.PLAYER_STATES[pid]['plex_id']:
|
if plex_id == state.PLAYER_STATES[pid]['plex_id']:
|
||||||
# Kodi is playing this item - no need to set the playstate
|
# Kodi is playing this item - no need to set the playstate
|
||||||
continue
|
continue
|
||||||
with plexdb.Get_Plex_DB() as plex_db:
|
|
||||||
kodi_info = plex_db.getItem_byId(ratingKey)
|
|
||||||
if kodi_info is None:
|
|
||||||
# Item not (yet) in Kodi library
|
|
||||||
continue
|
|
||||||
sessionKey = item['sessionKey']
|
sessionKey = item['sessionKey']
|
||||||
# Do we already have a sessionKey stored?
|
# Do we already have a sessionKey stored?
|
||||||
if sessionKey not in self.sessionKeys:
|
if sessionKey not in self.sessionKeys:
|
||||||
|
with plexdb.Get_Plex_DB() as plex_db:
|
||||||
|
kodi_info = plex_db.getItem_byId(plex_id)
|
||||||
|
if kodi_info is None:
|
||||||
|
# Item not (yet) in Kodi library
|
||||||
|
continue
|
||||||
if settings('plex_serverowned') == 'false':
|
if settings('plex_serverowned') == 'false':
|
||||||
# Not our PMS, we are not authorized to get the
|
# Not our PMS, we are not authorized to get the sessions
|
||||||
# sessions
|
|
||||||
# On the bright side, it must be us playing :-)
|
# On the bright side, it must be us playing :-)
|
||||||
self.sessionKeys = {
|
self.sessionKeys[sessionKey] = {}
|
||||||
sessionKey: {}
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
# PMS is ours - get all current sessions
|
# PMS is ours - get all current sessions
|
||||||
self.sessionKeys = GetPMSStatus(state.PLEX_TOKEN)
|
self.sessionKeys.update(GetPMSStatus(state.PLEX_TOKEN))
|
||||||
log.debug('Updated current sessions. They are: %s'
|
log.debug('Updated current sessions. They are: %s',
|
||||||
% self.sessionKeys)
|
self.sessionKeys)
|
||||||
if sessionKey not in self.sessionKeys:
|
if sessionKey not in self.sessionKeys:
|
||||||
log.warn('Session key %s still unknown! Skip '
|
log.info('Session key %s still unknown! Skip '
|
||||||
'item' % sessionKey)
|
'playstate update', sessionKey)
|
||||||
continue
|
continue
|
||||||
|
# Attach Kodi info to the session
|
||||||
currSess = self.sessionKeys[sessionKey]
|
self.sessionKeys[sessionKey]['kodi_id'] = kodi_info[0]
|
||||||
|
self.sessionKeys[sessionKey]['file_id'] = kodi_info[1]
|
||||||
|
self.sessionKeys[sessionKey]['kodi_type'] = kodi_info[4]
|
||||||
|
session = self.sessionKeys[sessionKey]
|
||||||
if settings('plex_serverowned') != 'false':
|
if settings('plex_serverowned') != 'false':
|
||||||
# Identify the user - same one as signed on with PKC? Skip
|
# Identify the user - same one as signed on with PKC? Skip
|
||||||
# update if neither session's username nor userid match
|
# update if neither session's username nor userid match
|
||||||
# (Owner sometime's returns id '1', not always)
|
# (Owner sometime's returns id '1', not always)
|
||||||
if (not state.PLEX_TOKEN and currSess['userId'] == '1'):
|
if not state.PLEX_TOKEN and session['userId'] == '1':
|
||||||
# PKC not signed in to plex.tv. Plus owner of PMS is
|
# PKC not signed in to plex.tv. Plus owner of PMS is
|
||||||
# playing (the '1').
|
# playing (the '1').
|
||||||
# Hence must be us (since several users require plex.tv
|
# Hence must be us (since several users require plex.tv
|
||||||
# token for PKC)
|
# token for PKC)
|
||||||
pass
|
pass
|
||||||
elif not (currSess['userId'] == state.PLEX_USER_ID
|
elif not (session['userId'] == state.PLEX_USER_ID or
|
||||||
or
|
session['username'] == state.PLEX_USERNAME):
|
||||||
currSess['username'] == state.PLEX_USERNAME):
|
|
||||||
log.debug('Our username %s, userid %s did not match '
|
log.debug('Our username %s, userid %s did not match '
|
||||||
'the session username %s with userid %s'
|
'the session username %s with userid %s',
|
||||||
% (state.PLEX_USERNAME,
|
state.PLEX_USERNAME,
|
||||||
state.PLEX_USER_ID,
|
state.PLEX_USER_ID,
|
||||||
currSess['username'],
|
session['username'],
|
||||||
currSess['userId']))
|
session['userId'])
|
||||||
continue
|
continue
|
||||||
|
# Get an up-to-date XML from the PMS because PMS will NOT directly
|
||||||
# Get an up-to-date XML from the PMS
|
# tell us: duration of item viewCount
|
||||||
# because PMS will NOT directly tell us:
|
if session.get('duration') is None:
|
||||||
# duration of item
|
xml = GetPlexMetadata(plex_id)
|
||||||
# viewCount
|
|
||||||
if currSess.get('duration') is None:
|
|
||||||
xml = GetPlexMetadata(ratingKey)
|
|
||||||
if xml in (None, 401):
|
if xml in (None, 401):
|
||||||
log.error('Could not get up-to-date xml for item %s'
|
log.error('Could not get up-to-date xml for item %s',
|
||||||
% ratingKey)
|
plex_id)
|
||||||
continue
|
continue
|
||||||
API = PlexAPI.API(xml[0])
|
api = PlexAPI.API(xml[0])
|
||||||
userdata = API.getUserData()
|
userdata = api.getUserData()
|
||||||
currSess['duration'] = userdata['Runtime']
|
session['duration'] = userdata['Runtime']
|
||||||
currSess['viewCount'] = userdata['PlayCount']
|
session['viewCount'] = userdata['PlayCount']
|
||||||
# Sometimes, Plex tells us resume points in milliseconds and
|
# Sometimes, Plex tells us resume points in milliseconds and
|
||||||
# not in seconds - thank you very much!
|
# not in seconds - thank you very much!
|
||||||
if item.get('viewOffset') > currSess['duration']:
|
if item['viewOffset'] > session['duration']:
|
||||||
resume = item.get('viewOffset') / 1000
|
resume = item['viewOffset'] / 1000
|
||||||
else:
|
else:
|
||||||
resume = item.get('viewOffset')
|
resume = item['viewOffset']
|
||||||
if resume >= v.MARK_PLAYED_AT and status not in ('stopped', 'ended'):
|
if resume < v.IGNORE_SECONDS_AT_START:
|
||||||
# We need to drop these as we'll otherwise NOT mark an item as
|
|
||||||
# completely watched after having seen >90%
|
|
||||||
continue
|
continue
|
||||||
# Append to list that we need to process
|
try:
|
||||||
items.append({
|
completed = float(resume) / float(session['duration'])
|
||||||
'ratingKey': ratingKey,
|
except (ZeroDivisionError, TypeError):
|
||||||
'kodi_id': kodi_info[0],
|
log.error('Could not mark playstate for %s and session %s',
|
||||||
'file_id': kodi_info[1],
|
data, session)
|
||||||
'kodi_type': kodi_info[4],
|
continue
|
||||||
'viewOffset': resume,
|
if completed >= v.MARK_PLAYED_AT:
|
||||||
'state': status,
|
# Only mark completely watched ONCE
|
||||||
'duration': currSess['duration'],
|
if session.get('marked_played') is None:
|
||||||
'viewCount': currSess['viewCount'],
|
session['marked_played'] = True
|
||||||
'lastViewedAt': DateToKodi(getUnixTimestamp())
|
mark_played = True
|
||||||
})
|
else:
|
||||||
log.debug('Update playstate for user %s with id %s: %s'
|
# Don't mark it as completely watched again
|
||||||
% (state.PLEX_USERNAME,
|
continue
|
||||||
state.PLEX_USER_ID,
|
else:
|
||||||
items[-1]))
|
mark_played = False
|
||||||
# Now tell Kodi where we are
|
log.debug('Update playstate for user %s with id %s for plex id %s',
|
||||||
for item in items:
|
state.PLEX_USERNAME, state.PLEX_USER_ID, plex_id)
|
||||||
itemFkt = getattr(itemtypes,
|
item_fkt = getattr(itemtypes,
|
||||||
v.ITEMTYPE_FROM_KODITYPE[item['kodi_type']])
|
v.ITEMTYPE_FROM_KODITYPE[session['kodi_type']])
|
||||||
with itemFkt() as Fkt:
|
with item_fkt() as fkt:
|
||||||
Fkt.updatePlaystate(item)
|
fkt.updatePlaystate(mark_played,
|
||||||
|
session['viewCount'],
|
||||||
|
resume,
|
||||||
|
session['duration'],
|
||||||
|
session['file_id'],
|
||||||
|
DateToKodi(getUnixTimestamp()))
|
||||||
|
|
||||||
def fanartSync(self, refresh=False):
|
def fanartSync(self, refresh=False):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -21,7 +21,13 @@ def tryDecode(string, encoding='utf-8'):
|
||||||
string = string.decode()
|
string = string.decode()
|
||||||
return string
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
# Percent of playback progress for watching item as partially watched. Anything
|
||||||
|
# more and item will NOT be marked as partially, but fully watched
|
||||||
MARK_PLAYED_AT = 0.9
|
MARK_PLAYED_AT = 0.9
|
||||||
|
# How many seconds of playback do we ignore before marking an item as partially
|
||||||
|
# watched?
|
||||||
|
IGNORE_SECONDS_AT_START = 60
|
||||||
|
|
||||||
_ADDON = Addon()
|
_ADDON = Addon()
|
||||||
ADDON_NAME = 'PlexKodiConnect'
|
ADDON_NAME = 'PlexKodiConnect'
|
||||||
|
|
Loading…
Reference in a new issue