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>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.1.0"/>
|
<import addon="xbmc.python" version="2.1.0"/>
|
||||||
<import addon="script.module.requests" version="2.3.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>
|
</requires>
|
||||||
<extension point="xbmc.python.pluginsource"
|
<extension point="xbmc.python.pluginsource"
|
||||||
library="default.py">
|
library="default.py">
|
||||||
|
|
93
default.py
93
default.py
|
@ -3,36 +3,38 @@
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
from os import path as os_path
|
||||||
import sys
|
from sys import path as sys_path, argv
|
||||||
import urlparse
|
from urlparse import parse_qsl
|
||||||
|
|
||||||
import xbmc
|
from xbmc import translatePath, sleep, executebuiltin
|
||||||
import xbmcaddon
|
from xbmcaddon import Addon
|
||||||
import xbmcgui
|
from xbmcgui import ListItem, Dialog
|
||||||
|
from xbmcplugin import setResolvedUrl
|
||||||
|
|
||||||
|
_addon = Addon(id='plugin.video.plexkodiconnect')
|
||||||
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
|
||||||
try:
|
try:
|
||||||
_addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
_addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
||||||
except TypeError:
|
except TypeError:
|
||||||
_addon_path = _addon.getAddonInfo('path').decode()
|
_addon_path = _addon.getAddonInfo('path').decode()
|
||||||
try:
|
try:
|
||||||
_base_resource = xbmc.translatePath(os.path.join(
|
_base_resource = translatePath(os_path.join(
|
||||||
_addon_path,
|
_addon_path,
|
||||||
'resources',
|
'resources',
|
||||||
'lib')).decode('utf-8')
|
'lib')).decode('utf-8')
|
||||||
except TypeError:
|
except TypeError:
|
||||||
_base_resource = xbmc.translatePath(os.path.join(
|
_base_resource = translatePath(os_path.join(
|
||||||
_addon_path,
|
_addon_path,
|
||||||
'resources',
|
'resources',
|
||||||
'lib')).decode()
|
'lib')).decode()
|
||||||
sys.path.append(_base_resource)
|
sys_path.append(_base_resource)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import entrypoint
|
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():
|
class Main():
|
||||||
|
|
||||||
# MAIN ENTRY POINT
|
# MAIN ENTRY POINT
|
||||||
# @utils.profiling()
|
# @utils.profiling()
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
log.debug("Full sys.argv received: %s" % ARGV)
|
||||||
# Parse parameters
|
# Parse parameters
|
||||||
log.warn("Full sys.argv received: %s" % sys.argv)
|
params = dict(parse_qsl(ARGV[2][1:]))
|
||||||
base_url = sys.argv[0]
|
|
||||||
params = urlparse.parse_qs(sys.argv[2][1:])
|
|
||||||
try:
|
try:
|
||||||
mode = params['mode'][0]
|
mode = params['mode']
|
||||||
itemid = params.get('id', '')
|
itemid = params.get('id', '')
|
||||||
if itemid:
|
|
||||||
try:
|
|
||||||
itemid = itemid[0]
|
|
||||||
except:
|
except:
|
||||||
pass
|
|
||||||
except:
|
|
||||||
params = {}
|
|
||||||
mode = ""
|
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 = {
|
modes = {
|
||||||
|
'reset': reset,
|
||||||
'reset': utils.reset,
|
|
||||||
'resetauth': entrypoint.resetAuth,
|
'resetauth': entrypoint.resetAuth,
|
||||||
'play': entrypoint.doPlayback,
|
'passwords': passwordsXML,
|
||||||
'passwords': utils.passwordsXML,
|
|
||||||
'channels': entrypoint.BrowseChannels,
|
'channels': entrypoint.BrowseChannels,
|
||||||
'channelsfolder': entrypoint.BrowseChannels,
|
'channelsfolder': entrypoint.BrowseChannels,
|
||||||
'browsecontent': entrypoint.BrowseContent,
|
'browsecontent': entrypoint.BrowseContent,
|
||||||
|
@ -79,7 +94,6 @@ class Main():
|
||||||
'inprogressepisodes': entrypoint.getInProgressEpisodes,
|
'inprogressepisodes': entrypoint.getInProgressEpisodes,
|
||||||
'recentepisodes': entrypoint.getRecentEpisodes,
|
'recentepisodes': entrypoint.getRecentEpisodes,
|
||||||
'refreshplaylist': entrypoint.refreshPlaylist,
|
'refreshplaylist': entrypoint.refreshPlaylist,
|
||||||
'companion': entrypoint.plexCompanion,
|
|
||||||
'switchuser': entrypoint.switchPlexUser,
|
'switchuser': entrypoint.switchPlexUser,
|
||||||
'deviceid': entrypoint.resetDeviceId,
|
'deviceid': entrypoint.resetDeviceId,
|
||||||
'delete': entrypoint.deleteItem,
|
'delete': entrypoint.deleteItem,
|
||||||
|
@ -92,8 +106,8 @@ class Main():
|
||||||
'playwatchlater': entrypoint.playWatchLater
|
'playwatchlater': entrypoint.playWatchLater
|
||||||
}
|
}
|
||||||
|
|
||||||
if "/extrafanart" in sys.argv[0]:
|
if "/extrafanart" in ARGV[0]:
|
||||||
plexpath = sys.argv[2][1:]
|
plexpath = ARGV[2][1:]
|
||||||
plexid = params.get('id', [""])[0]
|
plexid = params.get('id', [""])[0]
|
||||||
entrypoint.getExtraFanArt(plexid, plexpath)
|
entrypoint.getExtraFanArt(plexid, plexpath)
|
||||||
entrypoint.getVideoFiles(plexid, plexpath)
|
entrypoint.getVideoFiles(plexid, plexpath)
|
||||||
|
@ -101,11 +115,11 @@ class Main():
|
||||||
|
|
||||||
if mode == 'fanart':
|
if mode == 'fanart':
|
||||||
log.info('User requested fanarttv refresh')
|
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
|
# Called by e.g. 3rd party plugin video extras
|
||||||
if ("/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0] or
|
if ("/Extras" in ARGV[0] or "/VideoFiles" in ARGV[0] or
|
||||||
"/Extras" in sys.argv[2]):
|
"/Extras" in ARGV[2]):
|
||||||
plexId = params.get('id', [None])[0]
|
plexId = params.get('id', [None])[0]
|
||||||
entrypoint.getVideoFiles(plexId, params)
|
entrypoint.getVideoFiles(plexId, params)
|
||||||
|
|
||||||
|
@ -143,7 +157,7 @@ class Main():
|
||||||
folderid = params['folderid'][0]
|
folderid = params['folderid'][0]
|
||||||
modes[mode](itemid, folderid)
|
modes[mode](itemid, folderid)
|
||||||
elif mode == "companion":
|
elif mode == "companion":
|
||||||
modes[mode](itemid, params=sys.argv[2])
|
modes[mode](itemid, params=ARGV[2])
|
||||||
elif mode == 'playwatchlater':
|
elif mode == 'playwatchlater':
|
||||||
modes[mode](params.get('id')[0], params.get('viewOffset')[0])
|
modes[mode](params.get('id')[0], params.get('viewOffset')[0])
|
||||||
else:
|
else:
|
||||||
|
@ -151,25 +165,24 @@ class Main():
|
||||||
else:
|
else:
|
||||||
# Other functions
|
# Other functions
|
||||||
if mode == "settings":
|
if mode == "settings":
|
||||||
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||||
elif mode in ("manualsync", "repair"):
|
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
|
# Server is not online, do not run the sync
|
||||||
xbmcgui.Dialog().ok(
|
Dialog().ok(
|
||||||
"PlexKodiConnect",
|
"PlexKodiConnect",
|
||||||
"Unable to run the sync, the add-on is not connected "
|
"Unable to run the sync, the add-on is not connected "
|
||||||
"to a Plex server.")
|
"to a Plex server.")
|
||||||
log.error("Not connected to a PMS.")
|
log.error("Not connected to a PMS.")
|
||||||
else:
|
else:
|
||||||
if mode == 'repair':
|
if mode == 'repair':
|
||||||
utils.window('plex_runLibScan', value="repair")
|
window('plex_runLibScan', value="repair")
|
||||||
log.info("Requesting repair lib sync")
|
log.info("Requesting repair lib sync")
|
||||||
elif mode == 'manualsync':
|
elif mode == 'manualsync':
|
||||||
log.info("Requesting full library scan")
|
log.info("Requesting full library scan")
|
||||||
utils.window('plex_runLibScan', value="full")
|
window('plex_runLibScan', value="full")
|
||||||
|
|
||||||
elif mode == "texturecache":
|
elif mode == "texturecache":
|
||||||
utils.window('plex_runLibScan', value='del_textures')
|
window('plex_runLibScan', value='del_textures')
|
||||||
else:
|
else:
|
||||||
entrypoint.doMainListing()
|
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('isPlayable', 'true')
|
||||||
# listItem.setProperty('isFolder', 'true')
|
# listItem.setProperty('isFolder', 'true')
|
||||||
# Further stuff
|
# Further stuff
|
||||||
listItem.setIconImage('DefaultPicture.png')
|
listItem.setArt({'icon': 'DefaultPicture.png'})
|
||||||
return listItem
|
return listItem
|
||||||
|
|
||||||
def _createVideoListItem(self,
|
def _createVideoListItem(self,
|
||||||
|
@ -2456,14 +2456,14 @@ class API():
|
||||||
"s%.2de%.2d" % (season, episode))
|
"s%.2de%.2d" % (season, episode))
|
||||||
if appendSxxExx is True:
|
if appendSxxExx is True:
|
||||||
title = "S%.2dE%.2d - %s" % (season, episode, title)
|
title = "S%.2dE%.2d - %s" % (season, episode, title)
|
||||||
listItem.setIconImage('DefaultTVShows.png')
|
listItem.setArt({'icon': 'DefaultTVShows.png'})
|
||||||
if appendShowTitle is True:
|
if appendShowTitle is True:
|
||||||
title = "%s - %s " % (show, title)
|
title = "%s - %s " % (show, title)
|
||||||
elif typus == "movie":
|
elif typus == "movie":
|
||||||
listItem.setIconImage('DefaultMovies.png')
|
listItem.setArt({'icon': 'DefaultMovies.png'})
|
||||||
else:
|
else:
|
||||||
# E.g. clips, trailers, ...
|
# E.g. clips, trailers, ...
|
||||||
listItem.setIconImage('DefaultVideo.png')
|
listItem.setArt({'icon': 'DefaultVideo.png'})
|
||||||
|
|
||||||
plexId = self.getRatingKey()
|
plexId = self.getRatingKey()
|
||||||
listItem.setProperty('plexid', plexId)
|
listItem.setProperty('plexid', plexId)
|
||||||
|
|
|
@ -85,19 +85,11 @@ class PlexCompanion(Thread):
|
||||||
return
|
return
|
||||||
playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
||||||
data['type'])
|
data['type'])
|
||||||
if ID != playqueue.ID:
|
|
||||||
# playqueue changed somehow
|
|
||||||
self.mgr.playqueue.update_playqueue_from_PMS(
|
self.mgr.playqueue.update_playqueue_from_PMS(
|
||||||
playqueue,
|
playqueue,
|
||||||
ID,
|
ID,
|
||||||
query.get('repeat'),
|
repeat=query.get('repeat'),
|
||||||
data.get('offset'))
|
offset=data.get('offset'))
|
||||||
else:
|
|
||||||
# No change to the playqueue
|
|
||||||
self.mgr.playqueue.start_playqueue_initiated_by_companion(
|
|
||||||
playqueue,
|
|
||||||
query.get('repeat'),
|
|
||||||
data.get('offset'))
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
httpd = False
|
httpd = False
|
||||||
|
|
|
@ -212,6 +212,6 @@ class ContextMenu(object):
|
||||||
'mode': "play"
|
'mode': "play"
|
||||||
}
|
}
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
handle = ("plugin://plugin.video.plexkodiconnect.movies?%s"
|
handle = ("plugin://plugin.video.plexkodiconnect/movies?%s"
|
||||||
% urlencode(params))
|
% urlencode(params))
|
||||||
xbmc.executebuiltin('RunPlugin(%s)' % handle)
|
xbmc.executebuiltin('RunPlugin(%s)' % handle)
|
||||||
|
|
|
@ -67,7 +67,7 @@ class UsersConnect(xbmcgui.WindowXMLDialog):
|
||||||
if self.kodi_version > 15:
|
if self.kodi_version > 15:
|
||||||
item.setArt({'Icon': user_image})
|
item.setArt({'Icon': user_image})
|
||||||
else:
|
else:
|
||||||
item.setIconImage(user_image)
|
item.setArt({'icon': user_image})
|
||||||
|
|
||||||
return item
|
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():
|
def chooseServer():
|
||||||
"""
|
"""
|
||||||
Lets user choose from list of PMS
|
Lets user choose from list of PMS
|
||||||
|
@ -180,40 +146,6 @@ def playWatchLater(itemid, viewOffset):
|
||||||
return pbutils.PlaybackUtils(xml).play(None, 'plexnode')
|
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 #####
|
##### DO RESET AUTH #####
|
||||||
def resetAuth():
|
def resetAuth():
|
||||||
# User tried login and failed too many times
|
# 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.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.setThumbnailImage(img_path)
|
||||||
li.setProperty("plot",API.getOverview())
|
li.setProperty("plot",API.getOverview())
|
||||||
li.setIconImage('DefaultPicture.png')
|
li.setArt({'icon': 'DefaultPicture.png'})
|
||||||
else:
|
else:
|
||||||
#normal video items
|
#normal video items
|
||||||
li.setProperty('IsPlayable', 'true')
|
li.setProperty('IsPlayable', 'true')
|
||||||
|
@ -541,7 +473,7 @@ def createListItemFromEmbyItem(item,art=None,doUtils=downloadutils.DownloadUtils
|
||||||
if allart.get('Primary'):
|
if allart.get('Primary'):
|
||||||
li.setThumbnailImage(allart.get('Primary'))
|
li.setThumbnailImage(allart.get('Primary'))
|
||||||
else: li.setThumbnailImage('DefaultTVShows.png')
|
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
|
if not allart.get('Background'): #add image as fanart for use with skinhelper auto thumb/backgrund creation
|
||||||
li.setArt( {"fanart": allart.get('Primary') } )
|
li.setArt( {"fanart": allart.get('Primary') } )
|
||||||
else:
|
else:
|
||||||
|
@ -663,7 +595,7 @@ def createListItem(item, appendShowTitle=False, appendSxxExx=False):
|
||||||
li.setProperty('totaltime', str(item['resume']['total']))
|
li.setProperty('totaltime', str(item['resume']['total']))
|
||||||
li.setArt(item['art'])
|
li.setArt(item['art'])
|
||||||
li.setThumbnailImage(item['art'].get('thumb',''))
|
li.setThumbnailImage(item['art'].get('thumb',''))
|
||||||
li.setIconImage('DefaultTVShows.png')
|
li.setArt({'icon': 'DefaultTVShows.png'})
|
||||||
li.setProperty('dbid', str(item['episodeid']))
|
li.setProperty('dbid', str(item['episodeid']))
|
||||||
li.setProperty('fanart_image', item['art'].get('tvshow.fanart',''))
|
li.setProperty('fanart_image', item['art'].get('tvshow.fanart',''))
|
||||||
for key, value in item['streamdetails'].iteritems():
|
for key, value in item['streamdetails'].iteritems():
|
||||||
|
@ -1167,7 +1099,7 @@ def getOnDeck(viewid, mediatype, tagname, limit):
|
||||||
'id': API.getRatingKey(),
|
'id': API.getRatingKey(),
|
||||||
'dbid': listitem.getProperty('dbid')
|
'dbid': listitem.getProperty('dbid')
|
||||||
}
|
}
|
||||||
url = "plugin://plugin.video.plexkodiconnect.tvshows/?%s" \
|
url = "plugin://plugin.video.plexkodiconnect/tvshows/?%s" \
|
||||||
% urllib.urlencode(params)
|
% urllib.urlencode(params)
|
||||||
xbmcplugin.addDirectoryItem(
|
xbmcplugin.addDirectoryItem(
|
||||||
handle=int(sys.argv[1]),
|
handle=int(sys.argv[1]),
|
||||||
|
|
|
@ -407,7 +407,7 @@ class Movies(Items):
|
||||||
path = playurl.replace(filename, "")
|
path = playurl.replace(filename, "")
|
||||||
if doIndirect:
|
if doIndirect:
|
||||||
# Set plugin path and media flags using real filename
|
# Set plugin path and media flags using real filename
|
||||||
path = "plugin://plugin.video.plexkodiconnect.movies/"
|
path = "plugin://plugin.video.plexkodiconnect/movies/"
|
||||||
params = {
|
params = {
|
||||||
'filename': API.getKey(),
|
'filename': API.getKey(),
|
||||||
'id': itemid,
|
'id': itemid,
|
||||||
|
@ -675,7 +675,7 @@ class TVShows(Items):
|
||||||
toplevelpath = "%s/" % dirname(dirname(path))
|
toplevelpath = "%s/" % dirname(dirname(path))
|
||||||
if doIndirect:
|
if doIndirect:
|
||||||
# Set plugin path
|
# Set plugin path
|
||||||
toplevelpath = "plugin://plugin.video.plexkodiconnect.tvshows/"
|
toplevelpath = "plugin://plugin.video.plexkodiconnect/tvshows/"
|
||||||
path = "%s%s/" % (toplevelpath, itemid)
|
path = "%s%s/" % (toplevelpath, itemid)
|
||||||
|
|
||||||
# Add top path
|
# Add top path
|
||||||
|
@ -956,7 +956,7 @@ class TVShows(Items):
|
||||||
filename = playurl.rsplit('/', 1)[1]
|
filename = playurl.rsplit('/', 1)[1]
|
||||||
else:
|
else:
|
||||||
filename = 'file_not_found.mkv'
|
filename = 'file_not_found.mkv'
|
||||||
path = "plugin://plugin.video.plexkodiconnect.tvshows/%s/" % seriesId
|
path = "plugin://plugin.video.plexkodiconnect/tvshows/%s/" % seriesId
|
||||||
params = {
|
params = {
|
||||||
'filename': tryEncode(filename),
|
'filename': tryEncode(filename),
|
||||||
'id': itemid,
|
'id': itemid,
|
||||||
|
@ -966,7 +966,7 @@ class TVShows(Items):
|
||||||
filename = "%s?%s" % (path, tryDecode(urlencode(params)))
|
filename = "%s?%s" % (path, tryDecode(urlencode(params)))
|
||||||
playurl = filename
|
playurl = filename
|
||||||
parentPathId = self.kodi_db.addPath(
|
parentPathId = self.kodi_db.addPath(
|
||||||
'plugin://plugin.video.plexkodiconnect.tvshows/')
|
'plugin://plugin.video.plexkodiconnect/tvshows/')
|
||||||
|
|
||||||
# episodes table:
|
# episodes table:
|
||||||
# c18 - playurl
|
# c18 - playurl
|
||||||
|
@ -1093,7 +1093,7 @@ class TVShows(Items):
|
||||||
self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
||||||
if not self.directpath and resume:
|
if not self.directpath and resume:
|
||||||
# Create additional entry for widgets. This is only required for plugin/episode.
|
# 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)
|
tempfileid = self.kodi_db.addFile(filename, temppathid)
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ class Kodidb_Functions():
|
||||||
self.cursor.execute(
|
self.cursor.execute(
|
||||||
query, ('movies',
|
query, ('movies',
|
||||||
'metadata.local',
|
'metadata.local',
|
||||||
'plugin://plugin.video.plexkodiconnect.movies%%'))
|
'plugin://plugin.video.plexkodiconnect/movies%%'))
|
||||||
|
|
||||||
def getParentPathId(self, path):
|
def getParentPathId(self, path):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -164,20 +164,6 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
xbmc.sleep(5000)
|
xbmc.sleep(5000)
|
||||||
window('plex_runLibScan', value="full")
|
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):
|
def PlayBackStart(self, data):
|
||||||
"""
|
"""
|
||||||
Called whenever a playback is started
|
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 logging
|
||||||
import sys
|
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
import xbmc
|
from xbmc import getCondVisibility, Player
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
import xbmcplugin
|
|
||||||
|
|
||||||
import playutils as putils
|
import playutils as putils
|
||||||
from playqueue import Playqueue
|
|
||||||
from utils import window, settings, tryEncode, tryDecode
|
from utils import window, settings, tryEncode, tryDecode
|
||||||
import downloadutils
|
import downloadutils
|
||||||
|
|
||||||
import PlexAPI
|
from PlexAPI import API
|
||||||
import PlexFunctions as PF
|
from PlexFunctions import GetPlexPlaylist, KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE, \
|
||||||
import playlist_func as PL
|
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():
|
class PlaybackUtils():
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item, callback):
|
||||||
|
self.mgr = callback
|
||||||
self.item = item
|
self.item = item
|
||||||
self.API = PlexAPI.API(item)
|
self.api = API(item)
|
||||||
|
self.playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
||||||
self.userid = window('currUserId')
|
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[self.api.getType()])
|
||||||
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):
|
|
||||||
|
|
||||||
|
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
|
item = self.item
|
||||||
# Hack to get only existing entry in PMS response for THIS instance of
|
api = self.api
|
||||||
# playbackutils :-)
|
playqueue = self.playqueue
|
||||||
self.API = PlexAPI.API(item[0])
|
xml = None
|
||||||
API = self.API
|
result = Playback_Successful()
|
||||||
listitem = xbmcgui.ListItem()
|
listitem = ListItem()
|
||||||
playutils = putils.PlayUtils(item[0])
|
playutils = putils.PlayUtils(item)
|
||||||
|
|
||||||
log.info("Play called.")
|
|
||||||
log.debug('Playqueue: %s' % self.pl)
|
|
||||||
playurl = playutils.getPlayUrl()
|
playurl = playutils.getPlayUrl()
|
||||||
if not playurl:
|
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'):
|
if kodi_id in (None, 'plextrailer', 'plexnode'):
|
||||||
# Item is not in Kodi database, is a trailer or plex redirect
|
# Item is not in Kodi database, is a trailer/clip or plex redirect
|
||||||
# e.g. plex.tv watch later
|
# e.g. plex.tv watch later
|
||||||
API.CreateListItemFromPlexItem(listitem)
|
api.CreateListItemFromPlexItem(listitem)
|
||||||
self.setArtwork(listitem)
|
self.setArtwork(listitem)
|
||||||
if dbid == 'plexnode':
|
if kodi_id == 'plexnode':
|
||||||
# Need to get yet another xml to get final url
|
# Need to get yet another xml to get final url
|
||||||
window('emby_%s.playmethod' % playurl, clear=True)
|
window('emby_%s.playmethod' % playurl, clear=True)
|
||||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||||
'{server}%s' % item[0][0][0].attrib.get('key'))
|
'{server}%s' % item[0][0].attrib.get('key'))
|
||||||
if xml in (None, 401):
|
try:
|
||||||
|
xml[0].attrib
|
||||||
|
except (TypeError, AttributeError):
|
||||||
log.error('Could not download %s'
|
log.error('Could not download %s'
|
||||||
% item[0][0][0].attrib.get('key'))
|
% item[0][0].attrib.get('key'))
|
||||||
return xbmcplugin.setResolvedUrl(
|
return
|
||||||
int(sys.argv[1]), False, listitem)
|
|
||||||
playurl = tryEncode(xml[0].attrib.get('key'))
|
playurl = tryEncode(xml[0].attrib.get('key'))
|
||||||
window('emby_%s.playmethod' % playurl, value='DirectStream')
|
window('emby_%s.playmethod' % playurl, value='DirectStream')
|
||||||
|
|
||||||
|
@ -82,20 +86,23 @@ class PlaybackUtils():
|
||||||
window('emby_%s.playmethod' % playurl, "Transcode")
|
window('emby_%s.playmethod' % playurl, "Transcode")
|
||||||
listitem.setPath(playurl)
|
listitem.setPath(playurl)
|
||||||
self.setProperties(playurl, listitem)
|
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'
|
contextmenu_play = window('plex_contextplay') == 'true'
|
||||||
window('plex_contextplay', clear=True)
|
window('plex_contextplay', clear=True)
|
||||||
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
|
homeScreen = getCondVisibility('Window.IsActive(home)')
|
||||||
kodiPl = self.pl.kodi_pl
|
sizePlaylist = len(playqueue.items)
|
||||||
sizePlaylist = kodiPl.size()
|
|
||||||
if contextmenu_play:
|
if contextmenu_play:
|
||||||
# Need to start with the items we're inserting here
|
# Need to start with the items we're inserting here
|
||||||
startPos = sizePlaylist
|
startPos = sizePlaylist
|
||||||
else:
|
else:
|
||||||
# Can return -1
|
# Can return -1
|
||||||
startPos = max(kodiPl.getposition(), 0)
|
startPos = max(playqueue.kodi_pl.getposition(), 0)
|
||||||
self.currentPosition = startPos
|
self.currentPosition = startPos
|
||||||
|
|
||||||
propertiesPlayback = window('plex_playbackProps') == "true"
|
propertiesPlayback = window('plex_playbackProps') == "true"
|
||||||
|
@ -107,8 +114,8 @@ class PlaybackUtils():
|
||||||
log.info("Playlist plugin position: %s" % self.currentPosition)
|
log.info("Playlist plugin position: %s" % self.currentPosition)
|
||||||
log.info("Playlist size: %s" % sizePlaylist)
|
log.info("Playlist size: %s" % sizePlaylist)
|
||||||
|
|
||||||
############### RESUME POINT ################
|
# RESUME POINT ################
|
||||||
seektime, runtime = API.getRuntime()
|
seektime, runtime = api.getRuntime()
|
||||||
if window('plex_customplaylist.seektime'):
|
if window('plex_customplaylist.seektime'):
|
||||||
# Already got seektime, e.g. from playqueue & Plex companion
|
# Already got seektime, e.g. from playqueue & Plex companion
|
||||||
seektime = int(window('plex_customplaylist.seektime'))
|
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.
|
# We need to ensure we add the intro and additional parts only once.
|
||||||
# Otherwise we get a loop.
|
# Otherwise we get a loop.
|
||||||
if not propertiesPlayback:
|
if not propertiesPlayback:
|
||||||
|
|
||||||
window('plex_playbackProps', value="true")
|
window('plex_playbackProps', value="true")
|
||||||
log.info("Setting up properties in playlist.")
|
log.info("Setting up properties in playlist.")
|
||||||
|
# Where will the player need to start?
|
||||||
# Post playQueue to PMS
|
# Do we need to get trailers?
|
||||||
trailers = False
|
trailers = False
|
||||||
if settings('enableCinema') == "true":
|
if (api.getType() == 'movie' and not seektime and
|
||||||
|
settings('enableCinema') == "true"):
|
||||||
if settings('askCinema') == "true":
|
if settings('askCinema') == "true":
|
||||||
trailers = xbmcgui.Dialog().yesno(addonName,
|
trailers = xbmcgui.Dialog().yesno(
|
||||||
|
addonName,
|
||||||
"Play trailers?")
|
"Play trailers?")
|
||||||
else:
|
else:
|
||||||
trailers = True
|
trailers = True
|
||||||
xml = PF.GetPlexPlaylist(
|
# Post to the PMS. REUSE THE PLAYQUEUE!
|
||||||
itemid,
|
xml = GetPlexPlaylist(
|
||||||
item.attrib.get('librarySectionUUID'),
|
plex_id,
|
||||||
mediatype=API.getType(),
|
plex_lib_UUID,
|
||||||
|
mediatype=api.getType(),
|
||||||
trailers=trailers)
|
trailers=trailers)
|
||||||
# Save playQueueID for other PKC python instance & kodimonitor
|
log.debug('xml: ID: %s' % xml.attrib['playQueueID'])
|
||||||
window('plex_playQueueID', value=xml.attrib.get('playQueueID'))
|
get_playlist_details_from_xml(playqueue, xml=xml)
|
||||||
|
log.debug('finished ')
|
||||||
|
|
||||||
if (not homeScreen and not seektime and
|
if (not homeScreen and not seektime and
|
||||||
window('plex_customplaylist') != "true" and
|
window('plex_customplaylist') != "true" and
|
||||||
not contextmenu_play):
|
not contextmenu_play):
|
||||||
|
# Need to add a dummy file because the first item will fail
|
||||||
log.debug("Adding dummy file to playlist.")
|
log.debug("Adding dummy file to playlist.")
|
||||||
# Make sure Kodimonitor recognizes dummy
|
|
||||||
listitem.setLabel('plex_dummyfile')
|
|
||||||
dummyPlaylist = True
|
dummyPlaylist = True
|
||||||
PL.add_listitem_to_Kodi_playlist(
|
add_listitem_to_Kodi_playlist(
|
||||||
self.pl,
|
playqueue,
|
||||||
listitem,
|
startPos,
|
||||||
|
xbmcgui.ListItem(),
|
||||||
playurl,
|
playurl,
|
||||||
startPos)
|
xml[0])
|
||||||
# Remove the original item from playlist
|
# 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
|
# Readd the original item to playlist - via jsonrpc so we have
|
||||||
# full metadata
|
# full metadata
|
||||||
PL.insert_into_Kodi_playlist(
|
add_item_to_kodi_playlist(
|
||||||
self.pl,
|
playqueue,
|
||||||
self.currentPosition+1,
|
self.currentPosition+1,
|
||||||
dbid,
|
kodi_id=kodi_id,
|
||||||
PF.KODITYPE_FROM_PLEXTYPE[API.getType()])
|
kodi_type=kodi_type,
|
||||||
|
file=playurl)
|
||||||
self.currentPosition += 1
|
self.currentPosition += 1
|
||||||
|
|
||||||
############### -- CHECK FOR INTROS ################
|
# -- ADD TRAILERS ################
|
||||||
if trailers and not seektime:
|
if trailers:
|
||||||
# if we have any play them when the movie/show is not being resumed
|
|
||||||
introsPlaylist = self.AddTrailers(xml)
|
introsPlaylist = self.AddTrailers(xml)
|
||||||
|
|
||||||
############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ##############
|
# -- ADD MAIN ITEM ONLY FOR HOMESCREEN ##############
|
||||||
|
|
||||||
if homeScreen and not seektime and not sizePlaylist:
|
if homeScreen and not seektime and not sizePlaylist:
|
||||||
# Extend our current playlist with the actual item to play
|
# Extend our current playlist with the actual item to play
|
||||||
# only if there's no playlist first
|
# only if there's no playlist first
|
||||||
log.info("Adding main item to playlist.")
|
log.info("Adding main item to playlist.")
|
||||||
PL.add_dbid_to_Kodi_playlist(
|
add_item_to_kodi_playlist(
|
||||||
self.pl,
|
playqueue,
|
||||||
dbid=dbid,
|
self.currentPosition,
|
||||||
mediatype=PF.KODITYPE_FROM_PLEXTYPE[API.getType()])
|
kodi_id,
|
||||||
|
kodi_type)
|
||||||
|
|
||||||
elif contextmenu_play:
|
elif contextmenu_play:
|
||||||
if window('useDirectPaths') == 'true':
|
if window('useDirectPaths') == 'true':
|
||||||
|
@ -187,17 +199,16 @@ class PlaybackUtils():
|
||||||
listitem, tryDecode(playurl)))
|
listitem, tryDecode(playurl)))
|
||||||
window('emby_%s.playmethod' % playurl,
|
window('emby_%s.playmethod' % playurl,
|
||||||
value="Transcode")
|
value="Transcode")
|
||||||
|
api.CreateListItemFromPlexItem(listitem)
|
||||||
self.setProperties(playurl, listitem)
|
self.setProperties(playurl, listitem)
|
||||||
self.setArtwork(listitem)
|
self.setArtwork(listitem)
|
||||||
API.CreateListItemFromPlexItem(listitem)
|
|
||||||
kodiPl.add(playurl, listitem, index=self.currentPosition+1)
|
kodiPl.add(playurl, listitem, index=self.currentPosition+1)
|
||||||
else:
|
else:
|
||||||
# Full metadata
|
# Full metadata
|
||||||
PL.insert_into_Kodi_playlist(
|
self.pl.insertintoPlaylist(
|
||||||
self.pl,
|
|
||||||
self.currentPosition+1,
|
self.currentPosition+1,
|
||||||
dbid=dbid,
|
kodi_id,
|
||||||
mediatype=PF.KODITYPE_FROM_PLEXTYPE[API.getType()])
|
kodi_type)
|
||||||
self.currentPosition += 1
|
self.currentPosition += 1
|
||||||
if seektime:
|
if seektime:
|
||||||
window('plex_customplaylist.seektime', value=str(seektime))
|
window('plex_customplaylist.seektime', value=str(seektime))
|
||||||
|
@ -205,44 +216,50 @@ class PlaybackUtils():
|
||||||
# Ensure that additional parts are played after the main item
|
# Ensure that additional parts are played after the main item
|
||||||
self.currentPosition += 1
|
self.currentPosition += 1
|
||||||
|
|
||||||
############### -- CHECK FOR ADDITIONAL PARTS ################
|
# -- CHECK FOR ADDITIONAL PARTS ################
|
||||||
if len(item[0][0]) > 1:
|
if len(item[0]) > 1:
|
||||||
# Only add to the playlist after intros have played
|
# 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
|
# Never add first part
|
||||||
if counter == 0:
|
if counter == 0:
|
||||||
continue
|
continue
|
||||||
# Set listitem and properties for each additional parts
|
# Set listitem and properties for each additional parts
|
||||||
API.setPartNumber(counter)
|
api.setPartNumber(counter)
|
||||||
additionalListItem = xbmcgui.ListItem()
|
additionalListItem = xbmcgui.ListItem()
|
||||||
additionalPlayurl = playutils.getPlayUrl(
|
additionalPlayurl = playutils.getPlayUrl(
|
||||||
partNumber=counter)
|
partNumber=counter)
|
||||||
log.debug("Adding additional part: %s, url: %s"
|
log.debug("Adding additional part: %s, url: %s"
|
||||||
% (counter, additionalPlayurl))
|
% (counter, additionalPlayurl))
|
||||||
|
api.CreateListItemFromPlexItem(additionalListItem)
|
||||||
self.setProperties(additionalPlayurl, additionalListItem)
|
self.setProperties(additionalPlayurl, additionalListItem)
|
||||||
self.setArtwork(additionalListItem)
|
self.setArtwork(additionalListItem)
|
||||||
# NEW to Plex
|
add_listitem_to_playlist(
|
||||||
API.CreateListItemFromPlexItem(additionalListItem)
|
playqueue,
|
||||||
|
self.currentPosition,
|
||||||
kodiPl.add(additionalPlayurl, additionalListItem,
|
additionalListItem,
|
||||||
index=self.currentPosition)
|
kodi_id=kodi_id,
|
||||||
|
kodi_type=kodi_type,
|
||||||
|
plex_id=plex_id,
|
||||||
|
file=additionalPlayurl)
|
||||||
self.currentPosition += 1
|
self.currentPosition += 1
|
||||||
API.setPartNumber(0)
|
api.setPartNumber(0)
|
||||||
|
|
||||||
if dummyPlaylist:
|
if dummyPlaylist:
|
||||||
# Added a dummy file to the playlist,
|
# Added a dummy file to the playlist,
|
||||||
# because the first item is going to fail automatically.
|
# because the first item is going to fail automatically.
|
||||||
log.info("Processed as a playlist. First item is skipped.")
|
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.
|
# We just skipped adding properties. Reset flag for next time.
|
||||||
elif propertiesPlayback:
|
elif propertiesPlayback:
|
||||||
log.debug("Resetting properties playback flag.")
|
log.debug("Resetting properties playback flag.")
|
||||||
window('plex_playbackProps', clear=True)
|
window('plex_playbackProps', clear=True)
|
||||||
|
|
||||||
#self.pl.verifyPlaylist()
|
# SETUP MAIN ITEM ##########
|
||||||
########## SETUP MAIN ITEM ##########
|
|
||||||
# For transcoding only, ask for audio/subs pref
|
# For transcoding only, ask for audio/subs pref
|
||||||
if (window('emby_%s.playmethod' % playurl) == "Transcode" and
|
if (window('emby_%s.playmethod' % playurl) == "Transcode" and
|
||||||
not contextmenu_play):
|
not contextmenu_play):
|
||||||
|
@ -254,40 +271,42 @@ class PlaybackUtils():
|
||||||
listitem.setPath(playurl)
|
listitem.setPath(playurl)
|
||||||
self.setProperties(playurl, listitem)
|
self.setProperties(playurl, listitem)
|
||||||
|
|
||||||
############### PLAYBACK ################
|
# PLAYBACK ################
|
||||||
if (homeScreen and seektime and window('plex_customplaylist') != "true"
|
if (homeScreen and seektime and window('plex_customplaylist') != "true"
|
||||||
and not contextmenu_play):
|
and not contextmenu_play):
|
||||||
log.info("Play as a widget item.")
|
log.info("Play as a widget item")
|
||||||
API.CreateListItemFromPlexItem(listitem)
|
api.CreateListItemFromPlexItem(listitem)
|
||||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
result.listitem = listitem
|
||||||
|
return result
|
||||||
|
|
||||||
elif ((introsPlaylist and window('plex_customplaylist') == "true") or
|
elif ((introsPlaylist and window('plex_customplaylist') == "true") or
|
||||||
(homeScreen and not sizePlaylist) or
|
(homeScreen and not sizePlaylist) or
|
||||||
contextmenu_play):
|
contextmenu_play):
|
||||||
# Playlist was created just now, play it.
|
# Playlist was created just now, play it.
|
||||||
# Contextmenu plays always need this
|
# Contextmenu plays always need this
|
||||||
log.info("Play playlist.")
|
log.info("Play playlist")
|
||||||
xbmcplugin.endOfDirectory(int(sys.argv[1]), True, False, False)
|
# Need a separate thread because Player won't return in time
|
||||||
xbmc.Player().play(kodiPl, startpos=startPos)
|
thread = Thread(target=Player().play,
|
||||||
|
args=(playqueue.kodi_pl, None, False, startPos))
|
||||||
|
thread.setDaemon(True)
|
||||||
|
thread.start()
|
||||||
|
# Don't attach listitem
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
log.info("Play as a regular item.")
|
log.info("Play as a regular item")
|
||||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
result.listitem = listitem
|
||||||
|
return result
|
||||||
|
|
||||||
def AddTrailers(self, xml):
|
def AddTrailers(self, xml):
|
||||||
"""
|
"""
|
||||||
Adds trailers to a movie, if applicable. Returns True if trailers were
|
Adds trailers to a movie, if applicable. Returns True if trailers were
|
||||||
added
|
added
|
||||||
"""
|
"""
|
||||||
# Failure when downloading trailer playQueue
|
|
||||||
if xml in (None, 401):
|
|
||||||
return False
|
|
||||||
# Failure when getting trailers, e.g. when no plex pass
|
# Failure when getting trailers, e.g. when no plex pass
|
||||||
if xml.attrib.get('size') == '1':
|
if xml.attrib.get('size') == '1':
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Playurl needs to point back so we can get metadata!
|
# Playurl needs to point back so we can get metadata!
|
||||||
path = "plugin://plugin.video.plexkodiconnect.movies/"
|
path = "plugin://plugin.video.plexkodiconnect/movies/"
|
||||||
params = {
|
params = {
|
||||||
'mode': "play",
|
'mode': "play",
|
||||||
'dbid': 'plextrailer'
|
'dbid': 'plextrailer'
|
||||||
|
@ -296,28 +315,29 @@ class PlaybackUtils():
|
||||||
# Don't process the last item - it's the original movie
|
# Don't process the last item - it's the original movie
|
||||||
if counter == len(xml)-1:
|
if counter == len(xml)-1:
|
||||||
break
|
break
|
||||||
# The server randomly returns intros, process them.
|
introAPI = API(intro)
|
||||||
# introListItem = xbmcgui.ListItem()
|
listitem = introAPI.CreateListItemFromPlexItem()
|
||||||
# introPlayurl = putils.PlayUtils(intro).getPlayUrl()
|
|
||||||
introAPI = PlexAPI.API(intro)
|
|
||||||
params['id'] = introAPI.getRatingKey()
|
params['id'] = introAPI.getRatingKey()
|
||||||
params['filename'] = introAPI.getKey()
|
params['filename'] = introAPI.getKey()
|
||||||
introPlayurl = path + '?' + urlencode(params)
|
introPlayurl = path + '?' + urlencode(params)
|
||||||
|
self.setArtwork(listitem, introAPI)
|
||||||
|
# Overwrite the Plex url
|
||||||
|
listitem.setPath(introPlayurl)
|
||||||
log.info("Adding Intro: %s" % introPlayurl)
|
log.info("Adding Intro: %s" % introPlayurl)
|
||||||
|
add_listitem_to_Kodi_playlist(
|
||||||
PL.insert_into_Kodi_playlist(
|
self.playqueue,
|
||||||
self.pl,
|
|
||||||
self.currentPosition,
|
self.currentPosition,
|
||||||
url=introPlayurl)
|
listitem,
|
||||||
|
introPlayurl,
|
||||||
|
intro)
|
||||||
self.currentPosition += 1
|
self.currentPosition += 1
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setProperties(self, playurl, listitem):
|
def setProperties(self, playurl, listitem):
|
||||||
# Set all properties necessary for plugin path playback
|
# Set all properties necessary for plugin path playback
|
||||||
itemid = self.API.getRatingKey()
|
itemid = self.api.getRatingKey()
|
||||||
itemtype = self.API.getType()
|
itemtype = self.api.getType()
|
||||||
userdata = self.API.getUserData()
|
userdata = self.api.getUserData()
|
||||||
|
|
||||||
embyitem = "emby_%s" % playurl
|
embyitem = "emby_%s" % playurl
|
||||||
window('%s.runtime' % embyitem, value=str(userdata['Runtime']))
|
window('%s.runtime' % embyitem, value=str(userdata['Runtime']))
|
||||||
|
@ -327,20 +347,22 @@ class PlaybackUtils():
|
||||||
|
|
||||||
if itemtype == "episode":
|
if itemtype == "episode":
|
||||||
window('%s.refreshid' % embyitem,
|
window('%s.refreshid' % embyitem,
|
||||||
value=self.API.getParentRatingKey())
|
value=self.api.getParentRatingKey())
|
||||||
else:
|
else:
|
||||||
window('%s.refreshid' % embyitem, value=itemid)
|
window('%s.refreshid' % embyitem, value=itemid)
|
||||||
|
|
||||||
# Append external subtitles to stream
|
# Append external subtitles to stream
|
||||||
playmethod = window('%s.playmethod' % embyitem)
|
playmethod = window('%s.playmethod' % embyitem)
|
||||||
if playmethod in ("DirectStream", "DirectPlay"):
|
if playmethod in ("DirectStream", "DirectPlay"):
|
||||||
subtitles = self.API.externalSubs(playurl)
|
subtitles = self.api.externalSubs(playurl)
|
||||||
listitem.setSubtitles(subtitles)
|
listitem.setSubtitles(subtitles)
|
||||||
|
|
||||||
self.setArtwork(listitem)
|
self.setArtwork(listitem)
|
||||||
|
|
||||||
def setArtwork(self, listItem):
|
def setArtwork(self, listItem, api=None):
|
||||||
allartwork = self.API.getAllArtwork(parentInfo=True)
|
if api is None:
|
||||||
|
api = self.api
|
||||||
|
allartwork = api.getAllArtwork(parentInfo=True)
|
||||||
arttypes = {
|
arttypes = {
|
||||||
'poster': "Primary",
|
'poster': "Primary",
|
||||||
'tvshow.poster': "Thumb",
|
'tvshow.poster': "Thumb",
|
||||||
|
|
|
@ -3,7 +3,7 @@ from urllib import quote
|
||||||
|
|
||||||
import embydb_functions as embydb
|
import embydb_functions as embydb
|
||||||
from downloadutils import DownloadUtils as DU
|
from downloadutils import DownloadUtils as DU
|
||||||
from utils import window, JSONRPC, tryEncode
|
from utils import JSONRPC, tryEncode, tryDecode
|
||||||
from PlexAPI import API
|
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):
|
class Playlist_Object_Baseclase(object):
|
||||||
playlistid = None # Kodi playlist ID, [int]
|
playlistid = None # Kodi playlist ID, [int]
|
||||||
|
@ -25,8 +28,6 @@ class Playlist_Object_Baseclase(object):
|
||||||
selectedItemOffset = None
|
selectedItemOffset = None
|
||||||
shuffled = 0 # [int], 0: not shuffled, 1: ??? 2: ???
|
shuffled = 0 # [int], 0: not shuffled, 1: ??? 2: ???
|
||||||
repeat = 0 # [int], 0: not repeated, 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):
|
def __repr__(self):
|
||||||
answ = "<%s: " % (self.__class__.__name__)
|
answ = "<%s: " % (self.__class__.__name__)
|
||||||
|
@ -52,7 +53,6 @@ class Playlist_Object_Baseclase(object):
|
||||||
self.selectedItemOffset = None
|
self.selectedItemOffset = None
|
||||||
self.shuffled = 0
|
self.shuffled = 0
|
||||||
self.repeat = 0
|
self.repeat = 0
|
||||||
self.PKC_playlist_edits = []
|
|
||||||
log.debug('Playlist cleared: %s' % self)
|
log.debug('Playlist cleared: %s' % self)
|
||||||
|
|
||||||
def log_Kodi_playlist(self):
|
def log_Kodi_playlist(self):
|
||||||
|
@ -84,7 +84,7 @@ class Playlist_Item(object):
|
||||||
return answ[:-2] + ">"
|
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
|
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'])
|
kodi_item['type'])
|
||||||
try:
|
try:
|
||||||
item.plex_id = emby_dbitem[0]
|
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:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
item.file = kodi_item.get('file') if kodi_item.get('file') else None
|
item.file = kodi_item.get('file')
|
||||||
item.kodi_type = kodi_item.get('type') if kodi_item.get('type') else None
|
item.kodi_type = kodi_item.get('type')
|
||||||
if item.plex_id is None:
|
if item.plex_id is None:
|
||||||
item.uri = 'library://whatever/item/%s' % quote(item.file, safe='')
|
item.uri = 'library://whatever/item/%s' % quote(item.file, safe='')
|
||||||
else:
|
else:
|
||||||
|
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
|
||||||
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
||||||
(item.plex_UUID, item.plex_id))
|
(item.plex_UUID, item.plex_id))
|
||||||
return item
|
return item
|
||||||
|
@ -115,6 +116,8 @@ def playlist_item_from_kodi_item(kodi_item):
|
||||||
def playlist_item_from_plex(plex_id):
|
def playlist_item_from_plex(plex_id):
|
||||||
"""
|
"""
|
||||||
Returns a playlist element providing the plex_id ("ratingKey")
|
Returns a playlist element providing the plex_id ("ratingKey")
|
||||||
|
|
||||||
|
Returns a Playlist_Item
|
||||||
"""
|
"""
|
||||||
item = Playlist_Item()
|
item = Playlist_Item()
|
||||||
item.plex_id = plex_id
|
item.plex_id = plex_id
|
||||||
|
@ -128,6 +131,26 @@ def playlist_item_from_plex(plex_id):
|
||||||
return item
|
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):
|
def _log_xml(xml):
|
||||||
try:
|
try:
|
||||||
xml.attrib
|
xml.attrib
|
||||||
|
@ -154,7 +177,7 @@ def _get_playListVersion_from_xml(playlist, xml):
|
||||||
return True
|
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.
|
Takes a PMS xml as input and overwrites all the playlist's details, e.g.
|
||||||
playlist.ID with the XML's playQueueID
|
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.error(traceback.format_exc())
|
||||||
_log_xml(xml)
|
_log_xml(xml)
|
||||||
raise KeyError
|
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):
|
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:
|
if plex_id:
|
||||||
item = playlist_item_from_plex(plex_id)
|
item = playlist_item_from_plex(plex_id)
|
||||||
else:
|
else:
|
||||||
item = playlist_item_from_kodi_item(kodi_item)
|
item = playlist_item_from_kodi(kodi_item)
|
||||||
params = {
|
params = {
|
||||||
'next': 0,
|
'next': 0,
|
||||||
'type': playlist.type,
|
'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,
|
xml = DU().downloadUrl(url="{server}/%ss" % playlist.kind,
|
||||||
action_type="POST",
|
action_type="POST",
|
||||||
parameters=params)
|
parameters=params)
|
||||||
_get_playlist_details_from_xml(playlist, xml)
|
get_playlist_details_from_xml(playlist, xml)
|
||||||
playlist.items.append(item)
|
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
|
Adds a listitem to both the Kodi and Plex playlist at position pos [int].
|
||||||
[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)
|
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")
|
xml = DU().downloadUrl(url, action_type="PUT")
|
||||||
try:
|
try:
|
||||||
item.ID = xml.attrib['%sLastAddedItemID' % playlist.kind]
|
item.ID = xml[-1].attrib['%sItemID' % playlist.kind]
|
||||||
except (TypeError, AttributeError, KeyError):
|
except (TypeError, AttributeError, KeyError):
|
||||||
log.error('Could not add item %s to playlist %s'
|
log.error('Could not add item %s to playlist %s'
|
||||||
% (kodi_item, playlist))
|
% (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:
|
if plex_item.attrib['%sItemID' % playlist.kind] == item.ID:
|
||||||
item.guid = plex_item.attrib['guid']
|
item.guid = plex_item.attrib['guid']
|
||||||
playlist.items.append(item)
|
playlist.items.append(item)
|
||||||
if after_pos == len(playlist.items) - 1:
|
if pos == len(playlist.items) - 1:
|
||||||
# Item was added at the end
|
# Item was added at the end
|
||||||
_get_playListVersion_from_xml(playlist, xml)
|
_get_playListVersion_from_xml(playlist, xml)
|
||||||
else:
|
else:
|
||||||
# Move the new item to the correct position
|
# Move the new item to the correct position
|
||||||
move_playlist_item(playlist,
|
move_playlist_item(playlist,
|
||||||
len(playlist.items) - 1,
|
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):
|
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:
|
if after_pos == 0:
|
||||||
url = "{server}/%ss/%s/items/%s/move?after=0" % \
|
url = "{server}/%ss/%s/items/%s/move?after=0" % \
|
||||||
(playlist.kind,
|
(playlist.kind,
|
||||||
|
@ -249,6 +377,7 @@ def move_playlist_item(playlist, before_pos, after_pos):
|
||||||
_get_playListVersion_from_xml(playlist, xml)
|
_get_playListVersion_from_xml(playlist, xml)
|
||||||
# Move our item's position in our internal playlist
|
# Move our item's position in our internal playlist
|
||||||
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
|
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):
|
def get_PMS_playlist(playlist, playlist_id=None):
|
||||||
|
@ -280,32 +409,14 @@ def refresh_playlist_from_PMS(playlist):
|
||||||
except:
|
except:
|
||||||
log.error('Could not download Plex playlist.')
|
log.error('Could not download Plex playlist.')
|
||||||
return
|
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
|
Delete the item at position pos [int] on the Plex side and our playlists
|
||||||
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]
|
|
||||||
"""
|
"""
|
||||||
|
log.debug('Deleting position %s for %s on the Plex side' % (pos, playlist))
|
||||||
xml = DU().downloadUrl("{server}/%ss/%s/items/%s?repeat=%s" %
|
xml = DU().downloadUrl("{server}/%ss/%s/items/%s?repeat=%s" %
|
||||||
(playlist.kind,
|
(playlist.kind,
|
||||||
playlist.ID,
|
playlist.ID,
|
||||||
|
@ -313,7 +424,7 @@ def delete_playlist_item(playlist, pos):
|
||||||
playlist.repeat),
|
playlist.repeat),
|
||||||
action_type="DELETE")
|
action_type="DELETE")
|
||||||
_get_playListVersion_from_xml(playlist, xml)
|
_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):
|
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).
|
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).
|
Pass in the PMS xml's video element (one level underneath MediaContainer).
|
||||||
|
|
||||||
Will return a Playlist_Item
|
Returns a Playlist_Item
|
||||||
"""
|
"""
|
||||||
item = Playlist_Item()
|
item = playlist_item_from_xml(playlist, xml_video_element)
|
||||||
api = API(xml_video_element)
|
|
||||||
params = {
|
params = {
|
||||||
'playlistid': playlist.playlistid
|
'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:
|
if item.kodi_id:
|
||||||
params['item'] = {'%sid' % item.kodi_type: item.kodi_id}
|
params['item'] = {'%sid' % item.kodi_type: item.kodi_id}
|
||||||
else:
|
else:
|
||||||
item.file = api.getFilePath()
|
|
||||||
params['item'] = {'file': tryEncode(item.file)}
|
params['item'] = {'file': tryEncode(item.file)}
|
||||||
log.debug(JSONRPC('Playlist.Add').execute(params))
|
log.debug(JSONRPC('Playlist.Add').execute(params))
|
||||||
playlist.PKC_playlist_edits.append(
|
|
||||||
item.kodi_id if item.kodi_id else item.file)
|
|
||||||
return item
|
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
|
Adds an xbmc listitem to the Kodi playlist.xml_video_element
|
||||||
by settings window('plex_ignore_Playlist.OnAdd')
|
|
||||||
|
WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS
|
||||||
"""
|
"""
|
||||||
playlist.kodi_pl.add(file, listitem, index=index)
|
log.debug('Insert listitem at position %s for Kodi only for %s'
|
||||||
|
% (pos, playlist))
|
||||||
|
# Add the item into Kodi playlist
|
||||||
def add_dbid_to_Kodi_playlist(playlist, dbid=None, mediatype=None, url=None):
|
playlist.kodi_pl.add(file, listitem, index=pos)
|
||||||
params = {
|
# We need to add this to our internal queue as well
|
||||||
'playlistid': playlist.playlistid
|
if xml_video_element is not None:
|
||||||
}
|
item = playlist_item_from_xml(playlist, xml_video_element)
|
||||||
if dbid is not None:
|
item.file = file
|
||||||
params['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
|
|
||||||
else:
|
else:
|
||||||
params['item'] = {'file': url}
|
item = playlist_item_from_kodi(kodi_item)
|
||||||
log.debug(JSONRPC('Playlist.Add').execute(params))
|
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
|
Removes the item at position pos from the Kodi playlist using JSON.
|
||||||
ignored by kodimonitor by settings window('plex_ignore_Playlist.OnRemove')
|
|
||||||
|
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({
|
log.debug(JSONRPC('Playlist.Remove').execute({
|
||||||
'playlistid': playlist.playlistid,
|
'playlistid': playlist.playlistid,
|
||||||
'position': position
|
'position': pos
|
||||||
}))
|
}))
|
||||||
|
del playlist.items[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)
|
|
||||||
|
|
||||||
|
|
||||||
# NOT YET UPDATED!!
|
# NOT YET UPDATED!!
|
||||||
|
@ -478,7 +564,7 @@ def _addtoPlaylist_xbmc(self, item):
|
||||||
'id': API.getRatingKey(),
|
'id': API.getRatingKey(),
|
||||||
'filename': API.getKey()
|
'filename': API.getKey()
|
||||||
}
|
}
|
||||||
playurl = "plugin://plugin.video.plexkodiconnect.movies/?%s" \
|
playurl = "plugin://plugin.video.plexkodiconnect/movies/?%s" \
|
||||||
% urlencode(params)
|
% urlencode(params)
|
||||||
|
|
||||||
listitem = API.CreateListItemFromPlexItem()
|
listitem = API.CreateListItemFromPlexItem()
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
###############################################################################
|
###############################################################################
|
||||||
import logging
|
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, \
|
from utils import window, ThreadMethods, ThreadMethodsAdditionalSuspend
|
||||||
Lock_Function
|
|
||||||
import playlist_func as PL
|
import playlist_func as PL
|
||||||
from PlexFunctions import ConvertPlexToKodiTime
|
from PlexFunctions import ConvertPlexToKodiTime
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
log = logging.getLogger("PLEX."+__name__)
|
log = logging.getLogger("PLEX."+__name__)
|
||||||
|
|
||||||
# Lock used to lock methods
|
# Lock used for playqueue manipulations
|
||||||
lock = Lock()
|
lock = RLock()
|
||||||
lockmethod = Lock_Function(lock)
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,16 +27,15 @@ class Playqueue(Thread):
|
||||||
__shared_state = {}
|
__shared_state = {}
|
||||||
playqueues = None
|
playqueues = None
|
||||||
|
|
||||||
@lockmethod.lockthis
|
|
||||||
def __init__(self, callback=None):
|
def __init__(self, callback=None):
|
||||||
self.__dict__ = self.__shared_state
|
self.__dict__ = self.__shared_state
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
if self.playqueues is not None:
|
if self.playqueues is not None:
|
||||||
return
|
return
|
||||||
self.mgr = callback
|
self.mgr = callback
|
||||||
self.player = xbmc.Player()
|
|
||||||
|
|
||||||
# Initialize Kodi playqueues
|
# Initialize Kodi playqueues
|
||||||
|
with lock:
|
||||||
self.playqueues = []
|
self.playqueues = []
|
||||||
for queue in PL.get_kodi_playqueues():
|
for queue in PL.get_kodi_playqueues():
|
||||||
playqueue = PL.Playqueue_Object()
|
playqueue = PL.Playqueue_Object()
|
||||||
|
@ -46,12 +43,12 @@ class Playqueue(Thread):
|
||||||
playqueue.type = queue['type']
|
playqueue.type = queue['type']
|
||||||
# Initialize each Kodi playlist
|
# Initialize each Kodi playlist
|
||||||
if playqueue.type == 'audio':
|
if playqueue.type == 'audio':
|
||||||
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
playqueue.kodi_pl = PlayList(PLAYLIST_MUSIC)
|
||||||
elif playqueue.type == 'video':
|
elif playqueue.type == 'video':
|
||||||
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
|
||||||
else:
|
else:
|
||||||
# Currently, only video or audio playqueues available
|
# Currently, only video or audio playqueues available
|
||||||
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
|
||||||
self.playqueues.append(playqueue)
|
self.playqueues.append(playqueue)
|
||||||
# sort the list by their playlistid, just in case
|
# sort the list by their playlistid, just in case
|
||||||
self.playqueues = sorted(
|
self.playqueues = sorted(
|
||||||
|
@ -63,30 +60,14 @@ class Playqueue(Thread):
|
||||||
Returns the playqueue according to the typus ('video', 'audio',
|
Returns the playqueue according to the typus ('video', 'audio',
|
||||||
'picture') passed in
|
'picture') passed in
|
||||||
"""
|
"""
|
||||||
|
with lock:
|
||||||
for playqueue in self.playqueues:
|
for playqueue in self.playqueues:
|
||||||
if playqueue.type == typus:
|
if playqueue.type == typus:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise ValueError('Wrong type was passed in: %s' % typus)
|
raise ValueError('Wrong playlist type passed in: %s' % typus)
|
||||||
return playqueue
|
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,
|
def update_playqueue_from_PMS(self,
|
||||||
playqueue,
|
playqueue,
|
||||||
playqueue_id=None,
|
playqueue_id=None,
|
||||||
|
@ -97,38 +78,16 @@ class Playqueue(Thread):
|
||||||
in playqueue_id if we need to fetch a new playqueue
|
in playqueue_id if we need to fetch a new playqueue
|
||||||
|
|
||||||
repeat = 0, 1, 2
|
repeat = 0, 1, 2
|
||||||
offset = time offset in Plextime
|
offset = time offset in Plextime (milliseconds)
|
||||||
"""
|
"""
|
||||||
log.info('New playqueue received from the PMS, updating!')
|
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)
|
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:
|
else:
|
||||||
startpos = None
|
log.debug('Restarting existing playQueue')
|
||||||
# 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 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)
|
PL.refresh_playlist_from_PMS(playqueue)
|
||||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||||
window('plex_customplaylist', value="true")
|
window('plex_customplaylist', value="true")
|
||||||
|
@ -140,126 +99,83 @@ class Playqueue(Thread):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
startpos = None
|
startpos = None
|
||||||
# Start playback
|
# Start playback. Player does not return in time
|
||||||
if startpos:
|
if startpos:
|
||||||
self.player.play(playqueue.kodi_pl, startpos=startpos)
|
thread = Thread(target=Player().play,
|
||||||
|
args=(playqueue.kodi_pl,
|
||||||
|
None,
|
||||||
|
False,
|
||||||
|
startpos))
|
||||||
else:
|
else:
|
||||||
self.player.play(playqueue.kodi_pl)
|
thread = Thread(target=Player().play,
|
||||||
playqueue.log_Kodi_playlist()
|
args=(playqueue.kodi_pl,))
|
||||||
|
thread.setDaemon(True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
@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):
|
def _compare_playqueues(self, playqueue, new):
|
||||||
"""
|
"""
|
||||||
Used to poll the Kodi playqueue and update the Plex playqueue if needed
|
Used to poll the Kodi playqueue and update the Plex playqueue if needed
|
||||||
"""
|
"""
|
||||||
if self.threadStopped():
|
old = list(playqueue.items)
|
||||||
# Chances are that we got an empty Kodi playlist due to Kodi exit
|
|
||||||
return
|
|
||||||
old = playqueue.old_kodi_pl
|
|
||||||
index = list(range(0, len(old)))
|
index = list(range(0, len(old)))
|
||||||
log.debug('Comparing new Kodi playqueue %s with our play queue %s'
|
log.debug('Comparing new Kodi playqueue %s with our play queue %s'
|
||||||
% (new, playqueue))
|
% (new, old))
|
||||||
for i, new_item in enumerate(new):
|
for i, new_item in enumerate(new):
|
||||||
for j, old_item in enumerate(old):
|
for j, old_item in enumerate(old):
|
||||||
if old_item.get('id') is None:
|
if self.threadStopped():
|
||||||
identical = old_item['file'] == new_item['file']
|
# 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:
|
else:
|
||||||
identical = (old_item['id'] == new_item['id'] and
|
identical = (old_item.kodi_id == new_item['id'] and
|
||||||
old_item['type'] == new_item['type'])
|
old_item.kodi_type == new_item['type'])
|
||||||
if j == 0 and identical:
|
if j == 0 and identical:
|
||||||
del old[j], index[j]
|
del old[j], index[j]
|
||||||
break
|
break
|
||||||
elif identical:
|
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)
|
PL.move_playlist_item(playqueue, i + j, i)
|
||||||
# Delete the item we just found
|
|
||||||
del old[j], index[j]
|
del old[j], index[j]
|
||||||
break
|
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:
|
else:
|
||||||
PL.add_playlist_item(playqueue, item, i)
|
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_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):
|
def run(self):
|
||||||
threadStopped = self.threadStopped
|
threadStopped = self.threadStopped
|
||||||
threadSuspended = self.threadSuspended
|
threadSuspended = self.threadSuspended
|
||||||
log.info("----===## Starting PlayQueue client ##===----")
|
log.info("----===## Starting PlayQueue client ##===----")
|
||||||
# Initialize the playqueues, if Kodi already got items in them
|
# 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 not threadStopped():
|
||||||
while threadSuspended():
|
while threadSuspended():
|
||||||
if threadStopped():
|
if threadStopped():
|
||||||
break
|
break
|
||||||
xbmc.sleep(1000)
|
sleep(1000)
|
||||||
|
with lock:
|
||||||
for playqueue in self.playqueues:
|
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)
|
kodi_playqueue = PL.get_kodi_playlist_items(playqueue)
|
||||||
if playqueue.old_kodi_pl != kodi_playqueue:
|
if playqueue.old_kodi_pl != kodi_playqueue:
|
||||||
# compare old and new playqueue
|
# compare old and new playqueue
|
||||||
self._compare_playqueues(playqueue, kodi_playqueue)
|
self._compare_playqueues(playqueue, kodi_playqueue)
|
||||||
playqueue.old_kodi_pl = list(kodi_playqueue)
|
playqueue.old_kodi_pl = list(kodi_playqueue)
|
||||||
xbmc.sleep(1000)
|
sleep(50)
|
||||||
log.info("----===## PlayQueue client stopped ##===----")
|
log.info("----===## PlayQueue client stopped ##===----")
|
||||||
|
|
|
@ -56,6 +56,24 @@ def window(property, value=None, clear=False, windowid=10000):
|
||||||
return tryDecode(win.getProperty(property))
|
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):
|
def settings(setting, value=None):
|
||||||
"""
|
"""
|
||||||
Get or add addon setting. Returns unicode
|
Get or add addon setting. Returns unicode
|
||||||
|
|
14
service.py
14
service.py
|
@ -44,6 +44,8 @@ from playqueue import Playqueue
|
||||||
|
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
from PlexCompanion import PlexCompanion
|
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
|
plexCompanion_running = False
|
||||||
playqueue_running = False
|
playqueue_running = False
|
||||||
kodimonitor_running = False
|
kodimonitor_running = False
|
||||||
|
playback_starter_running = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
|
@ -109,7 +112,8 @@ class Service():
|
||||||
"plex_authenticated", "PlexUserImage", "useDirectPaths",
|
"plex_authenticated", "PlexUserImage", "useDirectPaths",
|
||||||
"suspend_LibraryThread", "plex_terminateNow",
|
"suspend_LibraryThread", "plex_terminateNow",
|
||||||
"kodiplextimeoffset", "countError", "countUnauthorized",
|
"kodiplextimeoffset", "countError", "countUnauthorized",
|
||||||
"plex_restricteduser", "plex_allows_mediaDeletion"
|
"plex_restricteduser", "plex_allows_mediaDeletion",
|
||||||
|
"plex_play_new_item", "plex_result"
|
||||||
]
|
]
|
||||||
for prop in properties:
|
for prop in properties:
|
||||||
window(prop, clear=True)
|
window(prop, clear=True)
|
||||||
|
@ -133,6 +137,10 @@ class Service():
|
||||||
monitor = self.monitor
|
monitor = self.monitor
|
||||||
kodiProfile = xbmc.translatePath("special://profile")
|
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
|
# Server auto-detect
|
||||||
initialsetup.InitialSetup().setup()
|
initialsetup.InitialSetup().setup()
|
||||||
|
|
||||||
|
@ -142,6 +150,7 @@ class Service():
|
||||||
self.library = LibrarySync(self)
|
self.library = LibrarySync(self)
|
||||||
self.plexCompanion = PlexCompanion(self)
|
self.plexCompanion = PlexCompanion(self)
|
||||||
self.playqueue = Playqueue(self)
|
self.playqueue = Playqueue(self)
|
||||||
|
self.playback_starter = Playback_Starter(self)
|
||||||
|
|
||||||
plx = PlexAPI.PlexAPI()
|
plx = PlexAPI.PlexAPI()
|
||||||
|
|
||||||
|
@ -197,6 +206,9 @@ class Service():
|
||||||
if not self.plexCompanion_running:
|
if not self.plexCompanion_running:
|
||||||
self.plexCompanion_running = True
|
self.plexCompanion_running = True
|
||||||
self.plexCompanion.start()
|
self.plexCompanion.start()
|
||||||
|
if not self.playback_starter_running:
|
||||||
|
self.playback_starter_running = True
|
||||||
|
self.playback_starter.start()
|
||||||
else:
|
else:
|
||||||
if (self.user.currUser is None) and self.warn_auth:
|
if (self.user.currUser is None) and self.warn_auth:
|
||||||
# Alert user is not authenticated and suppress future
|
# Alert user is not authenticated and suppress future
|
||||||
|
|
Loading…
Add table
Reference in a new issue