Playqueues overhaul continued
This commit is contained in:
parent
a9f59868f0
commit
146f063fc9
19 changed files with 1049 additions and 563 deletions
|
@ -6,8 +6,6 @@
|
|||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
<import addon="script.module.requests" version="2.3.0" />
|
||||
<import addon="plugin.video.plexkodiconnect.movies" version="0.01" />
|
||||
<import addon="plugin.video.plexkodiconnect.tvshows" version="0.01" />
|
||||
</requires>
|
||||
<extension point="xbmc.python.pluginsource"
|
||||
library="default.py">
|
||||
|
|
95
default.py
95
default.py
|
@ -3,36 +3,38 @@
|
|||
###############################################################################
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import urlparse
|
||||
from os import path as os_path
|
||||
from sys import path as sys_path, argv
|
||||
from urlparse import parse_qsl
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
from xbmc import translatePath, sleep, executebuiltin
|
||||
from xbmcaddon import Addon
|
||||
from xbmcgui import ListItem, Dialog
|
||||
from xbmcplugin import setResolvedUrl
|
||||
|
||||
|
||||
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||
_addon = Addon(id='plugin.video.plexkodiconnect')
|
||||
try:
|
||||
_addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
||||
except TypeError:
|
||||
_addon_path = _addon.getAddonInfo('path').decode()
|
||||
try:
|
||||
_base_resource = xbmc.translatePath(os.path.join(
|
||||
_base_resource = translatePath(os_path.join(
|
||||
_addon_path,
|
||||
'resources',
|
||||
'lib')).decode('utf-8')
|
||||
except TypeError:
|
||||
_base_resource = xbmc.translatePath(os.path.join(
|
||||
_base_resource = translatePath(os_path.join(
|
||||
_addon_path,
|
||||
'resources',
|
||||
'lib')).decode()
|
||||
sys.path.append(_base_resource)
|
||||
sys_path.append(_base_resource)
|
||||
|
||||
###############################################################################
|
||||
|
||||
import entrypoint
|
||||
import utils
|
||||
from utils import window, pickl_window, reset, passwordsXML
|
||||
from pickler import unpickle_me
|
||||
from PKC_listitem import convert_PKC_to_listitem
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -43,34 +45,47 @@ log = logging.getLogger("PLEX.default")
|
|||
|
||||
###############################################################################
|
||||
|
||||
ARGV = argv
|
||||
HANDLE = int(argv[1])
|
||||
|
||||
|
||||
class Main():
|
||||
|
||||
# MAIN ENTRY POINT
|
||||
#@utils.profiling()
|
||||
# @utils.profiling()
|
||||
def __init__(self):
|
||||
log.debug("Full sys.argv received: %s" % ARGV)
|
||||
# Parse parameters
|
||||
log.warn("Full sys.argv received: %s" % sys.argv)
|
||||
base_url = sys.argv[0]
|
||||
params = urlparse.parse_qs(sys.argv[2][1:])
|
||||
params = dict(parse_qsl(ARGV[2][1:]))
|
||||
try:
|
||||
mode = params['mode'][0]
|
||||
mode = params['mode']
|
||||
itemid = params.get('id', '')
|
||||
if itemid:
|
||||
try:
|
||||
itemid = itemid[0]
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
params = {}
|
||||
mode = ""
|
||||
itemid = ''
|
||||
|
||||
if mode == 'play':
|
||||
# Put the request into the "queue"
|
||||
while window('plex_play_new_item'):
|
||||
sleep(20)
|
||||
window('plex_play_new_item',
|
||||
value='%s%s' % (mode, ARGV[2]))
|
||||
# Wait for the result
|
||||
while not pickl_window('plex_result'):
|
||||
sleep(20)
|
||||
result = unpickle_me()
|
||||
if result is None:
|
||||
log.error('Error encountered, aborting')
|
||||
setResolvedUrl(HANDLE, False, ListItem())
|
||||
elif result.listitem:
|
||||
listitem = convert_PKC_to_listitem(result.listitem)
|
||||
setResolvedUrl(HANDLE, True, listitem)
|
||||
return
|
||||
|
||||
modes = {
|
||||
|
||||
'reset': utils.reset,
|
||||
'reset': reset,
|
||||
'resetauth': entrypoint.resetAuth,
|
||||
'play': entrypoint.doPlayback,
|
||||
'passwords': utils.passwordsXML,
|
||||
'passwords': passwordsXML,
|
||||
'channels': entrypoint.BrowseChannels,
|
||||
'channelsfolder': entrypoint.BrowseChannels,
|
||||
'browsecontent': entrypoint.BrowseContent,
|
||||
|
@ -79,7 +94,6 @@ class Main():
|
|||
'inprogressepisodes': entrypoint.getInProgressEpisodes,
|
||||
'recentepisodes': entrypoint.getRecentEpisodes,
|
||||
'refreshplaylist': entrypoint.refreshPlaylist,
|
||||
'companion': entrypoint.plexCompanion,
|
||||
'switchuser': entrypoint.switchPlexUser,
|
||||
'deviceid': entrypoint.resetDeviceId,
|
||||
'delete': entrypoint.deleteItem,
|
||||
|
@ -92,8 +106,8 @@ class Main():
|
|||
'playwatchlater': entrypoint.playWatchLater
|
||||
}
|
||||
|
||||
if "/extrafanart" in sys.argv[0]:
|
||||
plexpath = sys.argv[2][1:]
|
||||
if "/extrafanart" in ARGV[0]:
|
||||
plexpath = ARGV[2][1:]
|
||||
plexid = params.get('id', [""])[0]
|
||||
entrypoint.getExtraFanArt(plexid, plexpath)
|
||||
entrypoint.getVideoFiles(plexid, plexpath)
|
||||
|
@ -101,11 +115,11 @@ class Main():
|
|||
|
||||
if mode == 'fanart':
|
||||
log.info('User requested fanarttv refresh')
|
||||
utils.window('plex_runLibScan', value='fanart')
|
||||
window('plex_runLibScan', value='fanart')
|
||||
|
||||
# Called by e.g. 3rd party plugin video extras
|
||||
if ("/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0] or
|
||||
"/Extras" in sys.argv[2]):
|
||||
if ("/Extras" in ARGV[0] or "/VideoFiles" in ARGV[0] or
|
||||
"/Extras" in ARGV[2]):
|
||||
plexId = params.get('id', [None])[0]
|
||||
entrypoint.getVideoFiles(plexId, params)
|
||||
|
||||
|
@ -143,7 +157,7 @@ class Main():
|
|||
folderid = params['folderid'][0]
|
||||
modes[mode](itemid, folderid)
|
||||
elif mode == "companion":
|
||||
modes[mode](itemid, params=sys.argv[2])
|
||||
modes[mode](itemid, params=ARGV[2])
|
||||
elif mode == 'playwatchlater':
|
||||
modes[mode](params.get('id')[0], params.get('viewOffset')[0])
|
||||
else:
|
||||
|
@ -151,25 +165,24 @@ class Main():
|
|||
else:
|
||||
# Other functions
|
||||
if mode == "settings":
|
||||
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||
elif mode in ("manualsync", "repair"):
|
||||
if utils.window('plex_online') != "true":
|
||||
if window('plex_online') != "true":
|
||||
# Server is not online, do not run the sync
|
||||
xbmcgui.Dialog().ok(
|
||||
Dialog().ok(
|
||||
"PlexKodiConnect",
|
||||
"Unable to run the sync, the add-on is not connected "
|
||||
"to a Plex server.")
|
||||
log.error("Not connected to a PMS.")
|
||||
else:
|
||||
if mode == 'repair':
|
||||
utils.window('plex_runLibScan', value="repair")
|
||||
window('plex_runLibScan', value="repair")
|
||||
log.info("Requesting repair lib sync")
|
||||
elif mode == 'manualsync':
|
||||
log.info("Requesting full library scan")
|
||||
utils.window('plex_runLibScan', value="full")
|
||||
|
||||
window('plex_runLibScan', value="full")
|
||||
elif mode == "texturecache":
|
||||
utils.window('plex_runLibScan', value='del_textures')
|
||||
window('plex_runLibScan', value='del_textures')
|
||||
else:
|
||||
entrypoint.doMainListing()
|
||||
|
||||
|
|
334
resources/lib/PKC_listitem.py
Normal file
334
resources/lib/PKC_listitem.py
Normal file
|
@ -0,0 +1,334 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
import logging
|
||||
|
||||
from xbmcgui import ListItem
|
||||
|
||||
###############################################################################
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
def convert_PKC_to_listitem(PKC_listitem):
|
||||
"""
|
||||
Insert a PKC_listitem and you will receive a valid XBMC listitem
|
||||
"""
|
||||
listitem = ListItem()
|
||||
for func, args in PKC_listitem.data.items():
|
||||
if isinstance(args, list):
|
||||
for arg in args:
|
||||
getattr(listitem, func)(*arg)
|
||||
elif isinstance(args, dict):
|
||||
for arg in args.items():
|
||||
getattr(listitem, func)(*arg)
|
||||
elif args is None:
|
||||
continue
|
||||
else:
|
||||
getattr(listitem, func)(args)
|
||||
return listitem
|
||||
|
||||
|
||||
class PKC_ListItem(object):
|
||||
"""
|
||||
Imitates xbmcgui.ListItem and its functions. Pass along PKC_Listitem().data
|
||||
when pickling!
|
||||
|
||||
WARNING: set/get path only via setPath and getPath! (not getProperty)
|
||||
"""
|
||||
def __init__(self, label=None, label2=None, path=None):
|
||||
self.data = {
|
||||
'addStreamInfo': [], # (type, values: dict { label: value })
|
||||
'setArt': [], # dict: { label: value }
|
||||
'setInfo': {}, # type: infoLabel (dict { label: value })
|
||||
'setLabel': label, # string
|
||||
'setLabel2': label2, # string
|
||||
'setPath': path, # string
|
||||
'setProperty': {}, # (key, value)
|
||||
'setSubtitles': [], # string
|
||||
}
|
||||
|
||||
def addContextMenuItems(self, items, replaceItems):
|
||||
"""
|
||||
Adds item(s) to the context menu for media lists.
|
||||
|
||||
items : list - [(label, action,)*] A list of tuples consisting of label
|
||||
and action pairs.
|
||||
- label : string or unicode - item's label.
|
||||
- action : string or unicode - any built-in function to perform.
|
||||
replaceItes : [opt] bool - True=only your items will show/False=your
|
||||
items will be amdded to context menu(Default).
|
||||
|
||||
List of functions - http://kodi.wiki/view/List_of_Built_In_Functions
|
||||
|
||||
*Note, You can use the above as keywords for arguments and skip
|
||||
certain optional arguments.
|
||||
|
||||
Once you use a keyword, all following arguments require the keyword.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def addStreamInfo(self, type, values):
|
||||
"""
|
||||
Add a stream with details.
|
||||
type : string - type of stream(video/audio/subtitle).
|
||||
values : dictionary - pairs of { label: value }.
|
||||
|
||||
- Video Values:
|
||||
- codec : string (h264)
|
||||
- aspect : float (1.78)
|
||||
- width : integer (1280)
|
||||
- height : integer (720)
|
||||
- duration : integer (seconds)
|
||||
- Audio Values:
|
||||
- codec : string (dts)
|
||||
- language : string (en)
|
||||
- channels : integer (2)
|
||||
- Subtitle Values:
|
||||
- language : string (en)
|
||||
"""
|
||||
self.data['addStreamInfo'].append((type, values))
|
||||
|
||||
def getLabel(self):
|
||||
"""
|
||||
Returns the listitem label
|
||||
"""
|
||||
return self.data['setLabel']
|
||||
|
||||
def getLabel2(self):
|
||||
"""
|
||||
Returns the listitem label.
|
||||
"""
|
||||
return self.data['setLabel2']
|
||||
|
||||
def getMusicInfoTag(self):
|
||||
"""
|
||||
returns the MusicInfoTag for this item.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getProperty(self, key):
|
||||
"""
|
||||
Returns a listitem property as a string, similar to an infolabel.
|
||||
key : string - property name.
|
||||
*Note, Key is NOT case sensitive.
|
||||
|
||||
You can use the above as keywords for arguments and skip certain
|
||||
optional arguments.
|
||||
|
||||
Once you use a keyword, all following arguments require the keyword.
|
||||
"""
|
||||
return self.data['setProperty'].get(key)
|
||||
|
||||
def getVideoInfoTag(self):
|
||||
"""
|
||||
returns the VideoInfoTag for this item
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getdescription(self):
|
||||
"""
|
||||
Returns the description of this PlayListItem
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getduration(self):
|
||||
"""
|
||||
Returns the duration of this PlayListItem
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getfilename(self):
|
||||
"""
|
||||
Returns the filename of this PlayListItem.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def isSelected(self):
|
||||
"""
|
||||
Returns the listitem's selected status
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def select(self):
|
||||
"""
|
||||
Sets the listitem's selected status.
|
||||
selected : bool - True=selected/False=not selected
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def setArt(self, values):
|
||||
"""
|
||||
Sets the listitem's art
|
||||
values : dictionary - pairs of { label: value }.
|
||||
|
||||
Some default art values (any string possible):
|
||||
- thumb : string - image filename
|
||||
- poster : string - image filename
|
||||
- banner : string - image filename
|
||||
- fanart : string - image filename
|
||||
- clearart : string - image filename
|
||||
- clearlogo : string - image filename
|
||||
- landscape : string - image filename
|
||||
- icon : string - image filename
|
||||
"""
|
||||
self.data['setArt'].append(values)
|
||||
|
||||
def setContentLookup(self, enable):
|
||||
"""
|
||||
Enable or disable content lookup for item.
|
||||
|
||||
If disabled, HEAD requests to e.g determine mime type will not be sent.
|
||||
|
||||
enable : bool
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def setInfo(self, type, infoLabels):
|
||||
"""
|
||||
type : string - type of media(video/music/pictures).
|
||||
|
||||
infoLabels : dictionary - pairs of { label: value }. *Note, To set
|
||||
pictures exif info, prepend 'exif:' to the label. Exif values must be
|
||||
passed as strings, separate value pairs with a comma. (eg.
|
||||
{'exif:resolution': '720,480'}
|
||||
|
||||
See CPictureInfoTag::TranslateString in PictureInfoTag.cpp for valid
|
||||
strings. You can use the above as keywords for arguments and skip
|
||||
certain optional arguments.
|
||||
|
||||
Once you use a keyword, all following arguments require the keyword.
|
||||
|
||||
- General Values that apply to all types:
|
||||
- count : integer (12) - can be used to store an id for later, or
|
||||
for sorting purposes
|
||||
- size : long (1024) - size in bytes
|
||||
- date : string (d.m.Y / 01.01.2009) - file date
|
||||
|
||||
- Video Values:
|
||||
- genre : string (Comedy)
|
||||
- year : integer (2009)
|
||||
- episode : integer (4)
|
||||
- season : integer (1)
|
||||
- top250 : integer (192)
|
||||
- tracknumber : integer (3)
|
||||
- rating : float (6.4) - range is 0..10
|
||||
- userrating : integer (9) - range is 1..10
|
||||
- watched : depreciated - use playcount instead
|
||||
- playcount : integer (2) - number of times this item has been
|
||||
played
|
||||
- overlay : integer (2) - range is 0..8. See GUIListItem.h for
|
||||
values
|
||||
- cast : list (["Michal C. Hall","Jennifer Carpenter"]) - if
|
||||
provided a list of tuples cast will be interpreted as castandrole
|
||||
- castandrole : list of tuples ([("Michael C.
|
||||
Hall","Dexter"),("Jennifer Carpenter","Debra")])
|
||||
- director : string (Dagur Kari)
|
||||
- mpaa : string (PG-13)
|
||||
- plot : string (Long Description)
|
||||
- plotoutline : string (Short Description)
|
||||
- title : string (Big Fan)
|
||||
- originaltitle : string (Big Fan)
|
||||
- sorttitle : string (Big Fan)
|
||||
- duration : integer (245) - duration in seconds
|
||||
- studio : string (Warner Bros.)
|
||||
- tagline : string (An awesome movie) - short description of movie
|
||||
- writer : string (Robert D. Siegel)
|
||||
- tvshowtitle : string (Heroes)
|
||||
- premiered : string (2005-03-04)
|
||||
- status : string (Continuing) - status of a TVshow
|
||||
- code : string (tt0110293) - IMDb code
|
||||
- aired : string (2008-12-07)
|
||||
- credits : string (Andy Kaufman) - writing credits
|
||||
- lastplayed : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
|
||||
- album : string (The Joshua Tree)
|
||||
- artist : list (['U2'])
|
||||
- votes : string (12345 votes)
|
||||
- trailer : string (/home/user/trailer.avi)
|
||||
- dateadded : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
|
||||
- mediatype : string - "video", "movie", "tvshow", "season",
|
||||
"episode" or "musicvideo"
|
||||
|
||||
- Music Values:
|
||||
- tracknumber : integer (8)
|
||||
- discnumber : integer (2)
|
||||
- duration : integer (245) - duration in seconds
|
||||
- year : integer (1998)
|
||||
- genre : string (Rock)
|
||||
- album : string (Pulse)
|
||||
- artist : string (Muse)
|
||||
- title : string (American Pie)
|
||||
- rating : string (3) - single character between 0 and 5
|
||||
- lyrics : string (On a dark desert highway...)
|
||||
- playcount : integer (2) - number of times this item has been
|
||||
played
|
||||
- lastplayed : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
|
||||
|
||||
- Picture Values:
|
||||
- title : string (In the last summer-1)
|
||||
- picturepath : string (/home/username/pictures/img001.jpg)
|
||||
- exif : string (See CPictureInfoTag::TranslateString in
|
||||
PictureInfoTag.cpp for valid strings)
|
||||
"""
|
||||
self.data['setInfo'][type] = infoLabels
|
||||
|
||||
def setLabel(self, label):
|
||||
"""
|
||||
Sets the listitem's label.
|
||||
label : string or unicode - text string.
|
||||
"""
|
||||
self.data['setLabel'] = label
|
||||
|
||||
def setLabel2(self, label):
|
||||
"""
|
||||
Sets the listitem's label2.
|
||||
label : string or unicode - text string.
|
||||
"""
|
||||
self.data['setLabel2'] = label
|
||||
|
||||
def setMimeType(self, mimetype):
|
||||
"""
|
||||
Sets the listitem's mimetype if known.
|
||||
mimetype : string or unicode - mimetype.
|
||||
|
||||
If known prehand, this can (but does not have to) avoid HEAD requests
|
||||
being sent to HTTP servers to figure out file type.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def setPath(self, path):
|
||||
"""
|
||||
Sets the listitem's path.
|
||||
path : string or unicode - path, activated when item is clicked.
|
||||
|
||||
*Note, You can use the above as keywords for arguments.
|
||||
"""
|
||||
self.data['setPath'] = path
|
||||
|
||||
def setProperty(self, key, value):
|
||||
"""
|
||||
Sets a listitem property, similar to an infolabel.
|
||||
key : string - property name.
|
||||
value : string or unicode - value of property.
|
||||
*Note, Key is NOT case sensitive.
|
||||
|
||||
You can use the above as keywords for arguments and skip certain
|
||||
optional arguments. Once you use a keyword, all following arguments
|
||||
require the keyword.
|
||||
|
||||
Some of these are treated internally by XBMC, such as the
|
||||
'StartOffset' property, which is the offset in seconds at which to
|
||||
start playback of an item. Others may be used in the skin to add extra
|
||||
information, such as 'WatchedCount' for tvshow items
|
||||
"""
|
||||
self.data['setProperty'][key] = value
|
||||
|
||||
def setSubtitles(self, subtitles):
|
||||
"""
|
||||
Sets subtitles for this listitem. Pass in a list of filepaths
|
||||
|
||||
example:
|
||||
- listitem.setSubtitles(['special://temp/example.srt',
|
||||
'http://example.com/example.srt' ])
|
||||
"""
|
||||
self.data['setSubtitles'].extend(([subtitles],))
|
|
@ -2393,7 +2393,7 @@ class API():
|
|||
# listItem.setProperty('isPlayable', 'true')
|
||||
# listItem.setProperty('isFolder', 'true')
|
||||
# Further stuff
|
||||
listItem.setIconImage('DefaultPicture.png')
|
||||
listItem.setArt({'icon': 'DefaultPicture.png'})
|
||||
return listItem
|
||||
|
||||
def _createVideoListItem(self,
|
||||
|
@ -2456,14 +2456,14 @@ class API():
|
|||
"s%.2de%.2d" % (season, episode))
|
||||
if appendSxxExx is True:
|
||||
title = "S%.2dE%.2d - %s" % (season, episode, title)
|
||||
listItem.setIconImage('DefaultTVShows.png')
|
||||
listItem.setArt({'icon': 'DefaultTVShows.png'})
|
||||
if appendShowTitle is True:
|
||||
title = "%s - %s " % (show, title)
|
||||
elif typus == "movie":
|
||||
listItem.setIconImage('DefaultMovies.png')
|
||||
listItem.setArt({'icon': 'DefaultMovies.png'})
|
||||
else:
|
||||
# E.g. clips, trailers, ...
|
||||
listItem.setIconImage('DefaultVideo.png')
|
||||
listItem.setArt({'icon': 'DefaultVideo.png'})
|
||||
|
||||
plexId = self.getRatingKey()
|
||||
listItem.setProperty('plexid', plexId)
|
||||
|
|
|
@ -85,19 +85,11 @@ class PlexCompanion(Thread):
|
|||
return
|
||||
playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
||||
data['type'])
|
||||
if ID != playqueue.ID:
|
||||
# playqueue changed somehow
|
||||
self.mgr.playqueue.update_playqueue_from_PMS(
|
||||
playqueue,
|
||||
ID,
|
||||
query.get('repeat'),
|
||||
data.get('offset'))
|
||||
else:
|
||||
# No change to the playqueue
|
||||
self.mgr.playqueue.start_playqueue_initiated_by_companion(
|
||||
playqueue,
|
||||
query.get('repeat'),
|
||||
data.get('offset'))
|
||||
self.mgr.playqueue.update_playqueue_from_PMS(
|
||||
playqueue,
|
||||
ID,
|
||||
repeat=query.get('repeat'),
|
||||
offset=data.get('offset'))
|
||||
|
||||
def run(self):
|
||||
httpd = False
|
||||
|
|
|
@ -212,6 +212,6 @@ class ContextMenu(object):
|
|||
'mode': "play"
|
||||
}
|
||||
from urllib import urlencode
|
||||
handle = ("plugin://plugin.video.plexkodiconnect.movies?%s"
|
||||
handle = ("plugin://plugin.video.plexkodiconnect/movies?%s"
|
||||
% urlencode(params))
|
||||
xbmc.executebuiltin('RunPlugin(%s)' % handle)
|
||||
|
|
|
@ -67,7 +67,7 @@ class UsersConnect(xbmcgui.WindowXMLDialog):
|
|||
if self.kodi_version > 15:
|
||||
item.setArt({'Icon': user_image})
|
||||
else:
|
||||
item.setIconImage(user_image)
|
||||
item.setArt({'icon': user_image})
|
||||
|
||||
return item
|
||||
|
||||
|
|
|
@ -32,40 +32,6 @@ addonName = "PlexKodiConnect"
|
|||
###############################################################################
|
||||
|
||||
|
||||
def plexCompanion(fullurl, params):
|
||||
params = PlexFunctions.LiteralEval(params[26:])
|
||||
|
||||
if params['machineIdentifier'] != window('plex_machineIdentifier'):
|
||||
log.error("Command was not for us, machineIdentifier controller: %s, "
|
||||
"our machineIdentifier : %s"
|
||||
% (params['machineIdentifier'],
|
||||
window('plex_machineIdentifier')))
|
||||
return
|
||||
|
||||
library, key, query = PlexFunctions.ParseContainerKey(
|
||||
params['containerKey'])
|
||||
# Construct a container key that works always (get rid of playlist args)
|
||||
window('containerKey', '/'+library+'/'+key)
|
||||
|
||||
if 'playQueues' in library:
|
||||
log.debug("Playing a playQueue. Query was: %s" % query)
|
||||
# Playing a playlist that we need to fetch from PMS
|
||||
xml = PlexFunctions.GetPlayQueue(key)
|
||||
if xml is None:
|
||||
log.error("Error getting PMS playlist for key %s" % key)
|
||||
return
|
||||
else:
|
||||
resume = PlexFunctions.ConvertPlexToKodiTime(
|
||||
params.get('offset', 0))
|
||||
itemids = []
|
||||
for item in xml:
|
||||
itemids.append(item.get('ratingKey'))
|
||||
return playlist.Playlist().playAll(itemids, resume)
|
||||
|
||||
else:
|
||||
log.error("Not knowing what to do for now - no playQueue sent")
|
||||
|
||||
|
||||
def chooseServer():
|
||||
"""
|
||||
Lets user choose from list of PMS
|
||||
|
@ -180,40 +146,6 @@ def playWatchLater(itemid, viewOffset):
|
|||
return pbutils.PlaybackUtils(xml).play(None, 'plexnode')
|
||||
|
||||
|
||||
def doPlayback(itemid, dbid):
|
||||
"""
|
||||
Called only for a SINGLE element, not playQueues
|
||||
|
||||
Always to return with a "setResolvedUrl"
|
||||
"""
|
||||
if window('plex_authenticated') != "true":
|
||||
log.error('Not yet authenticated for a PMS, abort starting playback')
|
||||
# Not yet connected to a PMS server
|
||||
xbmcgui.Dialog().notification(
|
||||
addonName,
|
||||
lang(39210),
|
||||
xbmcgui.NOTIFICATION_ERROR,
|
||||
7000,
|
||||
True)
|
||||
return xbmcplugin.setResolvedUrl(
|
||||
int(sys.argv[1]), False, xbmcgui.ListItem())
|
||||
|
||||
xml = PlexFunctions.GetPlexMetadata(itemid)
|
||||
if xml in (None, 401):
|
||||
return xbmcplugin.setResolvedUrl(
|
||||
int(sys.argv[1]), False, xbmcgui.ListItem())
|
||||
if xml[0].attrib.get('type') == 'photo':
|
||||
# Photo
|
||||
API = PlexAPI.API(xml[0])
|
||||
listitem = API.CreateListItemFromPlexItem()
|
||||
API.AddStreamInfo(listitem)
|
||||
pbutils.PlaybackUtils(xml[0]).setArtwork(listitem)
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
else:
|
||||
# Video
|
||||
return pbutils.PlaybackUtils(xml).play(itemid, dbid)
|
||||
|
||||
|
||||
##### DO RESET AUTH #####
|
||||
def resetAuth():
|
||||
# User tried login and failed too many times
|
||||
|
@ -497,7 +429,7 @@ def createListItemFromEmbyItem(item,art=None,doUtils=downloadutils.DownloadUtils
|
|||
li.setInfo('pictures', infoLabels={ "picturepath": img_path, "date": premieredate, "size": picture.get("Size"), "exif:width": str(picture.get("Width")), "exif:height": str(picture.get("Height")), "title": title})
|
||||
li.setThumbnailImage(img_path)
|
||||
li.setProperty("plot",API.getOverview())
|
||||
li.setIconImage('DefaultPicture.png')
|
||||
li.setArt({'icon': 'DefaultPicture.png'})
|
||||
else:
|
||||
#normal video items
|
||||
li.setProperty('IsPlayable', 'true')
|
||||
|
@ -541,7 +473,7 @@ def createListItemFromEmbyItem(item,art=None,doUtils=downloadutils.DownloadUtils
|
|||
if allart.get('Primary'):
|
||||
li.setThumbnailImage(allart.get('Primary'))
|
||||
else: li.setThumbnailImage('DefaultTVShows.png')
|
||||
li.setIconImage('DefaultTVShows.png')
|
||||
li.setArt({'icon': 'DefaultTVShows.png'})
|
||||
if not allart.get('Background'): #add image as fanart for use with skinhelper auto thumb/backgrund creation
|
||||
li.setArt( {"fanart": allart.get('Primary') } )
|
||||
else:
|
||||
|
@ -663,7 +595,7 @@ def createListItem(item, appendShowTitle=False, appendSxxExx=False):
|
|||
li.setProperty('totaltime', str(item['resume']['total']))
|
||||
li.setArt(item['art'])
|
||||
li.setThumbnailImage(item['art'].get('thumb',''))
|
||||
li.setIconImage('DefaultTVShows.png')
|
||||
li.setArt({'icon': 'DefaultTVShows.png'})
|
||||
li.setProperty('dbid', str(item['episodeid']))
|
||||
li.setProperty('fanart_image', item['art'].get('tvshow.fanart',''))
|
||||
for key, value in item['streamdetails'].iteritems():
|
||||
|
@ -1167,7 +1099,7 @@ def getOnDeck(viewid, mediatype, tagname, limit):
|
|||
'id': API.getRatingKey(),
|
||||
'dbid': listitem.getProperty('dbid')
|
||||
}
|
||||
url = "plugin://plugin.video.plexkodiconnect.tvshows/?%s" \
|
||||
url = "plugin://plugin.video.plexkodiconnect/tvshows/?%s" \
|
||||
% urllib.urlencode(params)
|
||||
xbmcplugin.addDirectoryItem(
|
||||
handle=int(sys.argv[1]),
|
||||
|
|
|
@ -407,7 +407,7 @@ class Movies(Items):
|
|||
path = playurl.replace(filename, "")
|
||||
if doIndirect:
|
||||
# Set plugin path and media flags using real filename
|
||||
path = "plugin://plugin.video.plexkodiconnect.movies/"
|
||||
path = "plugin://plugin.video.plexkodiconnect/movies/"
|
||||
params = {
|
||||
'filename': API.getKey(),
|
||||
'id': itemid,
|
||||
|
@ -675,7 +675,7 @@ class TVShows(Items):
|
|||
toplevelpath = "%s/" % dirname(dirname(path))
|
||||
if doIndirect:
|
||||
# Set plugin path
|
||||
toplevelpath = "plugin://plugin.video.plexkodiconnect.tvshows/"
|
||||
toplevelpath = "plugin://plugin.video.plexkodiconnect/tvshows/"
|
||||
path = "%s%s/" % (toplevelpath, itemid)
|
||||
|
||||
# Add top path
|
||||
|
@ -956,7 +956,7 @@ class TVShows(Items):
|
|||
filename = playurl.rsplit('/', 1)[1]
|
||||
else:
|
||||
filename = 'file_not_found.mkv'
|
||||
path = "plugin://plugin.video.plexkodiconnect.tvshows/%s/" % seriesId
|
||||
path = "plugin://plugin.video.plexkodiconnect/tvshows/%s/" % seriesId
|
||||
params = {
|
||||
'filename': tryEncode(filename),
|
||||
'id': itemid,
|
||||
|
@ -966,7 +966,7 @@ class TVShows(Items):
|
|||
filename = "%s?%s" % (path, tryDecode(urlencode(params)))
|
||||
playurl = filename
|
||||
parentPathId = self.kodi_db.addPath(
|
||||
'plugin://plugin.video.plexkodiconnect.tvshows/')
|
||||
'plugin://plugin.video.plexkodiconnect/tvshows/')
|
||||
|
||||
# episodes table:
|
||||
# c18 - playurl
|
||||
|
@ -1093,7 +1093,7 @@ class TVShows(Items):
|
|||
self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
||||
if not self.directpath and resume:
|
||||
# Create additional entry for widgets. This is only required for plugin/episode.
|
||||
temppathid = self.kodi_db.getPath("plugin://plugin.video.plexkodiconnect.tvshows/")
|
||||
temppathid = self.kodi_db.getPath("plugin://plugin.video.plexkodiconnect/tvshows/")
|
||||
tempfileid = self.kodi_db.addFile(filename, temppathid)
|
||||
query = ' '.join((
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ class Kodidb_Functions():
|
|||
self.cursor.execute(
|
||||
query, ('movies',
|
||||
'metadata.local',
|
||||
'plugin://plugin.video.plexkodiconnect.movies%%'))
|
||||
'plugin://plugin.video.plexkodiconnect/movies%%'))
|
||||
|
||||
def getParentPathId(self, path):
|
||||
"""
|
||||
|
|
|
@ -164,20 +164,6 @@ class KodiMonitor(xbmc.Monitor):
|
|||
xbmc.sleep(5000)
|
||||
window('plex_runLibScan', value="full")
|
||||
|
||||
elif method == "Playlist.OnClear":
|
||||
pass
|
||||
|
||||
elif method == "Playlist.OnAdd":
|
||||
# User (or PKC) manipulated Kodi playlist
|
||||
# Data : {u'item': {u'type': u'movie', u'id': 3}, u'playlistid': 1,
|
||||
# u'position': 0}
|
||||
self.playqueue.kodi_onadd(data)
|
||||
|
||||
elif method == "Playlist.OnRemove":
|
||||
# User (or PKC) deleted a playlist item
|
||||
# Data: {u'position': 2, u'playlistid': 1}
|
||||
self.playqueue.kodi_onremove(data)
|
||||
|
||||
def PlayBackStart(self, data):
|
||||
"""
|
||||
Called whenever a playback is started
|
||||
|
|
41
resources/lib/monitor_kodi_play.py
Normal file
41
resources/lib/monitor_kodi_play.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
import logging
|
||||
from threading import Thread
|
||||
from Queue import Queue
|
||||
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import window, ThreadMethods
|
||||
|
||||
###############################################################################
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@ThreadMethods
|
||||
class Monitor_Kodi_Play(Thread):
|
||||
"""
|
||||
Monitors for new plays initiated on the Kodi side with addon paths.
|
||||
Immediately throws them into a queue to be processed by playback_starter
|
||||
"""
|
||||
# Borg - multiple instances, shared state
|
||||
def __init__(self, callback=None):
|
||||
self.mgr = callback
|
||||
self.playback_queue = Queue()
|
||||
Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
threadStopped = self.threadStopped
|
||||
queue = self.playback_queue
|
||||
log.info("----===## Starting Kodi_Play_Client ##===----")
|
||||
while not threadStopped():
|
||||
if window('plex_play_new_item'):
|
||||
queue.put(window('plex_play_new_item'))
|
||||
window('plex_play_new_item', clear=True)
|
||||
else:
|
||||
sleep(20)
|
||||
# Put one last item into the queue to let playback_starter end
|
||||
queue.put(None)
|
||||
log.info("----===## Kodi_Play_Client stopped ##===----")
|
44
resources/lib/pickler.py
Normal file
44
resources/lib/pickler.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
import logging
|
||||
import cPickle as Pickle
|
||||
|
||||
from utils import pickl_window
|
||||
###############################################################################
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
def pickle_me(obj, window_var='plex_result'):
|
||||
"""
|
||||
Pickles the obj to the window variable. Use to transfer Python
|
||||
objects between different PKC python instances (e.g. if default.py is
|
||||
called and you'd want to use the service.py instance)
|
||||
|
||||
obj can be pretty much any Python object. However, classes and
|
||||
functions won't work. See the Pickle documentation
|
||||
"""
|
||||
log.debug('Start pickling: %s' % obj)
|
||||
pickl_window(window_var, value=Pickle.dumps(obj))
|
||||
log.debug('Successfully pickled')
|
||||
|
||||
|
||||
def unpickle_me(window_var='plex_result'):
|
||||
"""
|
||||
Unpickles a Python object from the window variable window_var.
|
||||
Will then clear the window variable!
|
||||
"""
|
||||
result = pickl_window(window_var)
|
||||
pickl_window(window_var, clear=True)
|
||||
log.debug('Start unpickling')
|
||||
obj = Pickle.loads(result)
|
||||
log.debug('Successfully unpickled: %s' % obj)
|
||||
return obj
|
||||
|
||||
|
||||
class Playback_Successful(object):
|
||||
"""
|
||||
Used to communicate with another PKC Python instance
|
||||
"""
|
||||
listitem = None
|
92
resources/lib/playback_starter.py
Normal file
92
resources/lib/playback_starter.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
import logging
|
||||
from threading import Thread
|
||||
from urlparse import parse_qsl
|
||||
|
||||
from PKC_listitem import PKC_ListItem
|
||||
from pickler import pickle_me, Playback_Successful
|
||||
from playbackutils import PlaybackUtils
|
||||
from utils import window
|
||||
from PlexFunctions import GetPlexMetadata
|
||||
from PlexAPI import API
|
||||
from playqueue import lock
|
||||
|
||||
###############################################################################
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
class Playback_Starter(Thread):
|
||||
"""
|
||||
Processes new plays
|
||||
"""
|
||||
def __init__(self, callback=None):
|
||||
self.mgr = callback
|
||||
self.playqueue = self.mgr.playqueue
|
||||
Thread.__init__(self)
|
||||
|
||||
def process_play(self, plex_id, kodi_id=None):
|
||||
"""
|
||||
Processes Kodi playback init for ONE item
|
||||
"""
|
||||
log.info("Process_play called with plex_id %s, kodi_id %s"
|
||||
% (plex_id, kodi_id))
|
||||
if window('plex_authenticated') != "true":
|
||||
log.error('Not yet authenticated for PMS, abort starting playback')
|
||||
# Todo: Warn user with dialog
|
||||
return
|
||||
xml = GetPlexMetadata(plex_id)
|
||||
if xml[0].attrib.get('type') == 'photo':
|
||||
# Photo
|
||||
result = Playback_Successful()
|
||||
listitem = PKC_ListItem()
|
||||
api = API(xml[0])
|
||||
listitem = api.CreateListItemFromPlexItem(listitem)
|
||||
api.AddStreamInfo(listitem)
|
||||
listitem = PlaybackUtils(xml[0], self.mgr).setArtwork(listitem)
|
||||
result.listitem = listitem
|
||||
else:
|
||||
# Video and Music
|
||||
with lock:
|
||||
result = PlaybackUtils(xml[0], self.mgr).play(
|
||||
plex_id,
|
||||
kodi_id,
|
||||
xml.attrib.get('librarySectionUUID'))
|
||||
log.info('Done process_play, playqueues: %s'
|
||||
% self.playqueue.playqueues)
|
||||
return result
|
||||
|
||||
def triage(self, item):
|
||||
mode, params = item.split('?', 1)
|
||||
params = dict(parse_qsl(params))
|
||||
log.debug('Received mode: %s, params: %s' % (mode, params))
|
||||
try:
|
||||
if mode == 'play':
|
||||
result = self.process_play(params.get('id'),
|
||||
params.get('dbid'))
|
||||
elif mode == 'companion':
|
||||
result = self.process_companion()
|
||||
except:
|
||||
log.error('Error encountered for mode %s, params %s'
|
||||
% (mode, params))
|
||||
import traceback
|
||||
log.error(traceback.format_exc())
|
||||
# Let default.py know!
|
||||
pickle_me(None)
|
||||
else:
|
||||
pickle_me(result)
|
||||
|
||||
def run(self):
|
||||
queue = self.mgr.monitor_kodi_play.playback_queue
|
||||
log.info("----===## Starting Playback_Starter ##===----")
|
||||
while True:
|
||||
item = queue.get()
|
||||
if item is None:
|
||||
# Need to shutdown - initiated by monitor_kodi_play
|
||||
break
|
||||
else:
|
||||
self.triage(item)
|
||||
queue.task_done()
|
||||
log.info("----===## Playback_Starter stopped ##===----")
|
|
@ -3,21 +3,25 @@
|
|||
###############################################################################
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from urllib import urlencode
|
||||
from threading import Thread
|
||||
|
||||
import xbmc
|
||||
from xbmc import getCondVisibility, Player
|
||||
import xbmcgui
|
||||
import xbmcplugin
|
||||
|
||||
import playutils as putils
|
||||
from playqueue import Playqueue
|
||||
from utils import window, settings, tryEncode, tryDecode
|
||||
import downloadutils
|
||||
|
||||
import PlexAPI
|
||||
import PlexFunctions as PF
|
||||
import playlist_func as PL
|
||||
from PlexAPI import API
|
||||
from PlexFunctions import GetPlexPlaylist, KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE, \
|
||||
KODITYPE_FROM_PLEXTYPE
|
||||
from PKC_listitem import PKC_ListItem as ListItem
|
||||
from playlist_func import add_item_to_kodi_playlist, \
|
||||
get_playlist_details_from_xml, add_listitem_to_Kodi_playlist, \
|
||||
add_listitem_to_playlist, remove_from_Kodi_playlist
|
||||
from playqueue import lock
|
||||
from pickler import Playback_Successful
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -30,47 +34,47 @@ addonName = "PlexKodiConnect"
|
|||
|
||||
class PlaybackUtils():
|
||||
|
||||
def __init__(self, item):
|
||||
|
||||
def __init__(self, item, callback):
|
||||
self.mgr = callback
|
||||
self.item = item
|
||||
self.API = PlexAPI.API(item)
|
||||
|
||||
self.userid = window('currUserId')
|
||||
self.server = window('pms_server')
|
||||
self.pl = Playqueue().get_playqueue_from_type(
|
||||
PF.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[item[0].attrib.get('type')])
|
||||
|
||||
def play(self, itemid, dbid=None):
|
||||
self.api = API(item)
|
||||
self.playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
||||
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[self.api.getType()])
|
||||
|
||||
def play(self, plex_id, kodi_id=None, plex_lib_UUID=None):
|
||||
"""
|
||||
plex_lib_UUID: xml attribute 'librarySectionUUID', needed for posting
|
||||
to the PMS
|
||||
"""
|
||||
log.info("Playbackutils called")
|
||||
item = self.item
|
||||
# Hack to get only existing entry in PMS response for THIS instance of
|
||||
# playbackutils :-)
|
||||
self.API = PlexAPI.API(item[0])
|
||||
API = self.API
|
||||
listitem = xbmcgui.ListItem()
|
||||
playutils = putils.PlayUtils(item[0])
|
||||
|
||||
log.info("Play called.")
|
||||
log.debug('Playqueue: %s' % self.pl)
|
||||
api = self.api
|
||||
playqueue = self.playqueue
|
||||
xml = None
|
||||
result = Playback_Successful()
|
||||
listitem = ListItem()
|
||||
playutils = putils.PlayUtils(item)
|
||||
playurl = playutils.getPlayUrl()
|
||||
if not playurl:
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||
log.error('No playurl found, aborting')
|
||||
return
|
||||
|
||||
if dbid in (None, 'plextrailer', 'plexnode'):
|
||||
# Item is not in Kodi database, is a trailer or plex redirect
|
||||
if kodi_id in (None, 'plextrailer', 'plexnode'):
|
||||
# Item is not in Kodi database, is a trailer/clip or plex redirect
|
||||
# e.g. plex.tv watch later
|
||||
API.CreateListItemFromPlexItem(listitem)
|
||||
api.CreateListItemFromPlexItem(listitem)
|
||||
self.setArtwork(listitem)
|
||||
if dbid == 'plexnode':
|
||||
if kodi_id == 'plexnode':
|
||||
# Need to get yet another xml to get final url
|
||||
window('emby_%s.playmethod' % playurl, clear=True)
|
||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||
'{server}%s' % item[0][0][0].attrib.get('key'))
|
||||
if xml in (None, 401):
|
||||
'{server}%s' % item[0][0].attrib.get('key'))
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (TypeError, AttributeError):
|
||||
log.error('Could not download %s'
|
||||
% item[0][0][0].attrib.get('key'))
|
||||
return xbmcplugin.setResolvedUrl(
|
||||
int(sys.argv[1]), False, listitem)
|
||||
% item[0][0].attrib.get('key'))
|
||||
return
|
||||
playurl = tryEncode(xml[0].attrib.get('key'))
|
||||
window('emby_%s.playmethod' % playurl, value='DirectStream')
|
||||
|
||||
|
@ -82,20 +86,23 @@ class PlaybackUtils():
|
|||
window('emby_%s.playmethod' % playurl, "Transcode")
|
||||
listitem.setPath(playurl)
|
||||
self.setProperties(playurl, listitem)
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
result.listitem = listitem
|
||||
return result
|
||||
|
||||
############### ORGANIZE CURRENT PLAYLIST ################
|
||||
kodi_type = KODITYPE_FROM_PLEXTYPE[api.getType()]
|
||||
kodi_id = int(kodi_id)
|
||||
|
||||
# ORGANIZE CURRENT PLAYLIST ################
|
||||
contextmenu_play = window('plex_contextplay') == 'true'
|
||||
window('plex_contextplay', clear=True)
|
||||
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
|
||||
kodiPl = self.pl.kodi_pl
|
||||
sizePlaylist = kodiPl.size()
|
||||
homeScreen = getCondVisibility('Window.IsActive(home)')
|
||||
sizePlaylist = len(playqueue.items)
|
||||
if contextmenu_play:
|
||||
# Need to start with the items we're inserting here
|
||||
startPos = sizePlaylist
|
||||
else:
|
||||
# Can return -1
|
||||
startPos = max(kodiPl.getposition(), 0)
|
||||
startPos = max(playqueue.kodi_pl.getposition(), 0)
|
||||
self.currentPosition = startPos
|
||||
|
||||
propertiesPlayback = window('plex_playbackProps') == "true"
|
||||
|
@ -107,8 +114,8 @@ class PlaybackUtils():
|
|||
log.info("Playlist plugin position: %s" % self.currentPosition)
|
||||
log.info("Playlist size: %s" % sizePlaylist)
|
||||
|
||||
############### RESUME POINT ################
|
||||
seektime, runtime = API.getRuntime()
|
||||
# RESUME POINT ################
|
||||
seektime, runtime = api.getRuntime()
|
||||
if window('plex_customplaylist.seektime'):
|
||||
# Already got seektime, e.g. from playqueue & Plex companion
|
||||
seektime = int(window('plex_customplaylist.seektime'))
|
||||
|
@ -116,64 +123,69 @@ class PlaybackUtils():
|
|||
# We need to ensure we add the intro and additional parts only once.
|
||||
# Otherwise we get a loop.
|
||||
if not propertiesPlayback:
|
||||
|
||||
window('plex_playbackProps', value="true")
|
||||
log.info("Setting up properties in playlist.")
|
||||
|
||||
# Post playQueue to PMS
|
||||
# Where will the player need to start?
|
||||
# Do we need to get trailers?
|
||||
trailers = False
|
||||
if settings('enableCinema') == "true":
|
||||
if (api.getType() == 'movie' and not seektime and
|
||||
settings('enableCinema') == "true"):
|
||||
if settings('askCinema') == "true":
|
||||
trailers = xbmcgui.Dialog().yesno(addonName,
|
||||
"Play trailers?")
|
||||
trailers = xbmcgui.Dialog().yesno(
|
||||
addonName,
|
||||
"Play trailers?")
|
||||
else:
|
||||
trailers = True
|
||||
xml = PF.GetPlexPlaylist(
|
||||
itemid,
|
||||
item.attrib.get('librarySectionUUID'),
|
||||
mediatype=API.getType(),
|
||||
# Post to the PMS. REUSE THE PLAYQUEUE!
|
||||
xml = GetPlexPlaylist(
|
||||
plex_id,
|
||||
plex_lib_UUID,
|
||||
mediatype=api.getType(),
|
||||
trailers=trailers)
|
||||
# Save playQueueID for other PKC python instance & kodimonitor
|
||||
window('plex_playQueueID', value=xml.attrib.get('playQueueID'))
|
||||
log.debug('xml: ID: %s' % xml.attrib['playQueueID'])
|
||||
get_playlist_details_from_xml(playqueue, xml=xml)
|
||||
log.debug('finished ')
|
||||
|
||||
if (not homeScreen and not seektime and
|
||||
window('plex_customplaylist') != "true" and
|
||||
not contextmenu_play):
|
||||
# Need to add a dummy file because the first item will fail
|
||||
log.debug("Adding dummy file to playlist.")
|
||||
# Make sure Kodimonitor recognizes dummy
|
||||
listitem.setLabel('plex_dummyfile')
|
||||
dummyPlaylist = True
|
||||
PL.add_listitem_to_Kodi_playlist(
|
||||
self.pl,
|
||||
listitem,
|
||||
add_listitem_to_Kodi_playlist(
|
||||
playqueue,
|
||||
startPos,
|
||||
xbmcgui.ListItem(),
|
||||
playurl,
|
||||
startPos)
|
||||
xml[0])
|
||||
# Remove the original item from playlist
|
||||
PL.remove_from_Kodi_playlist(self.pl, startPos+1)
|
||||
remove_from_Kodi_playlist(
|
||||
playqueue,
|
||||
startPos+1)
|
||||
# Readd the original item to playlist - via jsonrpc so we have
|
||||
# full metadata
|
||||
PL.insert_into_Kodi_playlist(
|
||||
self.pl,
|
||||
add_item_to_kodi_playlist(
|
||||
playqueue,
|
||||
self.currentPosition+1,
|
||||
dbid,
|
||||
PF.KODITYPE_FROM_PLEXTYPE[API.getType()])
|
||||
kodi_id=kodi_id,
|
||||
kodi_type=kodi_type,
|
||||
file=playurl)
|
||||
self.currentPosition += 1
|
||||
|
||||
############### -- CHECK FOR INTROS ################
|
||||
if trailers and not seektime:
|
||||
# if we have any play them when the movie/show is not being resumed
|
||||
# -- ADD TRAILERS ################
|
||||
if trailers:
|
||||
introsPlaylist = self.AddTrailers(xml)
|
||||
|
||||
############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ##############
|
||||
|
||||
# -- ADD MAIN ITEM ONLY FOR HOMESCREEN ##############
|
||||
if homeScreen and not seektime and not sizePlaylist:
|
||||
# Extend our current playlist with the actual item to play
|
||||
# only if there's no playlist first
|
||||
log.info("Adding main item to playlist.")
|
||||
PL.add_dbid_to_Kodi_playlist(
|
||||
self.pl,
|
||||
dbid=dbid,
|
||||
mediatype=PF.KODITYPE_FROM_PLEXTYPE[API.getType()])
|
||||
add_item_to_kodi_playlist(
|
||||
playqueue,
|
||||
self.currentPosition,
|
||||
kodi_id,
|
||||
kodi_type)
|
||||
|
||||
elif contextmenu_play:
|
||||
if window('useDirectPaths') == 'true':
|
||||
|
@ -187,17 +199,16 @@ class PlaybackUtils():
|
|||
listitem, tryDecode(playurl)))
|
||||
window('emby_%s.playmethod' % playurl,
|
||||
value="Transcode")
|
||||
api.CreateListItemFromPlexItem(listitem)
|
||||
self.setProperties(playurl, listitem)
|
||||
self.setArtwork(listitem)
|
||||
API.CreateListItemFromPlexItem(listitem)
|
||||
kodiPl.add(playurl, listitem, index=self.currentPosition+1)
|
||||
else:
|
||||
# Full metadata
|
||||
PL.insert_into_Kodi_playlist(
|
||||
self.pl,
|
||||
self.pl.insertintoPlaylist(
|
||||
self.currentPosition+1,
|
||||
dbid=dbid,
|
||||
mediatype=PF.KODITYPE_FROM_PLEXTYPE[API.getType()])
|
||||
kodi_id,
|
||||
kodi_type)
|
||||
self.currentPosition += 1
|
||||
if seektime:
|
||||
window('plex_customplaylist.seektime', value=str(seektime))
|
||||
|
@ -205,44 +216,50 @@ class PlaybackUtils():
|
|||
# Ensure that additional parts are played after the main item
|
||||
self.currentPosition += 1
|
||||
|
||||
############### -- CHECK FOR ADDITIONAL PARTS ################
|
||||
if len(item[0][0]) > 1:
|
||||
# -- CHECK FOR ADDITIONAL PARTS ################
|
||||
if len(item[0]) > 1:
|
||||
# Only add to the playlist after intros have played
|
||||
for counter, part in enumerate(item[0][0]):
|
||||
for counter, part in enumerate(item[0]):
|
||||
# Never add first part
|
||||
if counter == 0:
|
||||
continue
|
||||
# Set listitem and properties for each additional parts
|
||||
API.setPartNumber(counter)
|
||||
api.setPartNumber(counter)
|
||||
additionalListItem = xbmcgui.ListItem()
|
||||
additionalPlayurl = playutils.getPlayUrl(
|
||||
partNumber=counter)
|
||||
log.debug("Adding additional part: %s, url: %s"
|
||||
% (counter, additionalPlayurl))
|
||||
|
||||
api.CreateListItemFromPlexItem(additionalListItem)
|
||||
self.setProperties(additionalPlayurl, additionalListItem)
|
||||
self.setArtwork(additionalListItem)
|
||||
# NEW to Plex
|
||||
API.CreateListItemFromPlexItem(additionalListItem)
|
||||
|
||||
kodiPl.add(additionalPlayurl, additionalListItem,
|
||||
index=self.currentPosition)
|
||||
add_listitem_to_playlist(
|
||||
playqueue,
|
||||
self.currentPosition,
|
||||
additionalListItem,
|
||||
kodi_id=kodi_id,
|
||||
kodi_type=kodi_type,
|
||||
plex_id=plex_id,
|
||||
file=additionalPlayurl)
|
||||
self.currentPosition += 1
|
||||
API.setPartNumber(0)
|
||||
api.setPartNumber(0)
|
||||
|
||||
if dummyPlaylist:
|
||||
# Added a dummy file to the playlist,
|
||||
# because the first item is going to fail automatically.
|
||||
log.info("Processed as a playlist. First item is skipped.")
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||
# Delete the item that's gonna fail!
|
||||
with lock:
|
||||
del playqueue.items[startPos]
|
||||
# Don't attach listitem
|
||||
return result
|
||||
|
||||
# We just skipped adding properties. Reset flag for next time.
|
||||
elif propertiesPlayback:
|
||||
log.debug("Resetting properties playback flag.")
|
||||
window('plex_playbackProps', clear=True)
|
||||
|
||||
#self.pl.verifyPlaylist()
|
||||
########## SETUP MAIN ITEM ##########
|
||||
# SETUP MAIN ITEM ##########
|
||||
# For transcoding only, ask for audio/subs pref
|
||||
if (window('emby_%s.playmethod' % playurl) == "Transcode" and
|
||||
not contextmenu_play):
|
||||
|
@ -254,40 +271,42 @@ class PlaybackUtils():
|
|||
listitem.setPath(playurl)
|
||||
self.setProperties(playurl, listitem)
|
||||
|
||||
############### PLAYBACK ################
|
||||
# PLAYBACK ################
|
||||
if (homeScreen and seektime and window('plex_customplaylist') != "true"
|
||||
and not contextmenu_play):
|
||||
log.info("Play as a widget item.")
|
||||
API.CreateListItemFromPlexItem(listitem)
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
log.info("Play as a widget item")
|
||||
api.CreateListItemFromPlexItem(listitem)
|
||||
result.listitem = listitem
|
||||
return result
|
||||
|
||||
elif ((introsPlaylist and window('plex_customplaylist') == "true") or
|
||||
(homeScreen and not sizePlaylist) or
|
||||
contextmenu_play):
|
||||
# Playlist was created just now, play it.
|
||||
# Contextmenu plays always need this
|
||||
log.info("Play playlist.")
|
||||
xbmcplugin.endOfDirectory(int(sys.argv[1]), True, False, False)
|
||||
xbmc.Player().play(kodiPl, startpos=startPos)
|
||||
|
||||
log.info("Play playlist")
|
||||
# Need a separate thread because Player won't return in time
|
||||
thread = Thread(target=Player().play,
|
||||
args=(playqueue.kodi_pl, None, False, startPos))
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
# Don't attach listitem
|
||||
return result
|
||||
else:
|
||||
log.info("Play as a regular item.")
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
log.info("Play as a regular item")
|
||||
result.listitem = listitem
|
||||
return result
|
||||
|
||||
def AddTrailers(self, xml):
|
||||
"""
|
||||
Adds trailers to a movie, if applicable. Returns True if trailers were
|
||||
added
|
||||
"""
|
||||
# Failure when downloading trailer playQueue
|
||||
if xml in (None, 401):
|
||||
return False
|
||||
# Failure when getting trailers, e.g. when no plex pass
|
||||
if xml.attrib.get('size') == '1':
|
||||
return False
|
||||
|
||||
# Playurl needs to point back so we can get metadata!
|
||||
path = "plugin://plugin.video.plexkodiconnect.movies/"
|
||||
path = "plugin://plugin.video.plexkodiconnect/movies/"
|
||||
params = {
|
||||
'mode': "play",
|
||||
'dbid': 'plextrailer'
|
||||
|
@ -296,28 +315,29 @@ class PlaybackUtils():
|
|||
# Don't process the last item - it's the original movie
|
||||
if counter == len(xml)-1:
|
||||
break
|
||||
# The server randomly returns intros, process them.
|
||||
# introListItem = xbmcgui.ListItem()
|
||||
# introPlayurl = putils.PlayUtils(intro).getPlayUrl()
|
||||
introAPI = PlexAPI.API(intro)
|
||||
introAPI = API(intro)
|
||||
listitem = introAPI.CreateListItemFromPlexItem()
|
||||
params['id'] = introAPI.getRatingKey()
|
||||
params['filename'] = introAPI.getKey()
|
||||
introPlayurl = path + '?' + urlencode(params)
|
||||
self.setArtwork(listitem, introAPI)
|
||||
# Overwrite the Plex url
|
||||
listitem.setPath(introPlayurl)
|
||||
log.info("Adding Intro: %s" % introPlayurl)
|
||||
|
||||
PL.insert_into_Kodi_playlist(
|
||||
self.pl,
|
||||
add_listitem_to_Kodi_playlist(
|
||||
self.playqueue,
|
||||
self.currentPosition,
|
||||
url=introPlayurl)
|
||||
listitem,
|
||||
introPlayurl,
|
||||
intro)
|
||||
self.currentPosition += 1
|
||||
|
||||
return True
|
||||
|
||||
def setProperties(self, playurl, listitem):
|
||||
# Set all properties necessary for plugin path playback
|
||||
itemid = self.API.getRatingKey()
|
||||
itemtype = self.API.getType()
|
||||
userdata = self.API.getUserData()
|
||||
itemid = self.api.getRatingKey()
|
||||
itemtype = self.api.getType()
|
||||
userdata = self.api.getUserData()
|
||||
|
||||
embyitem = "emby_%s" % playurl
|
||||
window('%s.runtime' % embyitem, value=str(userdata['Runtime']))
|
||||
|
@ -327,20 +347,22 @@ class PlaybackUtils():
|
|||
|
||||
if itemtype == "episode":
|
||||
window('%s.refreshid' % embyitem,
|
||||
value=self.API.getParentRatingKey())
|
||||
value=self.api.getParentRatingKey())
|
||||
else:
|
||||
window('%s.refreshid' % embyitem, value=itemid)
|
||||
|
||||
# Append external subtitles to stream
|
||||
playmethod = window('%s.playmethod' % embyitem)
|
||||
if playmethod in ("DirectStream", "DirectPlay"):
|
||||
subtitles = self.API.externalSubs(playurl)
|
||||
subtitles = self.api.externalSubs(playurl)
|
||||
listitem.setSubtitles(subtitles)
|
||||
|
||||
self.setArtwork(listitem)
|
||||
|
||||
def setArtwork(self, listItem):
|
||||
allartwork = self.API.getAllArtwork(parentInfo=True)
|
||||
def setArtwork(self, listItem, api=None):
|
||||
if api is None:
|
||||
api = self.api
|
||||
allartwork = api.getAllArtwork(parentInfo=True)
|
||||
arttypes = {
|
||||
'poster': "Primary",
|
||||
'tvshow.poster': "Thumb",
|
||||
|
|
|
@ -3,7 +3,7 @@ from urllib import quote
|
|||
|
||||
import embydb_functions as embydb
|
||||
from downloadutils import DownloadUtils as DU
|
||||
from utils import window, JSONRPC, tryEncode
|
||||
from utils import JSONRPC, tryEncode, tryDecode
|
||||
from PlexAPI import API
|
||||
|
||||
###############################################################################
|
||||
|
@ -12,6 +12,9 @@ log = logging.getLogger("PLEX."+__name__)
|
|||
|
||||
###############################################################################
|
||||
|
||||
# kodi_item:
|
||||
# {u'type': u'movie', u'id': 3, 'file': path-to-file}
|
||||
|
||||
|
||||
class Playlist_Object_Baseclase(object):
|
||||
playlistid = None # Kodi playlist ID, [int]
|
||||
|
@ -25,8 +28,6 @@ class Playlist_Object_Baseclase(object):
|
|||
selectedItemOffset = None
|
||||
shuffled = 0 # [int], 0: not shuffled, 1: ??? 2: ???
|
||||
repeat = 0 # [int], 0: not repeated, 1: ??? 2: ???
|
||||
# Hack to later ignore all Kodi playlist adds that PKC did (Kodimonitor)
|
||||
PKC_playlist_edits = []
|
||||
|
||||
def __repr__(self):
|
||||
answ = "<%s: " % (self.__class__.__name__)
|
||||
|
@ -52,7 +53,6 @@ class Playlist_Object_Baseclase(object):
|
|||
self.selectedItemOffset = None
|
||||
self.shuffled = 0
|
||||
self.repeat = 0
|
||||
self.PKC_playlist_edits = []
|
||||
log.debug('Playlist cleared: %s' % self)
|
||||
|
||||
def log_Kodi_playlist(self):
|
||||
|
@ -84,7 +84,7 @@ class Playlist_Item(object):
|
|||
return answ[:-2] + ">"
|
||||
|
||||
|
||||
def playlist_item_from_kodi_item(kodi_item):
|
||||
def playlist_item_from_kodi(kodi_item):
|
||||
"""
|
||||
Turns the JSON answer from Kodi into a playlist element
|
||||
|
||||
|
@ -99,14 +99,15 @@ def playlist_item_from_kodi_item(kodi_item):
|
|||
kodi_item['type'])
|
||||
try:
|
||||
item.plex_id = emby_dbitem[0]
|
||||
item.plex_UUID = emby_dbitem[0]
|
||||
item.plex_UUID = emby_dbitem[0] # we dont need the uuid yet :-)
|
||||
except TypeError:
|
||||
pass
|
||||
item.file = kodi_item.get('file') if kodi_item.get('file') else None
|
||||
item.kodi_type = kodi_item.get('type') if kodi_item.get('type') else None
|
||||
item.file = kodi_item.get('file')
|
||||
item.kodi_type = kodi_item.get('type')
|
||||
if item.plex_id is None:
|
||||
item.uri = 'library://whatever/item/%s' % quote(item.file, safe='')
|
||||
else:
|
||||
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
|
||||
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
||||
(item.plex_UUID, item.plex_id))
|
||||
return item
|
||||
|
@ -115,6 +116,8 @@ def playlist_item_from_kodi_item(kodi_item):
|
|||
def playlist_item_from_plex(plex_id):
|
||||
"""
|
||||
Returns a playlist element providing the plex_id ("ratingKey")
|
||||
|
||||
Returns a Playlist_Item
|
||||
"""
|
||||
item = Playlist_Item()
|
||||
item.plex_id = plex_id
|
||||
|
@ -128,6 +131,26 @@ def playlist_item_from_plex(plex_id):
|
|||
return item
|
||||
|
||||
|
||||
def playlist_item_from_xml(playlist, xml_video_element):
|
||||
"""
|
||||
Returns a playlist element for the playqueue using the Plex xml
|
||||
"""
|
||||
item = Playlist_Item()
|
||||
api = API(xml_video_element)
|
||||
item.plex_id = api.getRatingKey()
|
||||
item.ID = xml_video_element.attrib['%sItemID' % playlist.kind]
|
||||
item.guid = xml_video_element.attrib.get('guid')
|
||||
if item.plex_id:
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
db_element = emby_db.getItem_byId(item.plex_id)
|
||||
try:
|
||||
item.kodi_id, item.kodi_type = int(db_element[0]), db_element[4]
|
||||
except TypeError:
|
||||
pass
|
||||
log.debug('Created new playlist item from xml: %s' % item)
|
||||
return item
|
||||
|
||||
|
||||
def _log_xml(xml):
|
||||
try:
|
||||
xml.attrib
|
||||
|
@ -154,7 +177,7 @@ def _get_playListVersion_from_xml(playlist, xml):
|
|||
return True
|
||||
|
||||
|
||||
def _get_playlist_details_from_xml(playlist, xml):
|
||||
def get_playlist_details_from_xml(playlist, xml):
|
||||
"""
|
||||
Takes a PMS xml as input and overwrites all the playlist's details, e.g.
|
||||
playlist.ID with the XML's playQueueID
|
||||
|
@ -174,16 +197,42 @@ def _get_playlist_details_from_xml(playlist, xml):
|
|||
log.error(traceback.format_exc())
|
||||
_log_xml(xml)
|
||||
raise KeyError
|
||||
log.debug('Updated playlist from xml: %s' % playlist)
|
||||
|
||||
|
||||
def update_playlist_from_PMS(playlist, playlist_id=None, xml=None):
|
||||
"""
|
||||
Updates Kodi playlist using a new PMS playlist. Pass in playlist_id if we
|
||||
need to fetch a new playqueue
|
||||
|
||||
If an xml is passed in, the playlist will be overwritten with its info
|
||||
"""
|
||||
if xml is None:
|
||||
xml = get_PMS_playlist(playlist, playlist_id)
|
||||
try:
|
||||
xml.attrib['%sVersion' % playlist.kind]
|
||||
except:
|
||||
log.error('Could not download Plex playlist.')
|
||||
return
|
||||
# Clear our existing playlist and the associated Kodi playlist
|
||||
playlist.clear()
|
||||
# Set new values
|
||||
get_playlist_details_from_xml(playlist, xml)
|
||||
for plex_item in xml:
|
||||
playlist.items.append(add_to_Kodi_playlist(playlist, plex_item))
|
||||
|
||||
|
||||
def init_Plex_playlist(playlist, plex_id=None, kodi_item=None):
|
||||
"""
|
||||
Supply either with a plex_id OR the data supplied by Kodi JSON-RPC
|
||||
Initializes the Plex side without changing the Kodi playlists
|
||||
|
||||
WILL ALSO UPDATE OUR PLAYLISTS
|
||||
"""
|
||||
log.debug('Initializing the playlist %s on the Plex side' % playlist)
|
||||
if plex_id:
|
||||
item = playlist_item_from_plex(plex_id)
|
||||
else:
|
||||
item = playlist_item_from_kodi_item(kodi_item)
|
||||
item = playlist_item_from_kodi(kodi_item)
|
||||
params = {
|
||||
'next': 0,
|
||||
'type': playlist.type,
|
||||
|
@ -192,22 +241,74 @@ def init_Plex_playlist(playlist, plex_id=None, kodi_item=None):
|
|||
xml = DU().downloadUrl(url="{server}/%ss" % playlist.kind,
|
||||
action_type="POST",
|
||||
parameters=params)
|
||||
_get_playlist_details_from_xml(playlist, xml)
|
||||
get_playlist_details_from_xml(playlist, xml)
|
||||
playlist.items.append(item)
|
||||
log.debug('Initialized the playlist: %s' % playlist)
|
||||
log.debug('Initialized the playlist on the Plex side: %s' % playlist)
|
||||
|
||||
|
||||
def add_playlist_item(playlist, kodi_item, after_pos):
|
||||
def add_listitem_to_playlist(playlist, pos, listitem, kodi_id=None,
|
||||
kodi_type=None, plex_id=None, file=None):
|
||||
"""
|
||||
Adds the new kodi_item to playlist after item at position after_pos
|
||||
[int]
|
||||
Adds a listitem to both the Kodi and Plex playlist at position pos [int].
|
||||
|
||||
If file is not None, file will overrule kodi_id!
|
||||
"""
|
||||
item = playlist_item_from_kodi_item(kodi_item)
|
||||
log.debug('add_listitem_to_playlist. Playlist before add: %s' % playlist)
|
||||
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
|
||||
if playlist.ID is None:
|
||||
init_Plex_playlist(playlist, plex_id, kodi_item)
|
||||
else:
|
||||
add_item_to_PMS_playlist(playlist, pos, plex_id, kodi_item)
|
||||
if kodi_id is None and playlist.items[pos].kodi_id:
|
||||
kodi_id = playlist.items[pos].kodi_id
|
||||
kodi_type = playlist.items[pos].kodi_type
|
||||
if file is None:
|
||||
file = playlist.items[pos].file
|
||||
# Otherwise we double the item!
|
||||
del playlist.items[pos]
|
||||
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
|
||||
add_listitem_to_Kodi_playlist(playlist,
|
||||
pos,
|
||||
listitem,
|
||||
file,
|
||||
kodi_item=kodi_item)
|
||||
|
||||
|
||||
def add_item_to_playlist(playlist, pos, kodi_id=None, kodi_type=None,
|
||||
plex_id=None, file=None):
|
||||
"""
|
||||
Adds an item to BOTH the Kodi and Plex playlist at position pos [int]
|
||||
"""
|
||||
log.debug('add_item_to_playlist. Playlist before adding: %s' % playlist)
|
||||
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
|
||||
if playlist.ID is None:
|
||||
init_Plex_playlist(playlist, plex_id, kodi_item)
|
||||
else:
|
||||
add_item_to_PMS_playlist(playlist, pos, plex_id, kodi_item)
|
||||
kodi_id = playlist.items[pos].kodi_id
|
||||
kodi_type = playlist.items[pos].kodi_type
|
||||
file = playlist.items[pos].file
|
||||
add_item_to_kodi_playlist(playlist, pos, kodi_id, kodi_type, file)
|
||||
|
||||
|
||||
def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None):
|
||||
"""
|
||||
Adds a new item to the playlist at position pos [int] only on the Plex
|
||||
side of things (e.g. because the user changed the Kodi side)
|
||||
|
||||
WILL ALSO UPDATE OUR PLAYLISTS
|
||||
"""
|
||||
log.debug('Adding new item plex_id: %s, kodi_item: %s on the Plex side at '
|
||||
'position %s for %s' % (plex_id, kodi_item, pos, playlist))
|
||||
if plex_id:
|
||||
item = playlist_item_from_plex(plex_id)
|
||||
else:
|
||||
item = playlist_item_from_kodi(kodi_item)
|
||||
url = "{server}/%ss/%s?uri=%s" % (playlist.kind, playlist.ID, item.uri)
|
||||
# Will always put the new item at the end of the playlist
|
||||
# Will always put the new item at the end of the Plex playlist
|
||||
xml = DU().downloadUrl(url, action_type="PUT")
|
||||
try:
|
||||
item.ID = xml.attrib['%sLastAddedItemID' % playlist.kind]
|
||||
item.ID = xml[-1].attrib['%sItemID' % playlist.kind]
|
||||
except (TypeError, AttributeError, KeyError):
|
||||
log.error('Could not add item %s to playlist %s'
|
||||
% (kodi_item, playlist))
|
||||
|
@ -218,21 +319,48 @@ def add_playlist_item(playlist, kodi_item, after_pos):
|
|||
if plex_item.attrib['%sItemID' % playlist.kind] == item.ID:
|
||||
item.guid = plex_item.attrib['guid']
|
||||
playlist.items.append(item)
|
||||
if after_pos == len(playlist.items) - 1:
|
||||
if pos == len(playlist.items) - 1:
|
||||
# Item was added at the end
|
||||
_get_playListVersion_from_xml(playlist, xml)
|
||||
else:
|
||||
# Move the new item to the correct position
|
||||
move_playlist_item(playlist,
|
||||
len(playlist.items) - 1,
|
||||
after_pos)
|
||||
pos)
|
||||
log.debug('Successfully added item on the Plex side: %s' % playlist)
|
||||
|
||||
|
||||
def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
|
||||
file=None):
|
||||
"""
|
||||
Adds an item to the KODI playlist only
|
||||
|
||||
WILL ALSO UPDATE OUR PLAYLISTS
|
||||
"""
|
||||
log.debug('Adding new item kodi_id: %s, kodi_type: %s, file: %s to Kodi '
|
||||
'only at position %s for %s'
|
||||
% (kodi_id, kodi_type, file, pos, playlist))
|
||||
params = {
|
||||
'playlistid': playlist.playlistid,
|
||||
'position': pos
|
||||
}
|
||||
if kodi_id is not None:
|
||||
params['item'] = {'%sid' % kodi_type: int(kodi_id)}
|
||||
else:
|
||||
params['item'] = {'file': file}
|
||||
log.debug(JSONRPC('Playlist.Insert').execute(params))
|
||||
playlist.items.insert(pos, playlist_item_from_kodi(
|
||||
{'id': kodi_id, 'type': kodi_type, 'file': file}))
|
||||
|
||||
|
||||
def move_playlist_item(playlist, before_pos, after_pos):
|
||||
"""
|
||||
Moves playlist item from before_pos [int] to after_pos [int]
|
||||
Moves playlist item from before_pos [int] to after_pos [int] for Plex only.
|
||||
|
||||
WILL ALSO CHANGE OUR PLAYLISTS
|
||||
"""
|
||||
log.debug('Moving item from %s to %s' % (before_pos, after_pos))
|
||||
log.debug('Moving item from %s to %s on the Plex side for %s'
|
||||
% (before_pos, after_pos, playlist))
|
||||
if after_pos == 0:
|
||||
url = "{server}/%ss/%s/items/%s/move?after=0" % \
|
||||
(playlist.kind,
|
||||
|
@ -249,6 +377,7 @@ def move_playlist_item(playlist, before_pos, after_pos):
|
|||
_get_playListVersion_from_xml(playlist, xml)
|
||||
# Move our item's position in our internal playlist
|
||||
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
|
||||
log.debug('Done moving for %s' % playlist)
|
||||
|
||||
|
||||
def get_PMS_playlist(playlist, playlist_id=None):
|
||||
|
@ -280,32 +409,14 @@ def refresh_playlist_from_PMS(playlist):
|
|||
except:
|
||||
log.error('Could not download Plex playlist.')
|
||||
return
|
||||
_get_playlist_details_from_xml(playlist, xml)
|
||||
get_playlist_details_from_xml(playlist, xml)
|
||||
|
||||
|
||||
def update_playlist_from_PMS(playlist, playlist_id=None):
|
||||
def delete_playlist_item_from_PMS(playlist, pos):
|
||||
"""
|
||||
Updates Kodi playlist using a new PMS playlist. Pass in playlist_id if we
|
||||
need to fetch a new playqueue
|
||||
"""
|
||||
xml = get_PMS_playlist(playlist, playlist_id)
|
||||
try:
|
||||
xml.attrib['%sVersion' % playlist.kind]
|
||||
except:
|
||||
log.error('Could not download Plex playlist.')
|
||||
return
|
||||
# Clear our existing playlist and the associated Kodi playlist
|
||||
playlist.clear()
|
||||
# Set new values
|
||||
_get_playlist_details_from_xml(playlist, xml)
|
||||
for plex_item in xml:
|
||||
playlist.items.append(add_to_Kodi_playlist(playlist, plex_item))
|
||||
|
||||
|
||||
def delete_playlist_item(playlist, pos):
|
||||
"""
|
||||
Delete the item at position pos [int]
|
||||
Delete the item at position pos [int] on the Plex side and our playlists
|
||||
"""
|
||||
log.debug('Deleting position %s for %s on the Plex side' % (pos, playlist))
|
||||
xml = DU().downloadUrl("{server}/%ss/%s/items/%s?repeat=%s" %
|
||||
(playlist.kind,
|
||||
playlist.ID,
|
||||
|
@ -313,7 +424,7 @@ def delete_playlist_item(playlist, pos):
|
|||
playlist.repeat),
|
||||
action_type="DELETE")
|
||||
_get_playListVersion_from_xml(playlist, xml)
|
||||
del playlist.items[pos], playlist.old_kodi_pl[pos]
|
||||
del playlist.items[pos]
|
||||
|
||||
|
||||
def get_kodi_playlist_items(playlist):
|
||||
|
@ -357,78 +468,53 @@ def add_to_Kodi_playlist(playlist, xml_video_element):
|
|||
Adds a new item to the Kodi playlist via JSON (at the end of the playlist).
|
||||
Pass in the PMS xml's video element (one level underneath MediaContainer).
|
||||
|
||||
Will return a Playlist_Item
|
||||
Returns a Playlist_Item
|
||||
"""
|
||||
item = Playlist_Item()
|
||||
api = API(xml_video_element)
|
||||
item = playlist_item_from_xml(playlist, xml_video_element)
|
||||
params = {
|
||||
'playlistid': playlist.playlistid
|
||||
}
|
||||
item.plex_id = api.getRatingKey()
|
||||
item.ID = xml_video_element.attrib['%sItemID' % playlist.kind]
|
||||
item.guid = xml_video_element.attrib.get('guid')
|
||||
if item.plex_id:
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
db_element = emby_db.getItem_byId(item.plex_id)
|
||||
try:
|
||||
item.kodi_id, item.kodi_type = int(db_element[0]), db_element[4]
|
||||
except TypeError:
|
||||
pass
|
||||
if item.kodi_id:
|
||||
params['item'] = {'%sid' % item.kodi_type: item.kodi_id}
|
||||
else:
|
||||
item.file = api.getFilePath()
|
||||
params['item'] = {'file': tryEncode(item.file)}
|
||||
log.debug(JSONRPC('Playlist.Add').execute(params))
|
||||
playlist.PKC_playlist_edits.append(
|
||||
item.kodi_id if item.kodi_id else item.file)
|
||||
return item
|
||||
|
||||
|
||||
def add_listitem_to_Kodi_playlist(playlist, listitem, file, index):
|
||||
def add_listitem_to_Kodi_playlist(playlist, pos, listitem, file,
|
||||
xml_video_element=None, kodi_item=None):
|
||||
"""
|
||||
Adds an xbmc listitem to the Kodi playlist. Will be ignored by kodimonitor
|
||||
by settings window('plex_ignore_Playlist.OnAdd')
|
||||
Adds an xbmc listitem to the Kodi playlist.xml_video_element
|
||||
|
||||
WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS
|
||||
"""
|
||||
playlist.kodi_pl.add(file, listitem, index=index)
|
||||
|
||||
|
||||
def add_dbid_to_Kodi_playlist(playlist, dbid=None, mediatype=None, url=None):
|
||||
params = {
|
||||
'playlistid': playlist.playlistid
|
||||
}
|
||||
if dbid is not None:
|
||||
params['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
|
||||
log.debug('Insert listitem at position %s for Kodi only for %s'
|
||||
% (pos, playlist))
|
||||
# Add the item into Kodi playlist
|
||||
playlist.kodi_pl.add(file, listitem, index=pos)
|
||||
# We need to add this to our internal queue as well
|
||||
if xml_video_element is not None:
|
||||
item = playlist_item_from_xml(playlist, xml_video_element)
|
||||
item.file = file
|
||||
else:
|
||||
params['item'] = {'file': url}
|
||||
log.debug(JSONRPC('Playlist.Add').execute(params))
|
||||
item = playlist_item_from_kodi(kodi_item)
|
||||
playlist.items.insert(pos, item)
|
||||
log.debug('Done inserting for %s' % playlist)
|
||||
|
||||
|
||||
def remove_from_Kodi_playlist(playlist, position):
|
||||
def remove_from_Kodi_playlist(playlist, pos):
|
||||
"""
|
||||
Removes the item at position from the Kodi playlist using JSON. Will be
|
||||
ignored by kodimonitor by settings window('plex_ignore_Playlist.OnRemove')
|
||||
Removes the item at position pos from the Kodi playlist using JSON.
|
||||
|
||||
WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS
|
||||
"""
|
||||
log.debug('Removing position %s from playlist %s' % (position, playlist))
|
||||
log.debug('Removing position %s from Kodi only from %s' % (pos, playlist))
|
||||
log.debug(JSONRPC('Playlist.Remove').execute({
|
||||
'playlistid': playlist.playlistid,
|
||||
'position': position
|
||||
'position': pos
|
||||
}))
|
||||
|
||||
|
||||
def insert_into_Kodi_playlist(playlist, position, dbid=None, mediatype=None,
|
||||
url=None):
|
||||
"""
|
||||
"""
|
||||
params = {
|
||||
'playlistid': playlist.playlistid,
|
||||
'position': position
|
||||
}
|
||||
if dbid is not None:
|
||||
params['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
|
||||
else:
|
||||
params['item'] = {'file': url}
|
||||
JSONRPC('Playlist.Insert').execute(params)
|
||||
del playlist.items[pos]
|
||||
|
||||
|
||||
# NOT YET UPDATED!!
|
||||
|
@ -478,7 +564,7 @@ def _addtoPlaylist_xbmc(self, item):
|
|||
'id': API.getRatingKey(),
|
||||
'filename': API.getKey()
|
||||
}
|
||||
playurl = "plugin://plugin.video.plexkodiconnect.movies/?%s" \
|
||||
playurl = "plugin://plugin.video.plexkodiconnect/movies/?%s" \
|
||||
% urlencode(params)
|
||||
|
||||
listitem = API.CreateListItemFromPlexItem()
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
import logging
|
||||
from threading import Lock, Thread
|
||||
from threading import RLock, Thread
|
||||
|
||||
import xbmc
|
||||
from xbmc import sleep, Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO
|
||||
|
||||
from utils import window, ThreadMethods, ThreadMethodsAdditionalSuspend, \
|
||||
Lock_Function
|
||||
from utils import window, ThreadMethods, ThreadMethodsAdditionalSuspend
|
||||
import playlist_func as PL
|
||||
from PlexFunctions import ConvertPlexToKodiTime
|
||||
|
||||
###############################################################################
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
# Lock used to lock methods
|
||||
lock = Lock()
|
||||
lockmethod = Lock_Function(lock)
|
||||
# Lock used for playqueue manipulations
|
||||
lock = RLock()
|
||||
###############################################################################
|
||||
|
||||
|
||||
|
@ -29,33 +27,32 @@ class Playqueue(Thread):
|
|||
__shared_state = {}
|
||||
playqueues = None
|
||||
|
||||
@lockmethod.lockthis
|
||||
def __init__(self, callback=None):
|
||||
self.__dict__ = self.__shared_state
|
||||
Thread.__init__(self)
|
||||
if self.playqueues is not None:
|
||||
return
|
||||
self.mgr = callback
|
||||
self.player = xbmc.Player()
|
||||
|
||||
# Initialize Kodi playqueues
|
||||
self.playqueues = []
|
||||
for queue in PL.get_kodi_playqueues():
|
||||
playqueue = PL.Playqueue_Object()
|
||||
playqueue.playlistid = queue['playlistid']
|
||||
playqueue.type = queue['type']
|
||||
# Initialize each Kodi playlist
|
||||
if playqueue.type == 'audio':
|
||||
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
||||
elif playqueue.type == 'video':
|
||||
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
else:
|
||||
# Currently, only video or audio playqueues available
|
||||
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
self.playqueues.append(playqueue)
|
||||
# sort the list by their playlistid, just in case
|
||||
self.playqueues = sorted(
|
||||
self.playqueues, key=lambda i: i.playlistid)
|
||||
with lock:
|
||||
self.playqueues = []
|
||||
for queue in PL.get_kodi_playqueues():
|
||||
playqueue = PL.Playqueue_Object()
|
||||
playqueue.playlistid = queue['playlistid']
|
||||
playqueue.type = queue['type']
|
||||
# Initialize each Kodi playlist
|
||||
if playqueue.type == 'audio':
|
||||
playqueue.kodi_pl = PlayList(PLAYLIST_MUSIC)
|
||||
elif playqueue.type == 'video':
|
||||
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
|
||||
else:
|
||||
# Currently, only video or audio playqueues available
|
||||
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
|
||||
self.playqueues.append(playqueue)
|
||||
# sort the list by their playlistid, just in case
|
||||
self.playqueues = sorted(
|
||||
self.playqueues, key=lambda i: i.playlistid)
|
||||
log.debug('Initialized the Kodi play queues: %s' % self.playqueues)
|
||||
|
||||
def get_playqueue_from_type(self, typus):
|
||||
|
@ -63,30 +60,14 @@ class Playqueue(Thread):
|
|||
Returns the playqueue according to the typus ('video', 'audio',
|
||||
'picture') passed in
|
||||
"""
|
||||
for playqueue in self.playqueues:
|
||||
if playqueue.type == typus:
|
||||
break
|
||||
else:
|
||||
raise ValueError('Wrong type was passed in: %s' % typus)
|
||||
return playqueue
|
||||
with lock:
|
||||
for playqueue in self.playqueues:
|
||||
if playqueue.type == typus:
|
||||
break
|
||||
else:
|
||||
raise ValueError('Wrong playlist type passed in: %s' % typus)
|
||||
return playqueue
|
||||
|
||||
def get_playqueue_from_playerid(self, kodi_player_id):
|
||||
for playqueue in self.playqueues:
|
||||
if playqueue.playlistid == kodi_player_id:
|
||||
break
|
||||
else:
|
||||
raise ValueError('Wrong kodi_player_id passed was passed in: %s'
|
||||
% kodi_player_id)
|
||||
return playqueue
|
||||
|
||||
def _grab_PMS_playqueue(self, playqueue, playqueue_id=None, repeat=None):
|
||||
"""
|
||||
For initiating out playqueues from the PMS because another PKC Python
|
||||
instance already is setting up the Kodi playlists
|
||||
"""
|
||||
PL.grab_PMS_playqueue(playqueue, playqueue_id)
|
||||
|
||||
@lockmethod.lockthis
|
||||
def update_playqueue_from_PMS(self,
|
||||
playqueue,
|
||||
playqueue_id=None,
|
||||
|
@ -97,169 +78,104 @@ class Playqueue(Thread):
|
|||
in playqueue_id if we need to fetch a new playqueue
|
||||
|
||||
repeat = 0, 1, 2
|
||||
offset = time offset in Plextime
|
||||
offset = time offset in Plextime (milliseconds)
|
||||
"""
|
||||
log.info('New playqueue received from the PMS, updating!')
|
||||
PL.update_playlist_from_PMS(playqueue, playqueue_id)
|
||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||
log.debug('Updated playqueue: %s' % playqueue)
|
||||
window('plex_customplaylist', value="true")
|
||||
if offset not in (None, "0"):
|
||||
window('plex_customplaylist.seektime',
|
||||
str(ConvertPlexToKodiTime(offset)))
|
||||
for startpos, item in enumerate(playqueue.items):
|
||||
if item.ID == playqueue.selectedItemID:
|
||||
break
|
||||
else:
|
||||
startpos = None
|
||||
# Start playback
|
||||
if startpos:
|
||||
self.player.play(playqueue.kodi_pl, startpos=startpos)
|
||||
else:
|
||||
self.player.play(playqueue.kodi_pl)
|
||||
playqueue.log_Kodi_playlist()
|
||||
log.info('New playqueue %s received from Plex companion with offset '
|
||||
'%s, repeat %s' % (playqueue_id, offset, repeat))
|
||||
with lock:
|
||||
if playqueue_id != playqueue.ID:
|
||||
log.debug('Need to fetch new playQueue from the PMS')
|
||||
PL.update_playlist_from_PMS(playqueue, playqueue_id)
|
||||
else:
|
||||
log.debug('Restarting existing playQueue')
|
||||
PL.refresh_playlist_from_PMS(playqueue)
|
||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||
window('plex_customplaylist', value="true")
|
||||
if offset not in (None, "0"):
|
||||
window('plex_customplaylist.seektime',
|
||||
str(ConvertPlexToKodiTime(offset)))
|
||||
for startpos, item in enumerate(playqueue.items):
|
||||
if item.ID == playqueue.selectedItemID:
|
||||
break
|
||||
else:
|
||||
startpos = None
|
||||
# Start playback. Player does not return in time
|
||||
if startpos:
|
||||
thread = Thread(target=Player().play,
|
||||
args=(playqueue.kodi_pl,
|
||||
None,
|
||||
False,
|
||||
startpos))
|
||||
else:
|
||||
thread = Thread(target=Player().play,
|
||||
args=(playqueue.kodi_pl,))
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
@lockmethod.lockthis
|
||||
def start_playqueue_initiated_by_companion(self,
|
||||
playqueue,
|
||||
playqueue_id=None,
|
||||
repeat=None,
|
||||
offset=None):
|
||||
log.info('Plex companion wants to restart playback of playqueue %s'
|
||||
% playqueue)
|
||||
# Still need to get new playQueue from the server - don't know what has
|
||||
# been selected
|
||||
PL.refresh_playlist_from_PMS(playqueue)
|
||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||
window('plex_customplaylist', value="true")
|
||||
if offset not in (None, "0"):
|
||||
window('plex_customplaylist.seektime',
|
||||
str(ConvertPlexToKodiTime(offset)))
|
||||
for startpos, item in enumerate(playqueue.items):
|
||||
if item.ID == playqueue.selectedItemID:
|
||||
break
|
||||
else:
|
||||
startpos = None
|
||||
# Start playback
|
||||
if startpos:
|
||||
self.player.play(playqueue.kodi_pl, startpos=startpos)
|
||||
else:
|
||||
self.player.play(playqueue.kodi_pl)
|
||||
playqueue.log_Kodi_playlist()
|
||||
|
||||
@lockmethod.lockthis
|
||||
def kodi_onadd(self, data):
|
||||
"""
|
||||
Called if an item is added to a Kodi playqueue. Data is Kodi JSON-RPC
|
||||
output, e.g.
|
||||
{
|
||||
u'item': {u'type': u'movie', u'id': 3},
|
||||
u'playlistid': 1,
|
||||
u'position': 0
|
||||
}
|
||||
"""
|
||||
playqueue = self.playqueues[data['playlistid']]
|
||||
if window('plex_playbackProps') == 'true':
|
||||
log.debug('kodi_onadd called during PKC playback setup')
|
||||
if window('plex_playQueueID'):
|
||||
self._grab_PMS_playqueue(playqueue, window('plex_playQueueID'))
|
||||
window('plex_playQueueID', clear=True)
|
||||
log.debug('Done setting up playQueue')
|
||||
return
|
||||
|
||||
if playqueue.PKC_playlist_edits:
|
||||
old = (data['item'].get('id') if data['item'].get('id')
|
||||
else data['item'].get('file'))
|
||||
for i, item in enumerate(playqueue.PKC_playlist_edits):
|
||||
if old == item:
|
||||
log.debug('kodimonitor told us of a PKC edit - ignore')
|
||||
del playqueue.PKC_playlist_edits[i]
|
||||
return
|
||||
if playqueue.ID is None:
|
||||
# Need to initialize the queue for the first time
|
||||
PL.init_Plex_playlist(playqueue, kodi_item=data['item'])
|
||||
else:
|
||||
PL.add_playlist_item(playqueue, data['item'], data['position'])
|
||||
log.debug('Added a new item to the playqueue: %s' % playqueue)
|
||||
|
||||
@lockmethod.lockthis
|
||||
def kodi_onremove(self, data):
|
||||
"""
|
||||
Called if an item is removed from a Kodi playqueue. Data is Kodi JSON-
|
||||
RPC output, e.g.
|
||||
{u'position': 2, u'playlistid': 1}
|
||||
"""
|
||||
if window('plex_playbackProps') == 'true':
|
||||
log.debug('kodi_onremove called during PKC playback setup')
|
||||
return
|
||||
playqueue = self.playqueues[data['playlistid']]
|
||||
PL.delete_playlist_item(playqueue, data['position'])
|
||||
log.debug('Deleted item at position %s. New playqueue: %s'
|
||||
% (data['position'], playqueue))
|
||||
|
||||
@lockmethod.lockthis
|
||||
def _compare_playqueues(self, playqueue, new):
|
||||
"""
|
||||
Used to poll the Kodi playqueue and update the Plex playqueue if needed
|
||||
"""
|
||||
if self.threadStopped():
|
||||
# Chances are that we got an empty Kodi playlist due to Kodi exit
|
||||
return
|
||||
old = playqueue.old_kodi_pl
|
||||
old = list(playqueue.items)
|
||||
index = list(range(0, len(old)))
|
||||
log.debug('Comparing new Kodi playqueue %s with our play queue %s'
|
||||
% (new, playqueue))
|
||||
% (new, old))
|
||||
for i, new_item in enumerate(new):
|
||||
for j, old_item in enumerate(old):
|
||||
if old_item.get('id') is None:
|
||||
identical = old_item['file'] == new_item['file']
|
||||
if self.threadStopped():
|
||||
# Chances are that we got an empty Kodi playlist due to
|
||||
# Kodi exit
|
||||
return
|
||||
if new_item.get('id') is None:
|
||||
identical = old_item.file == new_item['file']
|
||||
else:
|
||||
identical = (old_item['id'] == new_item['id'] and
|
||||
old_item['type'] == new_item['type'])
|
||||
identical = (old_item.kodi_id == new_item['id'] and
|
||||
old_item.kodi_type == new_item['type'])
|
||||
if j == 0 and identical:
|
||||
del old[j], index[j]
|
||||
break
|
||||
elif identical:
|
||||
# item now at pos i has been moved from original pos i+j
|
||||
log.debug('Detected playqueue item %s moved to position %s'
|
||||
% (i+j, i))
|
||||
PL.move_playlist_item(playqueue, i + j, i)
|
||||
# Delete the item we just found
|
||||
del old[j], index[j]
|
||||
break
|
||||
# New elements and left-over elements will be taken care of by the kodi
|
||||
# monitor!
|
||||
log.debug('New playqueue: %s' % playqueue)
|
||||
|
||||
def init_playlists(self):
|
||||
"""
|
||||
Initializes the playqueues with already existing items.
|
||||
Called on startup AND for addon paths!
|
||||
"""
|
||||
for playqueue in self.playqueues:
|
||||
for i, item in enumerate(PL.get_kodi_playlist_items(playqueue)):
|
||||
if i == 0:
|
||||
PL.init_Plex_playlist(playqueue, kodi_item=item)
|
||||
else:
|
||||
log.debug('Detected new Kodi element: %s' % new_item)
|
||||
if playqueue.ID is None:
|
||||
PL.init_Plex_playlist(playqueue,
|
||||
kodi_item=new_item)
|
||||
else:
|
||||
PL.add_playlist_item(playqueue, item, i)
|
||||
PL.add_item_to_PMS_playlist(playqueue,
|
||||
i,
|
||||
kodi_item=new_item)
|
||||
for i in reversed(index):
|
||||
log.debug('Detected deletion of playqueue element at pos %s' % i)
|
||||
PL.delete_playlist_item_from_PMS(playqueue, i)
|
||||
log.debug('Done comparing playqueues')
|
||||
|
||||
def run(self):
|
||||
threadStopped = self.threadStopped
|
||||
threadSuspended = self.threadSuspended
|
||||
log.info("----===## Starting PlayQueue client ##===----")
|
||||
# Initialize the playqueues, if Kodi already got items in them
|
||||
self.init_playlists()
|
||||
for playqueue in self.playqueues:
|
||||
for i, item in enumerate(PL.get_kodi_playlist_items(playqueue)):
|
||||
if i == 0:
|
||||
PL.init_Plex_playlist(playqueue, kodi_item=item)
|
||||
else:
|
||||
PL.add_item_to_PMS_playlist(playqueue, i, kodi_item=item)
|
||||
while not threadStopped():
|
||||
while threadSuspended():
|
||||
if threadStopped():
|
||||
break
|
||||
xbmc.sleep(1000)
|
||||
for playqueue in self.playqueues:
|
||||
if not playqueue.items:
|
||||
# Skip empty playqueues as items can't be modified
|
||||
continue
|
||||
kodi_playqueue = PL.get_kodi_playlist_items(playqueue)
|
||||
if playqueue.old_kodi_pl != kodi_playqueue:
|
||||
# compare old and new playqueue
|
||||
self._compare_playqueues(playqueue, kodi_playqueue)
|
||||
playqueue.old_kodi_pl = list(kodi_playqueue)
|
||||
xbmc.sleep(1000)
|
||||
sleep(1000)
|
||||
with lock:
|
||||
for playqueue in self.playqueues:
|
||||
kodi_playqueue = PL.get_kodi_playlist_items(playqueue)
|
||||
if playqueue.old_kodi_pl != kodi_playqueue:
|
||||
# compare old and new playqueue
|
||||
self._compare_playqueues(playqueue, kodi_playqueue)
|
||||
playqueue.old_kodi_pl = list(kodi_playqueue)
|
||||
sleep(50)
|
||||
log.info("----===## PlayQueue client stopped ##===----")
|
||||
|
|
|
@ -56,6 +56,24 @@ def window(property, value=None, clear=False, windowid=10000):
|
|||
return tryDecode(win.getProperty(property))
|
||||
|
||||
|
||||
def pickl_window(property, value=None, clear=False, windowid=10000):
|
||||
"""
|
||||
Get or set window property - thread safe! For use with Pickle
|
||||
Property and value must be string
|
||||
"""
|
||||
if windowid != 10000:
|
||||
win = xbmcgui.Window(windowid)
|
||||
else:
|
||||
win = WINDOW
|
||||
|
||||
if clear:
|
||||
win.clearProperty(property)
|
||||
elif value is not None:
|
||||
win.setProperty(property, value)
|
||||
else:
|
||||
return win.getProperty(property)
|
||||
|
||||
|
||||
def settings(setting, value=None):
|
||||
"""
|
||||
Get or add addon setting. Returns unicode
|
||||
|
|
14
service.py
14
service.py
|
@ -44,6 +44,8 @@ from playqueue import Playqueue
|
|||
|
||||
import PlexAPI
|
||||
from PlexCompanion import PlexCompanion
|
||||
from monitor_kodi_play import Monitor_Kodi_Play
|
||||
from playback_starter import Playback_Starter
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -73,6 +75,7 @@ class Service():
|
|||
plexCompanion_running = False
|
||||
playqueue_running = False
|
||||
kodimonitor_running = False
|
||||
playback_starter_running = False
|
||||
|
||||
def __init__(self):
|
||||
|
||||
|
@ -109,7 +112,8 @@ class Service():
|
|||
"plex_authenticated", "PlexUserImage", "useDirectPaths",
|
||||
"suspend_LibraryThread", "plex_terminateNow",
|
||||
"kodiplextimeoffset", "countError", "countUnauthorized",
|
||||
"plex_restricteduser", "plex_allows_mediaDeletion"
|
||||
"plex_restricteduser", "plex_allows_mediaDeletion",
|
||||
"plex_play_new_item", "plex_result"
|
||||
]
|
||||
for prop in properties:
|
||||
window(prop, clear=True)
|
||||
|
@ -133,6 +137,10 @@ class Service():
|
|||
monitor = self.monitor
|
||||
kodiProfile = xbmc.translatePath("special://profile")
|
||||
|
||||
# Detect playback start early on
|
||||
self.monitor_kodi_play = Monitor_Kodi_Play(self)
|
||||
self.monitor_kodi_play.start()
|
||||
|
||||
# Server auto-detect
|
||||
initialsetup.InitialSetup().setup()
|
||||
|
||||
|
@ -142,6 +150,7 @@ class Service():
|
|||
self.library = LibrarySync(self)
|
||||
self.plexCompanion = PlexCompanion(self)
|
||||
self.playqueue = Playqueue(self)
|
||||
self.playback_starter = Playback_Starter(self)
|
||||
|
||||
plx = PlexAPI.PlexAPI()
|
||||
|
||||
|
@ -197,6 +206,9 @@ class Service():
|
|||
if not self.plexCompanion_running:
|
||||
self.plexCompanion_running = True
|
||||
self.plexCompanion.start()
|
||||
if not self.playback_starter_running:
|
||||
self.playback_starter_running = True
|
||||
self.playback_starter.start()
|
||||
else:
|
||||
if (self.user.currUser is None) and self.warn_auth:
|
||||
# Alert user is not authenticated and suppress future
|
||||
|
|
Loading…
Reference in a new issue