Merge branch 'develop'
This commit is contained in:
commit
2836f707c5
37 changed files with 2979 additions and 2077 deletions
|
@ -1,13 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.plexkodiconnect"
|
||||
name="PlexKodiConnect"
|
||||
version="1.5.1"
|
||||
version="1.5.2"
|
||||
provider-name="croneter">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
<import addon="script.module.requests" version="2.3.0" />
|
||||
<import addon="plugin.video.plexkodiconnect.movies" version="0.01" />
|
||||
<import addon="plugin.video.plexkodiconnect.tvshows" version="0.01" />
|
||||
</requires>
|
||||
<extension point="xbmc.python.pluginsource"
|
||||
library="default.py">
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
version 1.5.2 (beta only)
|
||||
A DATABASE RESET IS ABSOLUTELY NECESSARY
|
||||
- Plex Companion is completely rewired and should now handly anything you throw at it
|
||||
- New playback startup mechanism for plugin paths
|
||||
- Krypton: add ratings and IMDB id for movies
|
||||
- Krypton: add ratings and theTvDB id for TV shows
|
||||
- Don't support Plex Companion mirror
|
||||
- Fix for Plex Companion not showing up
|
||||
- Code rebranding from Emby to Plex, including a plex.db database :-)
|
||||
- Lots of code refactoring and code optimizations
|
||||
|
||||
version 1.5.1 (beta only)
|
||||
- Fix playstate and PMS item changes not working/not propagating anymore (caused by a change Plex made with the websocket interface). UPGRADE YOUR PMS!!
|
||||
- Improvements to the way PKC behaves if the PMS goes offline
|
||||
|
|
123
default.py
123
default.py
|
@ -3,36 +3,38 @@
|
|||
###############################################################################
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import urlparse
|
||||
from os import path as os_path
|
||||
from sys import path as sys_path, argv
|
||||
from urlparse import parse_qsl
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
from xbmc import translatePath, sleep, executebuiltin
|
||||
from xbmcaddon import Addon
|
||||
from xbmcgui import ListItem, Dialog
|
||||
from xbmcplugin import setResolvedUrl
|
||||
|
||||
|
||||
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||
_addon = Addon(id='plugin.video.plexkodiconnect')
|
||||
try:
|
||||
_addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
||||
except TypeError:
|
||||
_addon_path = _addon.getAddonInfo('path').decode()
|
||||
try:
|
||||
_base_resource = xbmc.translatePath(os.path.join(
|
||||
_base_resource = translatePath(os_path.join(
|
||||
_addon_path,
|
||||
'resources',
|
||||
'lib')).decode('utf-8')
|
||||
except TypeError:
|
||||
_base_resource = xbmc.translatePath(os.path.join(
|
||||
_base_resource = translatePath(os_path.join(
|
||||
_addon_path,
|
||||
'resources',
|
||||
'lib')).decode()
|
||||
sys.path.append(_base_resource)
|
||||
sys_path.append(_base_resource)
|
||||
|
||||
###############################################################################
|
||||
|
||||
import entrypoint
|
||||
import utils
|
||||
from utils import window, pickl_window, reset, passwordsXML
|
||||
from pickler import unpickle_me
|
||||
from PKC_listitem import convert_PKC_to_listitem
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -43,34 +45,47 @@ log = logging.getLogger("PLEX.default")
|
|||
|
||||
###############################################################################
|
||||
|
||||
ARGV = argv
|
||||
HANDLE = int(argv[1])
|
||||
|
||||
|
||||
class Main():
|
||||
|
||||
# MAIN ENTRY POINT
|
||||
#@utils.profiling()
|
||||
# @utils.profiling()
|
||||
def __init__(self):
|
||||
log.debug("Full sys.argv received: %s" % ARGV)
|
||||
# Parse parameters
|
||||
log.warn("Full sys.argv received: %s" % sys.argv)
|
||||
base_url = sys.argv[0]
|
||||
params = urlparse.parse_qs(sys.argv[2][1:])
|
||||
params = dict(parse_qsl(ARGV[2][1:]))
|
||||
try:
|
||||
mode = params['mode'][0]
|
||||
mode = params['mode']
|
||||
itemid = params.get('id', '')
|
||||
if itemid:
|
||||
try:
|
||||
itemid = itemid[0]
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
params = {}
|
||||
mode = ""
|
||||
itemid = ''
|
||||
|
||||
if mode == 'play':
|
||||
# Put the request into the "queue"
|
||||
while window('plex_play_new_item'):
|
||||
sleep(20)
|
||||
window('plex_play_new_item',
|
||||
value='%s%s' % (mode, ARGV[2]))
|
||||
# Wait for the result
|
||||
while not pickl_window('plex_result'):
|
||||
sleep(20)
|
||||
result = unpickle_me()
|
||||
if result is None:
|
||||
log.error('Error encountered, aborting')
|
||||
setResolvedUrl(HANDLE, False, ListItem())
|
||||
elif result.listitem:
|
||||
listitem = convert_PKC_to_listitem(result.listitem)
|
||||
setResolvedUrl(HANDLE, True, listitem)
|
||||
return
|
||||
|
||||
modes = {
|
||||
|
||||
'reset': utils.reset,
|
||||
'reset': reset,
|
||||
'resetauth': entrypoint.resetAuth,
|
||||
'play': entrypoint.doPlayback,
|
||||
'passwords': utils.passwordsXML,
|
||||
'passwords': passwordsXML,
|
||||
'channels': entrypoint.BrowseChannels,
|
||||
'channelsfolder': entrypoint.BrowseChannels,
|
||||
'browsecontent': entrypoint.BrowseContent,
|
||||
|
@ -79,7 +94,6 @@ class Main():
|
|||
'inprogressepisodes': entrypoint.getInProgressEpisodes,
|
||||
'recentepisodes': entrypoint.getRecentEpisodes,
|
||||
'refreshplaylist': entrypoint.refreshPlaylist,
|
||||
'companion': entrypoint.plexCompanion,
|
||||
'switchuser': entrypoint.switchPlexUser,
|
||||
'deviceid': entrypoint.resetDeviceId,
|
||||
'delete': entrypoint.deleteItem,
|
||||
|
@ -89,24 +103,24 @@ class Main():
|
|||
'watchlater': entrypoint.watchlater,
|
||||
'enterPMS': entrypoint.enterPMS,
|
||||
'togglePlexTV': entrypoint.togglePlexTV,
|
||||
'playwatchlater': entrypoint.playWatchLater
|
||||
'Plex_Node': entrypoint.Plex_Node
|
||||
}
|
||||
|
||||
if "/extrafanart" in sys.argv[0]:
|
||||
plexpath = sys.argv[2][1:]
|
||||
plexid = params.get('id', [""])[0]
|
||||
if "/extrafanart" in ARGV[0]:
|
||||
plexpath = ARGV[2][1:]
|
||||
plexid = params.get('id', [""])
|
||||
entrypoint.getExtraFanArt(plexid, plexpath)
|
||||
entrypoint.getVideoFiles(plexid, plexpath)
|
||||
return
|
||||
|
||||
if mode == 'fanart':
|
||||
log.info('User requested fanarttv refresh')
|
||||
utils.window('plex_runLibScan', value='fanart')
|
||||
window('plex_runLibScan', value='fanart')
|
||||
|
||||
# Called by e.g. 3rd party plugin video extras
|
||||
if ("/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0] or
|
||||
"/Extras" in sys.argv[2]):
|
||||
plexId = params.get('id', [None])[0]
|
||||
if ("/Extras" in ARGV[0] or "/VideoFiles" in ARGV[0] or
|
||||
"/Extras" in ARGV[2]):
|
||||
plexId = params.get('id', None)
|
||||
entrypoint.getVideoFiles(plexId, params)
|
||||
|
||||
if modes.get(mode):
|
||||
|
@ -117,59 +131,60 @@ class Main():
|
|||
modes[mode](itemid, dbid)
|
||||
|
||||
elif mode in ("nextup", "inprogressepisodes"):
|
||||
limit = int(params['limit'][0])
|
||||
limit = int(params['limit'])
|
||||
modes[mode](itemid, limit)
|
||||
|
||||
elif mode in ("channels","getsubfolders"):
|
||||
modes[mode](itemid)
|
||||
|
||||
elif mode == "browsecontent":
|
||||
modes[mode](itemid, params.get('type',[""])[0], params.get('folderid',[""])[0])
|
||||
modes[mode](itemid, params.get('type',[""]), params.get('folderid',[""]))
|
||||
|
||||
elif mode == 'browseplex':
|
||||
modes[mode](
|
||||
itemid,
|
||||
params.get('type', [""])[0],
|
||||
params.get('folderid', [""])[0])
|
||||
params.get('type', [""]),
|
||||
params.get('folderid', [""]))
|
||||
|
||||
elif mode in ('ondeck', 'recentepisodes'):
|
||||
modes[mode](
|
||||
itemid,
|
||||
params.get('type', [""])[0],
|
||||
params.get('tagname', [""])[0],
|
||||
int(params.get('limit', [""])[0]))
|
||||
params.get('type', [""]),
|
||||
params.get('tagname', [""]),
|
||||
int(params.get('limit', [""])))
|
||||
|
||||
elif mode == "channelsfolder":
|
||||
folderid = params['folderid'][0]
|
||||
folderid = params['folderid']
|
||||
modes[mode](itemid, folderid)
|
||||
elif mode == "companion":
|
||||
modes[mode](itemid, params=sys.argv[2])
|
||||
elif mode == 'playwatchlater':
|
||||
modes[mode](params.get('id')[0], params.get('viewOffset')[0])
|
||||
modes[mode](itemid, params=ARGV[2])
|
||||
elif mode == 'Plex_Node':
|
||||
modes[mode](params.get('id'),
|
||||
params.get('viewOffset'),
|
||||
params.get('plex_type'))
|
||||
else:
|
||||
modes[mode]()
|
||||
else:
|
||||
# Other functions
|
||||
if mode == "settings":
|
||||
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||
elif mode in ("manualsync", "repair"):
|
||||
if utils.window('plex_online') != "true":
|
||||
if window('plex_online') != "true":
|
||||
# Server is not online, do not run the sync
|
||||
xbmcgui.Dialog().ok(
|
||||
Dialog().ok(
|
||||
"PlexKodiConnect",
|
||||
"Unable to run the sync, the add-on is not connected "
|
||||
"to a Plex server.")
|
||||
log.error("Not connected to a PMS.")
|
||||
else:
|
||||
if mode == 'repair':
|
||||
utils.window('plex_runLibScan', value="repair")
|
||||
window('plex_runLibScan', value="repair")
|
||||
log.info("Requesting repair lib sync")
|
||||
elif mode == 'manualsync':
|
||||
log.info("Requesting full library scan")
|
||||
utils.window('plex_runLibScan', value="full")
|
||||
|
||||
window('plex_runLibScan', value="full")
|
||||
elif mode == "texturecache":
|
||||
utils.window('plex_runLibScan', value='del_textures')
|
||||
window('plex_runLibScan', value='del_textures')
|
||||
else:
|
||||
entrypoint.doMainListing()
|
||||
|
||||
|
|
334
resources/lib/PKC_listitem.py
Normal file
334
resources/lib/PKC_listitem.py
Normal file
|
@ -0,0 +1,334 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
import logging
|
||||
|
||||
from xbmcgui import ListItem
|
||||
|
||||
###############################################################################
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
def convert_PKC_to_listitem(PKC_listitem):
|
||||
"""
|
||||
Insert a PKC_listitem and you will receive a valid XBMC listitem
|
||||
"""
|
||||
listitem = ListItem()
|
||||
for func, args in PKC_listitem.data.items():
|
||||
if isinstance(args, list):
|
||||
for arg in args:
|
||||
getattr(listitem, func)(*arg)
|
||||
elif isinstance(args, dict):
|
||||
for arg in args.items():
|
||||
getattr(listitem, func)(*arg)
|
||||
elif args is None:
|
||||
continue
|
||||
else:
|
||||
getattr(listitem, func)(args)
|
||||
return listitem
|
||||
|
||||
|
||||
class PKC_ListItem(object):
|
||||
"""
|
||||
Imitates xbmcgui.ListItem and its functions. Pass along PKC_Listitem().data
|
||||
when pickling!
|
||||
|
||||
WARNING: set/get path only via setPath and getPath! (not getProperty)
|
||||
"""
|
||||
def __init__(self, label=None, label2=None, path=None):
|
||||
self.data = {
|
||||
'addStreamInfo': [], # (type, values: dict { label: value })
|
||||
'setArt': [], # dict: { label: value }
|
||||
'setInfo': {}, # type: infoLabel (dict { label: value })
|
||||
'setLabel': label, # string
|
||||
'setLabel2': label2, # string
|
||||
'setPath': path, # string
|
||||
'setProperty': {}, # (key, value)
|
||||
'setSubtitles': [], # string
|
||||
}
|
||||
|
||||
def addContextMenuItems(self, items, replaceItems):
|
||||
"""
|
||||
Adds item(s) to the context menu for media lists.
|
||||
|
||||
items : list - [(label, action,)*] A list of tuples consisting of label
|
||||
and action pairs.
|
||||
- label : string or unicode - item's label.
|
||||
- action : string or unicode - any built-in function to perform.
|
||||
replaceItes : [opt] bool - True=only your items will show/False=your
|
||||
items will be amdded to context menu(Default).
|
||||
|
||||
List of functions - http://kodi.wiki/view/List_of_Built_In_Functions
|
||||
|
||||
*Note, You can use the above as keywords for arguments and skip
|
||||
certain optional arguments.
|
||||
|
||||
Once you use a keyword, all following arguments require the keyword.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def addStreamInfo(self, type, values):
|
||||
"""
|
||||
Add a stream with details.
|
||||
type : string - type of stream(video/audio/subtitle).
|
||||
values : dictionary - pairs of { label: value }.
|
||||
|
||||
- Video Values:
|
||||
- codec : string (h264)
|
||||
- aspect : float (1.78)
|
||||
- width : integer (1280)
|
||||
- height : integer (720)
|
||||
- duration : integer (seconds)
|
||||
- Audio Values:
|
||||
- codec : string (dts)
|
||||
- language : string (en)
|
||||
- channels : integer (2)
|
||||
- Subtitle Values:
|
||||
- language : string (en)
|
||||
"""
|
||||
self.data['addStreamInfo'].append((type, values))
|
||||
|
||||
def getLabel(self):
|
||||
"""
|
||||
Returns the listitem label
|
||||
"""
|
||||
return self.data['setLabel']
|
||||
|
||||
def getLabel2(self):
|
||||
"""
|
||||
Returns the listitem label.
|
||||
"""
|
||||
return self.data['setLabel2']
|
||||
|
||||
def getMusicInfoTag(self):
|
||||
"""
|
||||
returns the MusicInfoTag for this item.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getProperty(self, key):
|
||||
"""
|
||||
Returns a listitem property as a string, similar to an infolabel.
|
||||
key : string - property name.
|
||||
*Note, Key is NOT case sensitive.
|
||||
|
||||
You can use the above as keywords for arguments and skip certain
|
||||
optional arguments.
|
||||
|
||||
Once you use a keyword, all following arguments require the keyword.
|
||||
"""
|
||||
return self.data['setProperty'].get(key)
|
||||
|
||||
def getVideoInfoTag(self):
|
||||
"""
|
||||
returns the VideoInfoTag for this item
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getdescription(self):
|
||||
"""
|
||||
Returns the description of this PlayListItem
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getduration(self):
|
||||
"""
|
||||
Returns the duration of this PlayListItem
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getfilename(self):
|
||||
"""
|
||||
Returns the filename of this PlayListItem.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def isSelected(self):
|
||||
"""
|
||||
Returns the listitem's selected status
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def select(self):
|
||||
"""
|
||||
Sets the listitem's selected status.
|
||||
selected : bool - True=selected/False=not selected
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def setArt(self, values):
|
||||
"""
|
||||
Sets the listitem's art
|
||||
values : dictionary - pairs of { label: value }.
|
||||
|
||||
Some default art values (any string possible):
|
||||
- thumb : string - image filename
|
||||
- poster : string - image filename
|
||||
- banner : string - image filename
|
||||
- fanart : string - image filename
|
||||
- clearart : string - image filename
|
||||
- clearlogo : string - image filename
|
||||
- landscape : string - image filename
|
||||
- icon : string - image filename
|
||||
"""
|
||||
self.data['setArt'].append(values)
|
||||
|
||||
def setContentLookup(self, enable):
|
||||
"""
|
||||
Enable or disable content lookup for item.
|
||||
|
||||
If disabled, HEAD requests to e.g determine mime type will not be sent.
|
||||
|
||||
enable : bool
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def setInfo(self, type, infoLabels):
|
||||
"""
|
||||
type : string - type of media(video/music/pictures).
|
||||
|
||||
infoLabels : dictionary - pairs of { label: value }. *Note, To set
|
||||
pictures exif info, prepend 'exif:' to the label. Exif values must be
|
||||
passed as strings, separate value pairs with a comma. (eg.
|
||||
{'exif:resolution': '720,480'}
|
||||
|
||||
See CPictureInfoTag::TranslateString in PictureInfoTag.cpp for valid
|
||||
strings. You can use the above as keywords for arguments and skip
|
||||
certain optional arguments.
|
||||
|
||||
Once you use a keyword, all following arguments require the keyword.
|
||||
|
||||
- General Values that apply to all types:
|
||||
- count : integer (12) - can be used to store an id for later, or
|
||||
for sorting purposes
|
||||
- size : long (1024) - size in bytes
|
||||
- date : string (d.m.Y / 01.01.2009) - file date
|
||||
|
||||
- Video Values:
|
||||
- genre : string (Comedy)
|
||||
- year : integer (2009)
|
||||
- episode : integer (4)
|
||||
- season : integer (1)
|
||||
- top250 : integer (192)
|
||||
- tracknumber : integer (3)
|
||||
- rating : float (6.4) - range is 0..10
|
||||
- userrating : integer (9) - range is 1..10
|
||||
- watched : depreciated - use playcount instead
|
||||
- playcount : integer (2) - number of times this item has been
|
||||
played
|
||||
- overlay : integer (2) - range is 0..8. See GUIListItem.h for
|
||||
values
|
||||
- cast : list (["Michal C. Hall","Jennifer Carpenter"]) - if
|
||||
provided a list of tuples cast will be interpreted as castandrole
|
||||
- castandrole : list of tuples ([("Michael C.
|
||||
Hall","Dexter"),("Jennifer Carpenter","Debra")])
|
||||
- director : string (Dagur Kari)
|
||||
- mpaa : string (PG-13)
|
||||
- plot : string (Long Description)
|
||||
- plotoutline : string (Short Description)
|
||||
- title : string (Big Fan)
|
||||
- originaltitle : string (Big Fan)
|
||||
- sorttitle : string (Big Fan)
|
||||
- duration : integer (245) - duration in seconds
|
||||
- studio : string (Warner Bros.)
|
||||
- tagline : string (An awesome movie) - short description of movie
|
||||
- writer : string (Robert D. Siegel)
|
||||
- tvshowtitle : string (Heroes)
|
||||
- premiered : string (2005-03-04)
|
||||
- status : string (Continuing) - status of a TVshow
|
||||
- code : string (tt0110293) - IMDb code
|
||||
- aired : string (2008-12-07)
|
||||
- credits : string (Andy Kaufman) - writing credits
|
||||
- lastplayed : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
|
||||
- album : string (The Joshua Tree)
|
||||
- artist : list (['U2'])
|
||||
- votes : string (12345 votes)
|
||||
- trailer : string (/home/user/trailer.avi)
|
||||
- dateadded : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
|
||||
- mediatype : string - "video", "movie", "tvshow", "season",
|
||||
"episode" or "musicvideo"
|
||||
|
||||
- Music Values:
|
||||
- tracknumber : integer (8)
|
||||
- discnumber : integer (2)
|
||||
- duration : integer (245) - duration in seconds
|
||||
- year : integer (1998)
|
||||
- genre : string (Rock)
|
||||
- album : string (Pulse)
|
||||
- artist : string (Muse)
|
||||
- title : string (American Pie)
|
||||
- rating : string (3) - single character between 0 and 5
|
||||
- lyrics : string (On a dark desert highway...)
|
||||
- playcount : integer (2) - number of times this item has been
|
||||
played
|
||||
- lastplayed : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
|
||||
|
||||
- Picture Values:
|
||||
- title : string (In the last summer-1)
|
||||
- picturepath : string (/home/username/pictures/img001.jpg)
|
||||
- exif : string (See CPictureInfoTag::TranslateString in
|
||||
PictureInfoTag.cpp for valid strings)
|
||||
"""
|
||||
self.data['setInfo'][type] = infoLabels
|
||||
|
||||
def setLabel(self, label):
|
||||
"""
|
||||
Sets the listitem's label.
|
||||
label : string or unicode - text string.
|
||||
"""
|
||||
self.data['setLabel'] = label
|
||||
|
||||
def setLabel2(self, label):
|
||||
"""
|
||||
Sets the listitem's label2.
|
||||
label : string or unicode - text string.
|
||||
"""
|
||||
self.data['setLabel2'] = label
|
||||
|
||||
def setMimeType(self, mimetype):
|
||||
"""
|
||||
Sets the listitem's mimetype if known.
|
||||
mimetype : string or unicode - mimetype.
|
||||
|
||||
If known prehand, this can (but does not have to) avoid HEAD requests
|
||||
being sent to HTTP servers to figure out file type.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def setPath(self, path):
|
||||
"""
|
||||
Sets the listitem's path.
|
||||
path : string or unicode - path, activated when item is clicked.
|
||||
|
||||
*Note, You can use the above as keywords for arguments.
|
||||
"""
|
||||
self.data['setPath'] = path
|
||||
|
||||
def setProperty(self, key, value):
|
||||
"""
|
||||
Sets a listitem property, similar to an infolabel.
|
||||
key : string - property name.
|
||||
value : string or unicode - value of property.
|
||||
*Note, Key is NOT case sensitive.
|
||||
|
||||
You can use the above as keywords for arguments and skip certain
|
||||
optional arguments. Once you use a keyword, all following arguments
|
||||
require the keyword.
|
||||
|
||||
Some of these are treated internally by XBMC, such as the
|
||||
'StartOffset' property, which is the offset in seconds at which to
|
||||
start playback of an item. Others may be used in the skin to add extra
|
||||
information, such as 'WatchedCount' for tvshow items
|
||||
"""
|
||||
self.data['setProperty'][key] = value
|
||||
|
||||
def setSubtitles(self, subtitles):
|
||||
"""
|
||||
Sets subtitles for this listitem. Pass in a list of filepaths
|
||||
|
||||
example:
|
||||
- listitem.setSubtitles(['special://temp/example.srt',
|
||||
'http://example.com/example.srt' ])
|
||||
"""
|
||||
self.data['setSubtitles'].extend(([subtitles],))
|
|
@ -50,8 +50,9 @@ import downloadutils
|
|||
from utils import window, settings, language as lang, tryDecode, tryEncode, \
|
||||
DateToKodi, KODILANGUAGE
|
||||
from PlexFunctions import PLEX_TO_KODI_TIMEFACTOR, PMSHttpsEnabled, \
|
||||
REMAP_TYPE_FROM_PLEXTYPE
|
||||
import embydb_functions as embydb
|
||||
REMAP_TYPE_FROM_PLEXTYPE, PLEX_TYPE_MOVIE, PLEX_TYPE_SHOW, \
|
||||
PLEX_TYPE_EPISODE
|
||||
import plexdb_functions as plexdb
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -1646,7 +1647,7 @@ class API():
|
|||
|
||||
If not found, empty str is returned
|
||||
"""
|
||||
return self.item.attrib.get('playQueueItemID', '')
|
||||
return self.item.attrib.get('playQueueItemID')
|
||||
|
||||
def getDataFromPartOrMedia(self, key):
|
||||
"""
|
||||
|
@ -1915,9 +1916,9 @@ class API():
|
|||
# Return the saved Plex id's, if applicable
|
||||
# Always seek collection's ids since not provided by PMS
|
||||
if collection is False:
|
||||
if media_type == 'movie':
|
||||
if media_type == PLEX_TYPE_MOVIE:
|
||||
mediaId = self.getProvider('imdb')
|
||||
elif media_type == 'show':
|
||||
elif media_type == PLEX_TYPE_SHOW:
|
||||
mediaId = self.getProvider('tvdb')
|
||||
if mediaId is not None:
|
||||
return mediaId
|
||||
|
@ -1927,7 +1928,7 @@ class API():
|
|||
log.info('Start movie set/collection lookup on themoviedb')
|
||||
|
||||
apiKey = settings('themoviedbAPIKey')
|
||||
if media_type == 'show':
|
||||
if media_type == PLEX_TYPE_SHOW:
|
||||
media_type = 'tv'
|
||||
title = item.get('title', '')
|
||||
# if the title has the year in remove it as tmdb cannot deal with it...
|
||||
|
@ -2305,10 +2306,10 @@ class API():
|
|||
kodiindex = 0
|
||||
for stream in mediastreams:
|
||||
index = stream.attrib['id']
|
||||
# Since Emby returns all possible tracks together, have to pull
|
||||
# Since plex returns all possible tracks together, have to pull
|
||||
# only external subtitles.
|
||||
key = stream.attrib.get('key')
|
||||
# IsTextSubtitleStream if true, is available to download from emby.
|
||||
# IsTextSubtitleStream if true, is available to download from plex.
|
||||
if stream.attrib.get('streamType') == "3" and key:
|
||||
# Direct stream
|
||||
url = ("%s%s" % (self.server, key))
|
||||
|
@ -2318,7 +2319,7 @@ class API():
|
|||
externalsubs.append(url)
|
||||
kodiindex += 1
|
||||
mapping = json.dumps(mapping)
|
||||
window('emby_%s.indexMapping' % playurl, value=mapping)
|
||||
window('plex_%s.indexMapping' % playurl, value=mapping)
|
||||
log.info('Found external subs: %s' % externalsubs)
|
||||
return externalsubs
|
||||
|
||||
|
@ -2393,7 +2394,7 @@ class API():
|
|||
# listItem.setProperty('isPlayable', 'true')
|
||||
# listItem.setProperty('isFolder', 'true')
|
||||
# Further stuff
|
||||
listItem.setIconImage('DefaultPicture.png')
|
||||
listItem.setArt({'icon': 'DefaultPicture.png'})
|
||||
return listItem
|
||||
|
||||
def _createVideoListItem(self,
|
||||
|
@ -2456,21 +2457,21 @@ class API():
|
|||
"s%.2de%.2d" % (season, episode))
|
||||
if appendSxxExx is True:
|
||||
title = "S%.2dE%.2d - %s" % (season, episode, title)
|
||||
listItem.setIconImage('DefaultTVShows.png')
|
||||
listItem.setArt({'icon': 'DefaultTVShows.png'})
|
||||
if appendShowTitle is True:
|
||||
title = "%s - %s " % (show, title)
|
||||
elif typus == "movie":
|
||||
listItem.setIconImage('DefaultMovies.png')
|
||||
listItem.setArt({'icon': 'DefaultMovies.png'})
|
||||
else:
|
||||
# E.g. clips, trailers, ...
|
||||
listItem.setIconImage('DefaultVideo.png')
|
||||
listItem.setArt({'icon': 'DefaultVideo.png'})
|
||||
|
||||
plexId = self.getRatingKey()
|
||||
listItem.setProperty('plexid', plexId)
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
try:
|
||||
listItem.setProperty('dbid',
|
||||
str(emby_db.getItem_byId(plexId)[0]))
|
||||
str(plex_db.getItem_byId(plexId)[0]))
|
||||
except TypeError:
|
||||
pass
|
||||
# Expensive operation
|
||||
|
@ -2563,3 +2564,68 @@ class API():
|
|||
line1=lang(39031) + url,
|
||||
line2=lang(39032))
|
||||
return resp
|
||||
|
||||
def set_listitem_artwork(self, listitem):
|
||||
"""
|
||||
Set all artwork to the listitem
|
||||
"""
|
||||
allartwork = self.getAllArtwork(parentInfo=True)
|
||||
arttypes = {
|
||||
'poster': "Primary",
|
||||
'tvshow.poster': "Thumb",
|
||||
'clearart': "Art",
|
||||
'tvshow.clearart': "Art",
|
||||
'clearart': "Primary",
|
||||
'tvshow.clearart': "Primary",
|
||||
'clearlogo': "Logo",
|
||||
'tvshow.clearlogo': "Logo",
|
||||
'discart': "Disc",
|
||||
'fanart_image': "Backdrop",
|
||||
'landscape': "Backdrop",
|
||||
"banner": "Banner"
|
||||
}
|
||||
for arttype in arttypes:
|
||||
art = arttypes[arttype]
|
||||
if art == "Backdrop":
|
||||
try:
|
||||
# Backdrop is a list, grab the first backdrop
|
||||
self._set_listitem_artprop(listitem,
|
||||
arttype,
|
||||
allartwork[art][0])
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
self._set_listitem_artprop(listitem, arttype, allartwork[art])
|
||||
|
||||
def _set_listitem_artprop(self, listitem, arttype, path):
|
||||
if arttype in (
|
||||
'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
|
||||
'medium_landscape', 'medium_poster', 'small_fanartimage',
|
||||
'medium_fanartimage', 'fanart_noindicators'):
|
||||
listitem.setProperty(arttype, path)
|
||||
else:
|
||||
listitem.setArt({arttype: path})
|
||||
|
||||
def set_playback_win_props(self, playurl, listitem):
|
||||
"""
|
||||
Set all properties necessary for plugin path playback for listitem
|
||||
"""
|
||||
itemtype = self.getType()
|
||||
userdata = self.getUserData()
|
||||
|
||||
plexitem = "plex_%s" % playurl
|
||||
window('%s.runtime' % plexitem, value=str(userdata['Runtime']))
|
||||
window('%s.type' % plexitem, value=itemtype)
|
||||
window('%s.itemid' % plexitem, value=self.getRatingKey())
|
||||
window('%s.playcount' % plexitem, value=str(userdata['PlayCount']))
|
||||
|
||||
if itemtype == PLEX_TYPE_EPISODE:
|
||||
window('%s.refreshid' % plexitem, value=self.getParentRatingKey())
|
||||
else:
|
||||
window('%s.refreshid' % plexitem, value=self.getRatingKey())
|
||||
|
||||
# Append external subtitles to stream
|
||||
playmethod = window('%s.playmethod' % plexitem)
|
||||
if playmethod in ("DirectStream", "DirectPlay"):
|
||||
subtitles = self.externalSubs(playurl)
|
||||
listitem.setSubtitles(subtitles)
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import threading
|
||||
import traceback
|
||||
import socket
|
||||
from threading import Thread
|
||||
import Queue
|
||||
from socket import SHUT_RDWR
|
||||
|
||||
import xbmc
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import settings, ThreadMethodsAdditionalSuspend, ThreadMethods
|
||||
from plexbmchelper import listener, plexgdm, subscribers, functions, \
|
||||
httppersist, plexsettings
|
||||
from PlexFunctions import ParseContainerKey, GetPlayQueue, \
|
||||
ConvertPlexToKodiTime
|
||||
import playlist
|
||||
from PlexFunctions import ParseContainerKey, KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE
|
||||
import player
|
||||
from entrypoint import Plex_Node
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -24,24 +22,23 @@ log = logging.getLogger("PLEX."+__name__)
|
|||
|
||||
@ThreadMethodsAdditionalSuspend('plex_serverStatus')
|
||||
@ThreadMethods
|
||||
class PlexCompanion(threading.Thread):
|
||||
class PlexCompanion(Thread):
|
||||
"""
|
||||
Initialize with a Queue for callbacks
|
||||
"""
|
||||
def __init__(self):
|
||||
def __init__(self, callback=None):
|
||||
log.info("----===## Starting PlexCompanion ##===----")
|
||||
if callback is not None:
|
||||
self.mgr = callback
|
||||
self.settings = plexsettings.getSettings()
|
||||
# Start GDM for server/client discovery
|
||||
self.client = plexgdm.plexgdm()
|
||||
self.client.clientDetails(self.settings)
|
||||
log.debug("Registration string is: %s "
|
||||
% self.client.getClientDetails())
|
||||
# Initialize playlist/queue stuff
|
||||
self.playlist = playlist.Playlist('video')
|
||||
# kodi player instance
|
||||
self.player = player.Player()
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
Thread.__init__(self)
|
||||
|
||||
def _getStartItem(self, string):
|
||||
"""
|
||||
|
@ -62,62 +59,48 @@ class PlexCompanion(threading.Thread):
|
|||
|
||||
def processTasks(self, task):
|
||||
"""
|
||||
Processes tasks picked up e.g. by Companion listener
|
||||
|
||||
task = {
|
||||
'action': 'playlist'
|
||||
'data': as received from Plex companion
|
||||
}
|
||||
Processes tasks picked up e.g. by Companion listener, e.g.
|
||||
{'action': 'playlist',
|
||||
'data': {'address': 'xyz.plex.direct',
|
||||
'commandID': '7',
|
||||
'containerKey': '/playQueues/6669?own=1&repeat=0&window=200',
|
||||
'key': '/library/metadata/220493',
|
||||
'machineIdentifier': 'xyz',
|
||||
'offset': '0',
|
||||
'port': '32400',
|
||||
'protocol': 'https',
|
||||
'token': 'transient-cd2527d1-0484-48e0-a5f7-f5caa7d591bd',
|
||||
'type': 'video'}}
|
||||
"""
|
||||
log.debug('Processing: %s' % task)
|
||||
data = task['data']
|
||||
|
||||
if task['action'] == 'playlist':
|
||||
if (task['action'] == 'playlist' and
|
||||
data.get('address') == 'node.plexapp.com'):
|
||||
# E.g. watch later initiated by Companion
|
||||
thread = Thread(target=Plex_Node,
|
||||
args=('{server}%s' % data.get('key'),
|
||||
data.get('offset'),
|
||||
data.get('type'),
|
||||
True),)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
elif task['action'] == 'playlist':
|
||||
# Get the playqueue ID
|
||||
try:
|
||||
_, queueId, query = ParseContainerKey(data['containerKey'])
|
||||
_, ID, query = ParseContainerKey(data['containerKey'])
|
||||
except Exception as e:
|
||||
log.error('Exception while processing: %s' % e)
|
||||
import traceback
|
||||
log.error("Traceback:\n%s" % traceback.format_exc())
|
||||
return
|
||||
if self.playlist is not None:
|
||||
if self.playlist.Typus() != data.get('type'):
|
||||
log.debug('Switching to Kodi playlist of type %s'
|
||||
% data.get('type'))
|
||||
self.playlist = None
|
||||
if self.playlist is None:
|
||||
if data.get('type') == 'music':
|
||||
self.playlist = playlist.Playlist('music')
|
||||
else:
|
||||
self.playlist = playlist.Playlist('video')
|
||||
if queueId != self.playlist.QueueId():
|
||||
log.info('New playlist received, updating!')
|
||||
xml = GetPlayQueue(queueId)
|
||||
if xml in (None, 401):
|
||||
log.error('Could not download Plex playlist.')
|
||||
return
|
||||
# Clear existing playlist on the Kodi side
|
||||
self.playlist.clear()
|
||||
# Set new values
|
||||
self.playlist.QueueId(queueId)
|
||||
self.playlist.PlayQueueVersion(int(
|
||||
xml.attrib.get('playQueueVersion')))
|
||||
self.playlist.Guid(xml.attrib.get('guid'))
|
||||
items = []
|
||||
for item in xml:
|
||||
items.append({
|
||||
'playQueueItemID': item.get('playQueueItemID'),
|
||||
'plexId': item.get('ratingKey'),
|
||||
'kodiId': None})
|
||||
self.playlist.playAll(
|
||||
items,
|
||||
startitem=self._getStartItem(data.get('key', '')),
|
||||
offset=ConvertPlexToKodiTime(data.get('offset', 0)))
|
||||
log.info('Initiated playlist no %s with version %s'
|
||||
% (self.playlist.QueueId(),
|
||||
self.playlist.PlayQueueVersion()))
|
||||
else:
|
||||
log.error('This has never happened before!')
|
||||
playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
||||
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
|
||||
self.mgr.playqueue.update_playqueue_from_PMS(
|
||||
playqueue,
|
||||
ID,
|
||||
repeat=query.get('repeat'),
|
||||
offset=data.get('offset'))
|
||||
|
||||
def run(self):
|
||||
httpd = False
|
||||
|
@ -130,7 +113,7 @@ class PlexCompanion(threading.Thread):
|
|||
requestMgr = httppersist.RequestMgr()
|
||||
jsonClass = functions.jsonClass(requestMgr, self.settings)
|
||||
subscriptionManager = subscribers.SubscriptionManager(
|
||||
jsonClass, requestMgr, self.player, self.playlist)
|
||||
jsonClass, requestMgr, self.player, self.mgr)
|
||||
|
||||
queue = Queue.Queue(maxsize=100)
|
||||
|
||||
|
@ -151,9 +134,10 @@ class PlexCompanion(threading.Thread):
|
|||
break
|
||||
except:
|
||||
log.error("Unable to start PlexCompanion. Traceback:")
|
||||
import traceback
|
||||
log.error(traceback.print_exc())
|
||||
|
||||
xbmc.sleep(3000)
|
||||
sleep(3000)
|
||||
|
||||
if start_count == 3:
|
||||
log.error("Error: Unable to start web helper.")
|
||||
|
@ -168,7 +152,7 @@ class PlexCompanion(threading.Thread):
|
|||
|
||||
message_count = 0
|
||||
if httpd:
|
||||
t = threading.Thread(target=httpd.handle_request)
|
||||
t = Thread(target=httpd.handle_request)
|
||||
|
||||
while not threadStopped():
|
||||
# If we are not authorized, sleep
|
||||
|
@ -177,13 +161,13 @@ class PlexCompanion(threading.Thread):
|
|||
while threadSuspended():
|
||||
if threadStopped():
|
||||
break
|
||||
xbmc.sleep(1000)
|
||||
sleep(1000)
|
||||
try:
|
||||
message_count += 1
|
||||
if httpd:
|
||||
if not t.isAlive():
|
||||
# Use threads cause the method will stall
|
||||
t = threading.Thread(target=httpd.handle_request)
|
||||
t = Thread(target=httpd.handle_request)
|
||||
t.start()
|
||||
|
||||
if message_count == 3000:
|
||||
|
@ -202,6 +186,7 @@ class PlexCompanion(threading.Thread):
|
|||
message_count = 0
|
||||
except:
|
||||
log.warn("Error in loop, continuing anyway. Traceback:")
|
||||
import traceback
|
||||
log.warn(traceback.format_exc())
|
||||
# See if there's anything we need to process
|
||||
try:
|
||||
|
@ -214,12 +199,12 @@ class PlexCompanion(threading.Thread):
|
|||
queue.task_done()
|
||||
# Don't sleep
|
||||
continue
|
||||
xbmc.sleep(20)
|
||||
sleep(20)
|
||||
|
||||
client.stop_all()
|
||||
if httpd:
|
||||
try:
|
||||
httpd.socket.shutdown(socket.SHUT_RDWR)
|
||||
httpd.socket.shutdown(SHUT_RDWR)
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
|
|
|
@ -20,46 +20,118 @@ addonName = 'PlexKodiConnect'
|
|||
# Multiply Plex time by this factor to receive Kodi time
|
||||
PLEX_TO_KODI_TIMEFACTOR = 1.0 / 1000.0
|
||||
|
||||
# Possible output of Kodi's ListItem.DBTYPE for all video items
|
||||
|
||||
# All the Plex types as communicated in the PMS xml replies
|
||||
PLEX_TYPE_VIDEO = 'video'
|
||||
PLEX_TYPE_MOVIE = 'movie'
|
||||
PLEX_TYPE_CLIP = 'clip' # e.g. trailers
|
||||
|
||||
PLEX_TYPE_EPISODE = 'episode'
|
||||
PLEX_TYPE_SEASON = 'season'
|
||||
PLEX_TYPE_SHOW = 'show'
|
||||
|
||||
PLEX_TYPE_AUDIO = 'music'
|
||||
PLEX_TYPE_SONG = 'track'
|
||||
PLEX_TYPE_ALBUM = 'album'
|
||||
PLEX_TYPE_ARTIST = 'artist'
|
||||
|
||||
PLEX_TYPE_PHOTO = 'photo'
|
||||
|
||||
|
||||
# All the Kodi types as e.g. used in the JSON API
|
||||
KODI_TYPE_VIDEO = 'video'
|
||||
KODI_TYPE_MOVIE = 'movie'
|
||||
KODI_TYPE_SET = 'set' # for movie sets of several movies
|
||||
KODI_TYPE_CLIP = 'clip' # e.g. trailers
|
||||
|
||||
KODI_TYPE_EPISODE = 'episode'
|
||||
KODI_TYPE_SEASON = 'season'
|
||||
KODI_TYPE_SHOW = 'tvshow'
|
||||
|
||||
KODI_TYPE_AUDIO = 'audio'
|
||||
KODI_TYPE_SONG = 'song'
|
||||
KODI_TYPE_ALBUM = 'album'
|
||||
KODI_TYPE_ARTIST = 'artist'
|
||||
|
||||
KODI_TYPE_PHOTO = 'photo'
|
||||
|
||||
|
||||
# Translation tables
|
||||
|
||||
KODI_VIDEOTYPES = (
|
||||
'video',
|
||||
'movie',
|
||||
'set',
|
||||
'tvshow',
|
||||
'season',
|
||||
'episode',
|
||||
'musicvideo'
|
||||
KODI_TYPE_VIDEO,
|
||||
KODI_TYPE_MOVIE,
|
||||
KODI_TYPE_SHOW,
|
||||
KODI_TYPE_SEASON,
|
||||
KODI_TYPE_EPISODE,
|
||||
KODI_TYPE_SET
|
||||
)
|
||||
|
||||
# Possible output of Kodi's ListItem.DBTYPE for all audio items
|
||||
KODI_AUDIOTYPES = (
|
||||
'music',
|
||||
'song',
|
||||
'album',
|
||||
'artist'
|
||||
KODI_TYPE_SONG,
|
||||
KODI_TYPE_ALBUM,
|
||||
KODI_TYPE_ARTIST,
|
||||
)
|
||||
|
||||
ITEMTYPE_FROM_PLEXTYPE = {
|
||||
'movie': 'Movies',
|
||||
'season': 'TVShows',
|
||||
'episode': 'TVShows',
|
||||
'show': 'TVShows',
|
||||
'artist': 'Music',
|
||||
'album': 'Music',
|
||||
'track': 'Music',
|
||||
'song': 'Music'
|
||||
PLEX_TYPE_MOVIE: 'Movies',
|
||||
PLEX_TYPE_SEASON: 'TVShows',
|
||||
KODI_TYPE_EPISODE: 'TVShows',
|
||||
PLEX_TYPE_SHOW: 'TVShows',
|
||||
PLEX_TYPE_ARTIST: 'Music',
|
||||
PLEX_TYPE_ALBUM: 'Music',
|
||||
PLEX_TYPE_SONG: 'Music',
|
||||
}
|
||||
|
||||
ITEMTYPE_FROM_KODITYPE = {
|
||||
KODI_TYPE_MOVIE: 'Movies',
|
||||
KODI_TYPE_SEASON: 'TVShows',
|
||||
KODI_TYPE_EPISODE: 'TVShows',
|
||||
KODI_TYPE_SHOW: 'TVShows',
|
||||
KODI_TYPE_ARTIST: 'Music',
|
||||
KODI_TYPE_ALBUM: 'Music',
|
||||
KODI_TYPE_SONG: 'Music',
|
||||
}
|
||||
|
||||
KODITYPE_FROM_PLEXTYPE = {
|
||||
'movie': 'movie',
|
||||
'episode': 'episode',
|
||||
'track': 'song',
|
||||
'artist': 'artist',
|
||||
'album': 'album',
|
||||
PLEX_TYPE_MOVIE: KODI_TYPE_MOVIE,
|
||||
PLEX_TYPE_EPISODE: KODI_TYPE_EPISODE,
|
||||
PLEX_TYPE_SEASON: KODI_TYPE_SEASON,
|
||||
PLEX_TYPE_SHOW: KODI_TYPE_SHOW,
|
||||
PLEX_TYPE_SONG: KODI_TYPE_SONG,
|
||||
PLEX_TYPE_ARTIST: KODI_TYPE_ARTIST,
|
||||
PLEX_TYPE_ALBUM: KODI_TYPE_ALBUM,
|
||||
PLEX_TYPE_PHOTO: KODI_TYPE_PHOTO,
|
||||
'XXXXXX': 'musicvideo',
|
||||
'XXXXXXX': 'genre'
|
||||
}
|
||||
|
||||
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE = {
|
||||
PLEX_TYPE_VIDEO: KODI_TYPE_VIDEO,
|
||||
PLEX_TYPE_MOVIE: KODI_TYPE_VIDEO,
|
||||
PLEX_TYPE_EPISODE: KODI_TYPE_VIDEO,
|
||||
PLEX_TYPE_SEASON: KODI_TYPE_VIDEO,
|
||||
PLEX_TYPE_SHOW: KODI_TYPE_VIDEO,
|
||||
PLEX_TYPE_CLIP: KODI_TYPE_VIDEO,
|
||||
PLEX_TYPE_ARTIST: KODI_TYPE_AUDIO,
|
||||
PLEX_TYPE_ALBUM: KODI_TYPE_AUDIO,
|
||||
PLEX_TYPE_SONG: KODI_TYPE_AUDIO,
|
||||
PLEX_TYPE_AUDIO: KODI_TYPE_AUDIO
|
||||
}
|
||||
|
||||
|
||||
REMAP_TYPE_FROM_PLEXTYPE = {
|
||||
PLEX_TYPE_MOVIE: 'movie',
|
||||
PLEX_TYPE_CLIP: 'clip',
|
||||
PLEX_TYPE_SHOW: 'tv',
|
||||
PLEX_TYPE_SEASON: 'tv',
|
||||
PLEX_TYPE_EPISODE: 'tv',
|
||||
PLEX_TYPE_ARTIST: 'music',
|
||||
PLEX_TYPE_ALBUM: 'music',
|
||||
PLEX_TYPE_SONG: 'music',
|
||||
PLEX_TYPE_PHOTO: 'photo'
|
||||
}
|
||||
|
||||
|
||||
REMAP_TYPE_FROM_PLEXTYPE = {
|
||||
'movie': 'movie',
|
||||
|
@ -159,22 +231,6 @@ def SelectStreams(url, args):
|
|||
url + '?' + urlencode(args), action_type='PUT')
|
||||
|
||||
|
||||
def GetPlayQueue(playQueueID):
|
||||
"""
|
||||
Fetches the PMS playqueue with the playQueueID as an XML
|
||||
|
||||
Returns None if something went wrong
|
||||
"""
|
||||
url = "{server}/playQueues/%s" % playQueueID
|
||||
args = {'Accept': 'application/xml'}
|
||||
xml = downloadutils.DownloadUtils().downloadUrl(url, headerOptions=args)
|
||||
try:
|
||||
xml.attrib['playQueueID']
|
||||
except (AttributeError, KeyError):
|
||||
return None
|
||||
return xml
|
||||
|
||||
|
||||
def GetPlexMetadata(key):
|
||||
"""
|
||||
Returns raw API metadata for key as an etree XML.
|
||||
|
@ -388,23 +444,22 @@ def GetPlexCollections(mediatype):
|
|||
return collections
|
||||
|
||||
|
||||
def GetPlexPlaylist(itemid, librarySectionUUID, mediatype='movie'):
|
||||
def GetPlexPlaylist(itemid, librarySectionUUID, mediatype='movie',
|
||||
trailers=False):
|
||||
"""
|
||||
Returns raw API metadata XML dump for a playlist with e.g. trailers.
|
||||
"""
|
||||
trailerNumber = settings('trailerNumber')
|
||||
if not trailerNumber:
|
||||
trailerNumber = '3'
|
||||
url = "{server}/playQueues"
|
||||
args = {
|
||||
'type': mediatype,
|
||||
'uri': ('library://' + librarySectionUUID +
|
||||
'/item/%2Flibrary%2Fmetadata%2F' + itemid),
|
||||
'includeChapters': '1',
|
||||
'extrasPrefixCount': trailerNumber,
|
||||
'shuffle': '0',
|
||||
'repeat': '0'
|
||||
}
|
||||
if trailers is True:
|
||||
args['extrasPrefixCount'] = settings('trailerNumber')
|
||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||
url + '?' + urlencode(args), action_type="POST")
|
||||
try:
|
||||
|
|
|
@ -281,7 +281,7 @@ class Artwork():
|
|||
def cacheTexture(self, url):
|
||||
# Cache a single image url to the texture cache
|
||||
if url and self.enableTextureCache:
|
||||
self.queue.put(double_urlencode(url))
|
||||
self.queue.put(double_urlencode(tryEncode(url)))
|
||||
|
||||
def addArtwork(self, artwork, kodiId, mediaType, cursor):
|
||||
# Kodi conversion table
|
||||
|
|
|
@ -49,7 +49,7 @@ class ClientInfo():
|
|||
'X-Plex-Product': self.getAddonName(),
|
||||
'X-Plex-Version': self.getVersion(),
|
||||
'X-Plex-Client-Identifier': self.getDeviceId(),
|
||||
'X-Plex-Provides': 'player',
|
||||
'X-Plex-Provides': 'client,controller,player',
|
||||
}
|
||||
|
||||
if window('pms_token'):
|
||||
|
|
|
@ -8,7 +8,7 @@ import xbmc
|
|||
import xbmcaddon
|
||||
|
||||
import PlexFunctions as PF
|
||||
import embydb_functions as embydb
|
||||
import plexdb_functions as plexdb
|
||||
from utils import window, settings, dialog, language as lang, kodiSQL
|
||||
from dialogs import context
|
||||
|
||||
|
@ -75,8 +75,8 @@ class ContextMenu(object):
|
|||
def _get_item_id(cls, kodi_id, item_type):
|
||||
item_id = xbmc.getInfoLabel('ListItem.Property(plexid)')
|
||||
if not item_id and kodi_id and item_type:
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
item = emby_db.getItem_byKodiId(kodi_id, item_type)
|
||||
with plexdb.Get_Plex_DB() as plexcursor:
|
||||
item = plexcursor.getItem_byKodiId(kodi_id, item_type)
|
||||
try:
|
||||
item_id = item[0]
|
||||
except TypeError:
|
||||
|
@ -140,8 +140,8 @@ class ContextMenu(object):
|
|||
elif selected == OPTIONS['PMS_Play']:
|
||||
self._PMS_play()
|
||||
|
||||
elif selected == OPTIONS['Refresh']:
|
||||
self.emby.refreshItem(self.item_id)
|
||||
# elif selected == OPTIONS['Refresh']:
|
||||
# self.emby.refreshItem(self.item_id)
|
||||
|
||||
# elif selected == OPTIONS['AddFav']:
|
||||
# self.emby.updateUserRating(self.item_id, favourite=True)
|
||||
|
@ -212,6 +212,6 @@ class ContextMenu(object):
|
|||
'mode': "play"
|
||||
}
|
||||
from urllib import urlencode
|
||||
handle = ("plugin://plugin.video.plexkodiconnect.movies?%s"
|
||||
handle = ("plugin://plugin.video.plexkodiconnect/movies?%s"
|
||||
% urlencode(params))
|
||||
xbmc.executebuiltin('RunPlugin(%s)' % handle)
|
||||
|
|
|
@ -67,7 +67,7 @@ class UsersConnect(xbmcgui.WindowXMLDialog):
|
|||
if self.kodi_version > 15:
|
||||
item.setArt({'Icon': user_image})
|
||||
else:
|
||||
item.setIconImage(user_image)
|
||||
item.setArt({'icon': user_image})
|
||||
|
||||
return item
|
||||
|
||||
|
|
|
@ -1,402 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
###############################################################################
|
||||
|
||||
from utils import kodiSQL
|
||||
import logging
|
||||
|
||||
###############################################################################
|
||||
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
class GetEmbyDB():
|
||||
"""
|
||||
Usage: with GetEmbyDB() as emby_db:
|
||||
do stuff with emby_db
|
||||
|
||||
On exiting "with" (no matter what), commits get automatically committed
|
||||
and the db gets closed
|
||||
"""
|
||||
def __enter__(self):
|
||||
self.embyconn = kodiSQL('emby')
|
||||
self.emby_db = Embydb_Functions(self.embyconn.cursor())
|
||||
return self.emby_db
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.embyconn.commit()
|
||||
self.embyconn.close()
|
||||
|
||||
|
||||
class Embydb_Functions():
|
||||
|
||||
def __init__(self, embycursor):
|
||||
|
||||
self.embycursor = embycursor
|
||||
|
||||
def getViews(self):
|
||||
|
||||
views = []
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_id",
|
||||
"FROM view"
|
||||
))
|
||||
self.embycursor.execute(query)
|
||||
rows = self.embycursor.fetchall()
|
||||
for row in rows:
|
||||
views.append(row[0])
|
||||
return views
|
||||
|
||||
def getAllViewInfo(self):
|
||||
|
||||
embycursor = self.embycursor
|
||||
views = []
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_id, view_name, media_type",
|
||||
"FROM view"
|
||||
))
|
||||
embycursor.execute(query)
|
||||
rows = embycursor.fetchall()
|
||||
for row in rows:
|
||||
views.append({'id': row[0],
|
||||
'name': row[1],
|
||||
'itemtype': row[2]})
|
||||
return views
|
||||
|
||||
def getView_byId(self, viewid):
|
||||
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_name, media_type, kodi_tagid",
|
||||
"FROM view",
|
||||
"WHERE view_id = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (viewid,))
|
||||
view = self.embycursor.fetchone()
|
||||
|
||||
return view
|
||||
|
||||
def getView_byType(self, mediatype):
|
||||
|
||||
views = []
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_id, view_name, media_type",
|
||||
"FROM view",
|
||||
"WHERE media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (mediatype,))
|
||||
rows = self.embycursor.fetchall()
|
||||
for row in rows:
|
||||
views.append({
|
||||
|
||||
'id': row[0],
|
||||
'name': row[1],
|
||||
'itemtype': row[2]
|
||||
})
|
||||
|
||||
return views
|
||||
|
||||
def getView_byName(self, tagname):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_id",
|
||||
"FROM view",
|
||||
"WHERE view_name = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (tagname,))
|
||||
try:
|
||||
view = self.embycursor.fetchone()[0]
|
||||
|
||||
except TypeError:
|
||||
view = None
|
||||
|
||||
return view
|
||||
|
||||
def addView(self, plexid, name, mediatype, tagid):
|
||||
|
||||
query = (
|
||||
'''
|
||||
INSERT INTO view(
|
||||
view_id, view_name, media_type, kodi_tagid)
|
||||
|
||||
VALUES (?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
self.embycursor.execute(query, (plexid, name, mediatype, tagid))
|
||||
|
||||
def updateView(self, name, tagid, mediafolderid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE view",
|
||||
"SET view_name = ?, kodi_tagid = ?",
|
||||
"WHERE view_id = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (name, tagid, mediafolderid))
|
||||
|
||||
def removeView(self, viewid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"DELETE FROM view",
|
||||
"WHERE view_id = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (viewid,))
|
||||
|
||||
def getItem_byFileId(self, fileId, kodiType):
|
||||
"""
|
||||
Returns the Plex itemId by using the Kodi fileId. VIDEO ONLY
|
||||
|
||||
kodiType: 'movie', 'episode', ...
|
||||
"""
|
||||
query = ' '.join((
|
||||
"SELECT emby_id",
|
||||
"FROM emby",
|
||||
"WHERE kodi_fileid = ? AND media_type = ?"
|
||||
))
|
||||
try:
|
||||
self.embycursor.execute(query, (fileId, kodiType))
|
||||
item = self.embycursor.fetchone()[0]
|
||||
return item
|
||||
except:
|
||||
return None
|
||||
|
||||
def getMusicItem_byFileId(self, fileId, kodiType):
|
||||
"""
|
||||
Returns the Plex itemId by using the Kodi fileId. MUSIC ONLY
|
||||
|
||||
kodiType: 'song'
|
||||
"""
|
||||
query = ' '.join((
|
||||
"SELECT emby_id",
|
||||
"FROM emby",
|
||||
"WHERE kodi_id = ? AND media_type = ?"
|
||||
))
|
||||
try:
|
||||
self.embycursor.execute(query, (fileId, kodiType))
|
||||
item = self.embycursor.fetchone()[0]
|
||||
return item
|
||||
except:
|
||||
return None
|
||||
|
||||
def getItem_byId(self, plexid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type",
|
||||
"FROM emby",
|
||||
"WHERE emby_id = ?"
|
||||
))
|
||||
try:
|
||||
self.embycursor.execute(query, (plexid,))
|
||||
item = self.embycursor.fetchone()
|
||||
return item
|
||||
except: return None
|
||||
|
||||
def getItem_byWildId(self, plexid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT kodi_id, media_type",
|
||||
"FROM emby",
|
||||
"WHERE emby_id LIKE ?"
|
||||
))
|
||||
self.embycursor.execute(query, (plexid+"%",))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getItem_byView(self, mediafolderid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT kodi_id",
|
||||
"FROM emby",
|
||||
"WHERE media_folder = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (mediafolderid,))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getPlexId(self, kodiid, mediatype):
|
||||
"""
|
||||
Returns the Plex ID usind the Kodiid. Result:
|
||||
(Plex Id, Parent's Plex Id)
|
||||
"""
|
||||
query = ' '.join((
|
||||
"SELECT emby_id, parent_id",
|
||||
"FROM emby",
|
||||
"WHERE kodi_id = ? AND media_type = ?"
|
||||
))
|
||||
try:
|
||||
self.embycursor.execute(query, (kodiid, mediatype))
|
||||
item = self.embycursor.fetchone()
|
||||
return item
|
||||
except:
|
||||
return None
|
||||
|
||||
def getItem_byKodiId(self, kodiid, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, parent_id",
|
||||
"FROM emby",
|
||||
"WHERE kodi_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (kodiid, mediatype,))
|
||||
return self.embycursor.fetchone()
|
||||
|
||||
def getItem_byParentId(self, parentid, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, kodi_id, kodi_fileid",
|
||||
"FROM emby",
|
||||
"WHERE parent_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (parentid, mediatype,))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getItemId_byParentId(self, parentid, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, kodi_id",
|
||||
"FROM emby",
|
||||
"WHERE parent_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (parentid, mediatype,))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getChecksum(self, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, checksum",
|
||||
"FROM emby",
|
||||
"WHERE emby_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (mediatype,))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getMediaType_byId(self, plexid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_type",
|
||||
"FROM emby",
|
||||
"WHERE emby_id = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (plexid,))
|
||||
try:
|
||||
itemtype = self.embycursor.fetchone()[0]
|
||||
|
||||
except TypeError:
|
||||
itemtype = None
|
||||
|
||||
return itemtype
|
||||
|
||||
def sortby_mediaType(self, itemids, unsorted=True):
|
||||
|
||||
sorted_items = {}
|
||||
|
||||
for itemid in itemids:
|
||||
mediatype = self.getMediaType_byId(itemid)
|
||||
if mediatype:
|
||||
sorted_items.setdefault(mediatype, []).append(itemid)
|
||||
elif unsorted:
|
||||
sorted_items.setdefault('Unsorted', []).append(itemid)
|
||||
|
||||
return sorted_items
|
||||
|
||||
def addReference(self, plexid, kodiid, embytype, mediatype, fileid=None, pathid=None,
|
||||
parentid=None, checksum=None, mediafolderid=None):
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO emby(
|
||||
emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id,
|
||||
checksum, media_folder)
|
||||
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
self.embycursor.execute(query, (plexid, kodiid, fileid, pathid, embytype, mediatype,
|
||||
parentid, checksum, mediafolderid))
|
||||
|
||||
def updateReference(self, plexid, checksum):
|
||||
|
||||
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
|
||||
self.embycursor.execute(query, (checksum, plexid))
|
||||
|
||||
def updateParentId(self, plexid, parent_kodiid):
|
||||
|
||||
query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?"
|
||||
self.embycursor.execute(query, (parent_kodiid, plexid))
|
||||
|
||||
def removeItems_byParentId(self, parent_kodiid, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"DELETE FROM emby",
|
||||
"WHERE parent_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (parent_kodiid, mediatype,))
|
||||
|
||||
def removeItem_byKodiId(self, kodiid, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"DELETE FROM emby",
|
||||
"WHERE kodi_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (kodiid, mediatype,))
|
||||
|
||||
def removeItem(self, plexid):
|
||||
|
||||
query = "DELETE FROM emby WHERE emby_id = ?"
|
||||
self.embycursor.execute(query, (plexid,))
|
||||
|
||||
def removeWildItem(self, plexid):
|
||||
|
||||
query = "DELETE FROM emby WHERE emby_id LIKE ?"
|
||||
self.embycursor.execute(query, (plexid+"%",))
|
||||
|
||||
def itemsByType(self, plextype):
|
||||
"""
|
||||
Returns a list of dictionaries for all Kodi DB items present for
|
||||
plextype. One dict is of the type
|
||||
|
||||
{
|
||||
'plexId': the Plex id
|
||||
'kodiId': the Kodi id
|
||||
'kodi_type': e.g. 'movie', 'tvshow'
|
||||
'plex_type': e.g. 'Movie', 'Series', the input plextype
|
||||
}
|
||||
"""
|
||||
query = ' '.join((
|
||||
"SELECT emby_id, kodi_id, media_type",
|
||||
"FROM emby",
|
||||
"WHERE emby_type = ?",
|
||||
))
|
||||
self.embycursor.execute(query, (plextype, ))
|
||||
result = []
|
||||
for row in self.embycursor.fetchall():
|
||||
result.append({
|
||||
'plexId': row[0],
|
||||
'kodiId': row[1],
|
||||
'kodi_type': row[2],
|
||||
'plex_type': plextype
|
||||
})
|
||||
return result
|
|
@ -17,12 +17,13 @@ from utils import window, settings, language as lang
|
|||
from utils import tryDecode, tryEncode, CatchExceptions
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import embydb_functions as embydb
|
||||
import plexdb_functions as plexdb
|
||||
import playbackutils as pbutils
|
||||
import playlist
|
||||
|
||||
import PlexFunctions
|
||||
import PlexAPI
|
||||
from PKC_listitem import convert_PKC_to_listitem
|
||||
from playqueue import Playqueue
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -33,40 +34,6 @@ addonName = "PlexKodiConnect"
|
|||
###############################################################################
|
||||
|
||||
|
||||
def plexCompanion(fullurl, params):
|
||||
params = PlexFunctions.LiteralEval(params[26:])
|
||||
|
||||
if params['machineIdentifier'] != window('plex_machineIdentifier'):
|
||||
log.error("Command was not for us, machineIdentifier controller: %s, "
|
||||
"our machineIdentifier : %s"
|
||||
% (params['machineIdentifier'],
|
||||
window('plex_machineIdentifier')))
|
||||
return
|
||||
|
||||
library, key, query = PlexFunctions.ParseContainerKey(
|
||||
params['containerKey'])
|
||||
# Construct a container key that works always (get rid of playlist args)
|
||||
window('containerKey', '/'+library+'/'+key)
|
||||
|
||||
if 'playQueues' in library:
|
||||
log.debug("Playing a playQueue. Query was: %s" % query)
|
||||
# Playing a playlist that we need to fetch from PMS
|
||||
xml = PlexFunctions.GetPlayQueue(key)
|
||||
if xml is None:
|
||||
log.error("Error getting PMS playlist for key %s" % key)
|
||||
return
|
||||
else:
|
||||
resume = PlexFunctions.ConvertPlexToKodiTime(
|
||||
params.get('offset', 0))
|
||||
itemids = []
|
||||
for item in xml:
|
||||
itemids.append(item.get('ratingKey'))
|
||||
return playlist.Playlist().playAll(itemids, resume)
|
||||
|
||||
else:
|
||||
log.error("Not knowing what to do for now - no playQueue sent")
|
||||
|
||||
|
||||
def chooseServer():
|
||||
"""
|
||||
Lets user choose from list of PMS
|
||||
|
@ -130,45 +97,21 @@ def togglePlexTV():
|
|||
sound=False)
|
||||
|
||||
|
||||
def PassPlaylist(xml, resume=None):
|
||||
"""
|
||||
resume in KodiTime - seconds.
|
||||
"""
|
||||
# Set window properties to make them available later for other threads
|
||||
windowArgs = [
|
||||
# 'containerKey'
|
||||
'playQueueID',
|
||||
'playQueueVersion']
|
||||
for arg in windowArgs:
|
||||
window(arg, value=xml.attrib.get(arg))
|
||||
|
||||
# Get resume point
|
||||
from utils import IntFromStr
|
||||
resume1 = PlexFunctions.ConvertPlexToKodiTime(IntFromStr(
|
||||
xml.attrib.get('playQueueSelectedItemOffset', 0)))
|
||||
resume2 = resume
|
||||
resume = max(resume1, resume2)
|
||||
|
||||
pbutils.PlaybackUtils(xml).StartPlay(
|
||||
resume=resume,
|
||||
resumeId=xml.attrib.get('playQueueSelectedItemID', None))
|
||||
|
||||
|
||||
def playWatchLater(itemid, viewOffset):
|
||||
def Plex_Node(url, viewOffset, plex_type, playdirectly=False):
|
||||
"""
|
||||
Called only for a SINGLE element for Plex.tv watch later
|
||||
|
||||
Always to return with a "setResolvedUrl"
|
||||
"""
|
||||
log.info('playWatchLater called with id: %s, viewOffset: %s'
|
||||
% (itemid, viewOffset))
|
||||
log.info('Plex_Node called with url: %s, viewOffset: %s'
|
||||
% (url, viewOffset))
|
||||
# Plex redirect, e.g. watch later. Need to get actual URLs
|
||||
xml = downloadutils.DownloadUtils().downloadUrl(itemid,
|
||||
authenticate=False)
|
||||
if xml in (None, 401):
|
||||
log.error("Could not resolve url %s" % itemid)
|
||||
return xbmcplugin.setResolvedUrl(
|
||||
int(sys.argv[1]), False, xbmcgui.ListItem())
|
||||
xml = downloadutils.DownloadUtils().downloadUrl(url)
|
||||
try:
|
||||
xml[0].attrib
|
||||
except:
|
||||
log.error('Could not download PMS metadata')
|
||||
return
|
||||
if viewOffset != '0':
|
||||
try:
|
||||
viewOffset = int(PlexFunctions.PLEX_TO_KODI_TIMEFACTOR *
|
||||
|
@ -178,41 +121,20 @@ def playWatchLater(itemid, viewOffset):
|
|||
else:
|
||||
window('plex_customplaylist.seektime', value=str(viewOffset))
|
||||
log.info('Set resume point to %s' % str(viewOffset))
|
||||
return pbutils.PlaybackUtils(xml).play(None, 'plexnode')
|
||||
|
||||
|
||||
def doPlayback(itemid, dbid):
|
||||
"""
|
||||
Called only for a SINGLE element, not playQueues
|
||||
|
||||
Always to return with a "setResolvedUrl"
|
||||
"""
|
||||
if window('plex_authenticated') != "true":
|
||||
log.error('Not yet authenticated for a PMS, abort starting playback')
|
||||
# Not yet connected to a PMS server
|
||||
xbmcgui.Dialog().notification(
|
||||
addonName,
|
||||
lang(39210),
|
||||
xbmcgui.NOTIFICATION_ERROR,
|
||||
7000,
|
||||
True)
|
||||
return xbmcplugin.setResolvedUrl(
|
||||
int(sys.argv[1]), False, xbmcgui.ListItem())
|
||||
|
||||
xml = PlexFunctions.GetPlexMetadata(itemid)
|
||||
if xml in (None, 401):
|
||||
return xbmcplugin.setResolvedUrl(
|
||||
int(sys.argv[1]), False, xbmcgui.ListItem())
|
||||
if xml[0].attrib.get('type') == 'photo':
|
||||
# Photo
|
||||
API = PlexAPI.API(xml[0])
|
||||
listitem = API.CreateListItemFromPlexItem()
|
||||
API.AddStreamInfo(listitem)
|
||||
pbutils.PlaybackUtils(xml[0]).setArtwork(listitem)
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
typus = PlexFunctions.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type]
|
||||
playqueue = Playqueue().get_playqueue_from_type(typus)
|
||||
result = pbutils.PlaybackUtils(xml, playqueue).play(
|
||||
None,
|
||||
kodi_id='plexnode',
|
||||
plex_lib_UUID=xml.attrib.get('librarySectionUUID'))
|
||||
if result.listitem:
|
||||
listitem = convert_PKC_to_listitem(result.listitem)
|
||||
else:
|
||||
# Video
|
||||
return pbutils.PlaybackUtils(xml).play(itemid, dbid)
|
||||
return
|
||||
if playdirectly:
|
||||
xbmc.Player().play(listitem.getfilename(), listitem)
|
||||
else:
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
|
||||
|
||||
##### DO RESET AUTH #####
|
||||
|
@ -319,12 +241,8 @@ def deleteItem():
|
|||
log.error("Unknown type, unable to proceed.")
|
||||
return
|
||||
|
||||
from utils import kodiSQL
|
||||
embyconn = kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
item = emby_db.getItem_byKodiId(dbid, itemtype)
|
||||
embycursor.close()
|
||||
with plexdb.Get_Plex_DB() as plexcursor:
|
||||
item = plexcursor.getItem_byKodiId(dbid, itemtype)
|
||||
|
||||
try:
|
||||
plexid = item[0]
|
||||
|
@ -467,99 +385,6 @@ def BrowseContent(viewname, browse_type="", folderid=""):
|
|||
|
||||
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
|
||||
|
||||
##### CREATE LISTITEM FROM EMBY METADATA #####
|
||||
# def createListItemFromEmbyItem(item,art=artwork.Artwork(),doUtils=downloadutils.DownloadUtils()):
|
||||
def createListItemFromEmbyItem(item,art=None,doUtils=downloadutils.DownloadUtils()):
|
||||
API = PlexAPI.API(item)
|
||||
itemid = item['Id']
|
||||
|
||||
title = item.get('Name')
|
||||
li = xbmcgui.ListItem(title)
|
||||
|
||||
premieredate = item.get('PremiereDate',"")
|
||||
if not premieredate: premieredate = item.get('DateCreated',"")
|
||||
if premieredate:
|
||||
premieredatelst = premieredate.split('T')[0].split("-")
|
||||
premieredate = "%s.%s.%s" %(premieredatelst[2],premieredatelst[1],premieredatelst[0])
|
||||
|
||||
li.setProperty("plexid",itemid)
|
||||
|
||||
allart = art.getAllArtwork(item)
|
||||
|
||||
if item["Type"] == "Photo":
|
||||
#listitem setup for pictures...
|
||||
img_path = allart.get('Primary')
|
||||
li.setProperty("path",img_path)
|
||||
picture = doUtils.downloadUrl("{server}/Items/%s/Images" %itemid)
|
||||
if picture:
|
||||
picture = picture[0]
|
||||
if picture.get("Width") > picture.get("Height"):
|
||||
li.setArt( {"fanart": img_path}) #add image as fanart for use with skinhelper auto thumb/backgrund creation
|
||||
li.setInfo('pictures', infoLabels={ "picturepath": img_path, "date": premieredate, "size": picture.get("Size"), "exif:width": str(picture.get("Width")), "exif:height": str(picture.get("Height")), "title": title})
|
||||
li.setThumbnailImage(img_path)
|
||||
li.setProperty("plot",API.getOverview())
|
||||
li.setIconImage('DefaultPicture.png')
|
||||
else:
|
||||
#normal video items
|
||||
li.setProperty('IsPlayable', 'true')
|
||||
path = "%s?id=%s&mode=play" % (sys.argv[0], item.get("Id"))
|
||||
li.setProperty("path",path)
|
||||
genre = API.getGenres()
|
||||
overlay = 0
|
||||
userdata = API.getUserData()
|
||||
runtime = item.get("RunTimeTicks",0)/ 10000000.0
|
||||
seektime = userdata['Resume']
|
||||
if seektime:
|
||||
li.setProperty("resumetime", str(seektime))
|
||||
li.setProperty("totaltime", str(runtime))
|
||||
|
||||
played = userdata['Played']
|
||||
if played: overlay = 7
|
||||
else: overlay = 6
|
||||
playcount = userdata['PlayCount']
|
||||
if playcount is None:
|
||||
playcount = 0
|
||||
|
||||
rating = item.get('CommunityRating')
|
||||
if not rating: rating = userdata['UserRating']
|
||||
|
||||
# Populate the extradata list and artwork
|
||||
extradata = {
|
||||
'id': itemid,
|
||||
'rating': rating,
|
||||
'year': item.get('ProductionYear'),
|
||||
'genre': genre,
|
||||
'playcount': str(playcount),
|
||||
'title': title,
|
||||
'plot': API.getOverview(),
|
||||
'Overlay': str(overlay),
|
||||
'duration': runtime
|
||||
}
|
||||
if premieredate:
|
||||
extradata["premieredate"] = premieredate
|
||||
extradata["date"] = premieredate
|
||||
li.setInfo('video', infoLabels=extradata)
|
||||
if allart.get('Primary'):
|
||||
li.setThumbnailImage(allart.get('Primary'))
|
||||
else: li.setThumbnailImage('DefaultTVShows.png')
|
||||
li.setIconImage('DefaultTVShows.png')
|
||||
if not allart.get('Background'): #add image as fanart for use with skinhelper auto thumb/backgrund creation
|
||||
li.setArt( {"fanart": allart.get('Primary') } )
|
||||
else:
|
||||
pbutils.PlaybackUtils(item).setArtwork(li)
|
||||
|
||||
mediastreams = API.getMediaStreams()
|
||||
videostreamFound = False
|
||||
if mediastreams:
|
||||
for key, value in mediastreams.iteritems():
|
||||
if key == "video" and value: videostreamFound = True
|
||||
if value: li.addStreamInfo(key, value[0])
|
||||
if not videostreamFound:
|
||||
#just set empty streamdetails to prevent errors in the logs
|
||||
li.addStreamInfo("video", {'duration': runtime})
|
||||
|
||||
return li
|
||||
|
||||
##### BROWSE EMBY CHANNELS #####
|
||||
def BrowseChannels(itemid, folderid=None):
|
||||
|
||||
|
@ -664,7 +489,7 @@ def createListItem(item, appendShowTitle=False, appendSxxExx=False):
|
|||
li.setProperty('totaltime', str(item['resume']['total']))
|
||||
li.setArt(item['art'])
|
||||
li.setThumbnailImage(item['art'].get('thumb',''))
|
||||
li.setIconImage('DefaultTVShows.png')
|
||||
li.setArt({'icon': 'DefaultTVShows.png'})
|
||||
li.setProperty('dbid', str(item['episodeid']))
|
||||
li.setProperty('fanart_image', item['art'].get('tvshow.fanart',''))
|
||||
for key, value in item['streamdetails'].iteritems():
|
||||
|
@ -1095,14 +920,14 @@ def BrowsePlexContent(viewid, mediatype="", folderid=""):
|
|||
li.setProperty('IsPlayable', 'false')
|
||||
path = "%s?id=%s&mode=browseplex&type=%s&folderid=%s" \
|
||||
% (sys.argv[0], viewid, mediatype, API.getKey())
|
||||
pbutils.PlaybackUtils(item).setArtwork(li)
|
||||
API.set_listitem_artwork(li)
|
||||
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
|
||||
url=path,
|
||||
listitem=li,
|
||||
isFolder=True)
|
||||
else:
|
||||
li = API.CreateListItemFromPlexItem()
|
||||
pbutils.PlaybackUtils(item).setArtwork(li)
|
||||
API.set_listitem_artwork(li)
|
||||
xbmcplugin.addDirectoryItem(
|
||||
handle=int(sys.argv[1]),
|
||||
url=li.getProperty("path"),
|
||||
|
@ -1159,7 +984,7 @@ def getOnDeck(viewid, mediatype, tagname, limit):
|
|||
appendShowTitle=appendShowTitle,
|
||||
appendSxxExx=appendSxxExx)
|
||||
API.AddStreamInfo(listitem)
|
||||
pbutils.PlaybackUtils(item).setArtwork(listitem)
|
||||
API.set_listitem_artwork(listitem)
|
||||
if directpaths:
|
||||
url = API.getFilePath()
|
||||
else:
|
||||
|
@ -1168,7 +993,7 @@ def getOnDeck(viewid, mediatype, tagname, limit):
|
|||
'id': API.getRatingKey(),
|
||||
'dbid': listitem.getProperty('dbid')
|
||||
}
|
||||
url = "plugin://plugin.video.plexkodiconnect.tvshows/?%s" \
|
||||
url = "plugin://plugin.video.plexkodiconnect/tvshows/?%s" \
|
||||
% urllib.urlencode(params)
|
||||
xbmcplugin.addDirectoryItem(
|
||||
handle=int(sys.argv[1]),
|
||||
|
@ -1306,15 +1131,16 @@ def watchlater():
|
|||
xbmcplugin.setContent(int(sys.argv[1]), 'movies')
|
||||
url = "plugin://plugin.video.plexkodiconnect/"
|
||||
params = {
|
||||
'mode': "playwatchlater",
|
||||
'mode': "Plex_Node",
|
||||
}
|
||||
for item in xml:
|
||||
API = PlexAPI.API(item)
|
||||
listitem = API.CreateListItemFromPlexItem()
|
||||
API.AddStreamInfo(listitem)
|
||||
pbutils.PlaybackUtils(item).setArtwork(listitem)
|
||||
API.set_listitem_artwork(listitem)
|
||||
params['id'] = item.attrib.get('key')
|
||||
params['viewOffset'] = item.attrib.get('viewOffset', '0')
|
||||
params['plex_type'] = item.attrib.get('type')
|
||||
xbmcplugin.addDirectoryItem(
|
||||
handle=int(sys.argv[1]),
|
||||
url="%s?%s" % (url, urllib.urlencode(params)),
|
||||
|
|
|
@ -9,7 +9,7 @@ import xbmcgui
|
|||
from utils import settings, window, language as lang
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import userclient
|
||||
from userclient import UserClient
|
||||
|
||||
import PlexAPI
|
||||
from PlexFunctions import GetMachineIdentifier, get_PMS_settings
|
||||
|
@ -30,11 +30,10 @@ class InitialSetup():
|
|||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonId = self.clientInfo.getAddonId()
|
||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||
self.userClient = userclient.UserClient()
|
||||
self.plx = PlexAPI.PlexAPI()
|
||||
self.dialog = xbmcgui.Dialog()
|
||||
|
||||
self.server = self.userClient.getServer()
|
||||
self.server = UserClient().getServer()
|
||||
self.serverid = settings('plex_machineIdentifier')
|
||||
# Get Plex credentials from settings file, if they exist
|
||||
plexdict = self.plx.GetPlexLoginFromSettings()
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,6 +7,7 @@ from ntpath import dirname
|
|||
|
||||
import artwork
|
||||
from utils import kodiSQL, KODIVERSION
|
||||
from PlexFunctions import KODI_TYPE_MOVIE, KODI_TYPE_EPISODE
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -31,8 +32,8 @@ class GetKodiDB():
|
|||
|
||||
def __enter__(self):
|
||||
self.kodiconn = kodiSQL(self.itemType)
|
||||
self.emby_db = Kodidb_Functions(self.kodiconn.cursor())
|
||||
return self.emby_db
|
||||
kodi_db = Kodidb_Functions(self.kodiconn.cursor())
|
||||
return kodi_db
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.kodiconn.commit()
|
||||
|
@ -61,7 +62,7 @@ class Kodidb_Functions():
|
|||
self.cursor.execute(
|
||||
query, ('movies',
|
||||
'metadata.local',
|
||||
'plugin://plugin.video.plexkodiconnect.movies%%'))
|
||||
'plugin://plugin.video.plexkodiconnect/movies%%'))
|
||||
|
||||
def getParentPathId(self, path):
|
||||
"""
|
||||
|
@ -869,7 +870,7 @@ class Kodidb_Functions():
|
|||
self.cursor.execute(query, (idFile,))
|
||||
try:
|
||||
itemId = self.cursor.fetchone()[0]
|
||||
typus = 'movie'
|
||||
typus = KODI_TYPE_MOVIE
|
||||
except TypeError:
|
||||
# Try tv shows next
|
||||
query = ' '.join((
|
||||
|
@ -880,7 +881,7 @@ class Kodidb_Functions():
|
|||
self.cursor.execute(query, (idFile,))
|
||||
try:
|
||||
itemId = self.cursor.fetchone()[0]
|
||||
typus = 'episode'
|
||||
typus = KODI_TYPE_EPISODE
|
||||
except TypeError:
|
||||
log.warn('Unexpectantly did not find a match!')
|
||||
return
|
||||
|
@ -907,13 +908,13 @@ class Kodidb_Functions():
|
|||
return ids
|
||||
|
||||
def getVideoRuntime(self, kodiid, mediatype):
|
||||
if mediatype == 'movie':
|
||||
if mediatype == KODI_TYPE_MOVIE:
|
||||
query = ' '.join((
|
||||
"SELECT c11",
|
||||
"FROM movie",
|
||||
"WHERE idMovie = ?",
|
||||
))
|
||||
elif mediatype == 'episode':
|
||||
elif mediatype == KODI_TYPE_EPISODE:
|
||||
query = ' '.join((
|
||||
"SELECT c09",
|
||||
"FROM episode",
|
||||
|
@ -1397,3 +1398,89 @@ class Kodidb_Functions():
|
|||
|
||||
query = "INSERT OR REPLACE INTO song_genre(idGenre, idSong) values(?, ?)"
|
||||
self.cursor.execute(query, (genreid, kodiid))
|
||||
|
||||
# Krypton only stuff ##############################
|
||||
|
||||
def create_entry_uniqueid(self):
|
||||
self.cursor.execute(
|
||||
"select coalesce(max(uniqueid_id),0) from uniqueid")
|
||||
return self.cursor.fetchone()[0] + 1
|
||||
|
||||
def add_uniqueid(self, *args):
|
||||
"""
|
||||
Feed with:
|
||||
uniqueid_id, media_id, media_type, value, type
|
||||
|
||||
type: e.g. 'imdb'
|
||||
"""
|
||||
query = '''
|
||||
INSERT INTO uniqueid(
|
||||
uniqueid_id, media_id, media_type, value, type)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
'''
|
||||
self.cursor.execute(query, (args))
|
||||
|
||||
def create_entry_rating(self):
|
||||
self.cursor.execute("select coalesce(max(rating_id),0) from rating")
|
||||
return self.cursor.fetchone()[0] + 1
|
||||
|
||||
def get_ratingid(self, media_id):
|
||||
query = "SELECT rating_id FROM rating WHERE media_id = ?"
|
||||
self.cursor.execute(query, (media_id,))
|
||||
try:
|
||||
ratingid = self.cursor.fetchone()[0]
|
||||
except TypeError:
|
||||
ratingid = None
|
||||
return ratingid
|
||||
|
||||
def update_ratings(self, *args):
|
||||
"""
|
||||
Feed with media_id, media_type, rating_type, rating, votes, rating_id
|
||||
"""
|
||||
query = '''
|
||||
UPDATE rating
|
||||
SET media_id = ?,
|
||||
media_type = ?,
|
||||
rating_type = ?,
|
||||
rating = ?,
|
||||
votes = ?
|
||||
WHERE rating_id = ?
|
||||
'''
|
||||
self.cursor.execute(query, (args))
|
||||
|
||||
def add_ratings(self, *args):
|
||||
"""
|
||||
feed with:
|
||||
rating_id, media_id, media_type, rating_type, rating, votes
|
||||
|
||||
rating_type = 'default'
|
||||
"""
|
||||
query = '''
|
||||
INSERT INTO rating(
|
||||
rating_id, media_id, media_type, rating_type, rating, votes)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
self.cursor.execute(query, (args))
|
||||
|
||||
|
||||
def get_kodiid_from_filename(file):
|
||||
"""
|
||||
Returns the tuple (kodiid, type) if we have a video in the database with
|
||||
said filename, or (None, None)
|
||||
"""
|
||||
kodiid = None
|
||||
typus = None
|
||||
try:
|
||||
filename = file.rsplit('/', 1)[1]
|
||||
path = file.rsplit('/', 1)[0] + '/'
|
||||
except IndexError:
|
||||
filename = file.rsplit('\\', 1)[1]
|
||||
path = file.rsplit('\\', 1)[0] + '\\'
|
||||
log.debug('Trying to figure out playing item from filename: %s '
|
||||
'and path: %s' % (filename, path))
|
||||
with GetKodiDB('video') as kodi_db:
|
||||
try:
|
||||
kodiid, typus = kodi_db.getIdFromFilename(filename, path)
|
||||
except TypeError:
|
||||
log.info('No kodi video element found with filename %s' % filename)
|
||||
return (kodiid, typus)
|
||||
|
|
|
@ -3,17 +3,16 @@
|
|||
###############################################################################
|
||||
|
||||
import logging
|
||||
import json
|
||||
from json import loads
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
from xbmc import Monitor, Player, sleep
|
||||
|
||||
import downloadutils
|
||||
import embydb_functions as embydb
|
||||
import kodidb_functions as kodidb
|
||||
import playbackutils as pbutils
|
||||
import plexdb_functions as plexdb
|
||||
from utils import window, settings, CatchExceptions, tryDecode, tryEncode
|
||||
from PlexFunctions import scrobble, REMAP_TYPE_FROM_PLEXTYPE
|
||||
from kodidb_functions import get_kodiid_from_filename
|
||||
from PlexAPI import API
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -22,13 +21,14 @@ log = logging.getLogger("PLEX."+__name__)
|
|||
###############################################################################
|
||||
|
||||
|
||||
class KodiMonitor(xbmc.Monitor):
|
||||
|
||||
def __init__(self):
|
||||
class KodiMonitor(Monitor):
|
||||
|
||||
def __init__(self, callback):
|
||||
self.mgr = callback
|
||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||
self.xbmcplayer = xbmc.Player()
|
||||
xbmc.Monitor.__init__(self)
|
||||
self.xbmcplayer = Player()
|
||||
self.playqueue = self.mgr.playqueue
|
||||
Monitor.__init__(self)
|
||||
log.info("Kodi monitor started.")
|
||||
|
||||
def onScanStarted(self, library):
|
||||
|
@ -70,7 +70,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
def onNotification(self, sender, method, data):
|
||||
|
||||
if data:
|
||||
data = json.loads(data, 'utf-8')
|
||||
data = loads(data, 'utf-8')
|
||||
log.debug("Method: %s Data: %s" % (method, data))
|
||||
|
||||
if method == "Player.OnPlay":
|
||||
|
@ -92,18 +92,18 @@ class KodiMonitor(xbmc.Monitor):
|
|||
log.info("Item is invalid for playstate update.")
|
||||
else:
|
||||
# Send notification to the server.
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type)
|
||||
with plexdb.Get_Plex_DB() as plexcur:
|
||||
plex_dbitem = plexcur.getItem_byKodiId(kodiid, item_type)
|
||||
try:
|
||||
itemid = emby_dbitem[0]
|
||||
itemid = plex_dbitem[0]
|
||||
except TypeError:
|
||||
log.error("Could not find itemid in emby database for a "
|
||||
log.error("Could not find itemid in plex database for a "
|
||||
"video library update")
|
||||
else:
|
||||
# Stop from manually marking as watched unwatched, with actual playback.
|
||||
if window('emby_skipWatched%s' % itemid) == "true":
|
||||
if window('plex_skipWatched%s' % itemid) == "true":
|
||||
# property is set in player.py
|
||||
window('emby_skipWatched%s' % itemid, clear=True)
|
||||
window('plex_skipWatched%s' % itemid, clear=True)
|
||||
else:
|
||||
# notify the server
|
||||
if playcount != 0:
|
||||
|
@ -112,40 +112,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
scrobble(itemid, 'unwatched')
|
||||
|
||||
elif method == "VideoLibrary.OnRemove":
|
||||
# Removed function, because with plugin paths + clean library, it will wipe
|
||||
# entire library if user has permissions. Instead, use the emby context menu available
|
||||
# in Isengard and higher version
|
||||
pass
|
||||
'''try:
|
||||
kodiid = data['id']
|
||||
type = data['type']
|
||||
except (KeyError, TypeError):
|
||||
log.info("Item is invalid for emby deletion.")
|
||||
else:
|
||||
# Send the delete action to the server.
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
|
||||
try:
|
||||
itemid = emby_dbitem[0]
|
||||
except TypeError:
|
||||
log.info("Could not find itemid in emby database.")
|
||||
else:
|
||||
if settings('skipContextMenu') != "true":
|
||||
resp = xbmcgui.Dialog().yesno(
|
||||
heading="Confirm delete",
|
||||
line1="Delete file on Emby Server?")
|
||||
if not resp:
|
||||
log.info("User skipped deletion.")
|
||||
embycursor.close()
|
||||
return
|
||||
|
||||
url = "{server}/emby/Items/%s?format=json" % itemid
|
||||
log.info("Deleting request: %s" % itemid)
|
||||
doUtils.downloadUrl(url, action_type="DELETE")
|
||||
finally:
|
||||
embycursor.close()'''
|
||||
|
||||
elif method == "System.OnSleep":
|
||||
# Connection is going to sleep
|
||||
|
@ -154,18 +121,15 @@ class KodiMonitor(xbmc.Monitor):
|
|||
|
||||
elif method == "System.OnWake":
|
||||
# Allow network to wake up
|
||||
xbmc.sleep(10000)
|
||||
sleep(10000)
|
||||
window('plex_onWake', value="true")
|
||||
window('plex_online', value="false")
|
||||
|
||||
elif method == "GUI.OnScreensaverDeactivated":
|
||||
if settings('dbSyncScreensaver') == "true":
|
||||
xbmc.sleep(5000)
|
||||
sleep(5000)
|
||||
window('plex_runLibScan', value="full")
|
||||
|
||||
elif method == "Playlist.OnClear":
|
||||
pass
|
||||
|
||||
def PlayBackStart(self, data):
|
||||
"""
|
||||
Called whenever a playback is started
|
||||
|
@ -177,7 +141,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
currentFile = None
|
||||
count = 0
|
||||
while currentFile is None:
|
||||
xbmc.sleep(100)
|
||||
sleep(100)
|
||||
try:
|
||||
currentFile = self.xbmcplayer.getPlayingFile()
|
||||
except:
|
||||
|
@ -201,7 +165,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
|
||||
# Try to get a Kodi ID
|
||||
# If PKC was used - native paths, not direct paths
|
||||
plexid = window('emby_%s.itemid' % tryEncode(currentFile))
|
||||
plexid = window('plex_%s.itemid' % tryEncode(currentFile))
|
||||
# Get rid of the '' if the window property was not set
|
||||
plexid = None if not plexid else plexid
|
||||
kodiid = None
|
||||
|
@ -215,27 +179,16 @@ class KodiMonitor(xbmc.Monitor):
|
|||
# When using Widgets, Kodi doesn't tell us shit so we need this hack
|
||||
if (kodiid is None and plexid is None and typus != 'song'
|
||||
and not currentFile.startswith('http')):
|
||||
try:
|
||||
filename = currentFile.rsplit('/', 1)[1]
|
||||
path = currentFile.rsplit('/', 1)[0] + '/'
|
||||
except IndexError:
|
||||
filename = currentFile.rsplit('\\', 1)[1]
|
||||
path = currentFile.rsplit('\\', 1)[0] + '\\'
|
||||
log.debug('Trying to figure out playing item from filename: %s '
|
||||
'and path: %s' % (filename, path))
|
||||
with kodidb.GetKodiDB('video') as kodi_db:
|
||||
try:
|
||||
kodiid, typus = kodi_db.getIdFromFilename(filename, path)
|
||||
except TypeError:
|
||||
log.info('Abort playback report, could not id kodi item')
|
||||
(kodiid, typus) = get_kodiid_from_filename(currentFile)
|
||||
if kodiid is None:
|
||||
return
|
||||
|
||||
if plexid is None:
|
||||
# Get Plex' item id
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, typus)
|
||||
with plexdb.Get_Plex_DB() as plexcursor:
|
||||
plex_dbitem = plexcursor.getItem_byKodiId(kodiid, typus)
|
||||
try:
|
||||
plexid = emby_dbitem[0]
|
||||
plexid = plex_dbitem[0]
|
||||
except TypeError:
|
||||
log.info("No Plex id returned for kodiid %s. Aborting playback"
|
||||
" report" % kodiid)
|
||||
|
@ -256,24 +209,25 @@ class KodiMonitor(xbmc.Monitor):
|
|||
# Save currentFile for cleanup later and to be able to access refs
|
||||
window('plex_lastPlayedFiled', value=currentFile)
|
||||
window('plex_currently_playing_itemid', value=plexid)
|
||||
window("emby_%s.itemid" % tryEncode(currentFile), value=plexid)
|
||||
window("plex_%s.itemid" % tryEncode(currentFile), value=plexid)
|
||||
log.info('Finish playback startup')
|
||||
|
||||
def StartDirectPath(self, plexid, type, currentFile):
|
||||
"""
|
||||
Set some additional stuff if playback was initiated by Kodi, not PKC
|
||||
"""
|
||||
result = self.doUtils('{server}/library/metadata/%s' % plexid)
|
||||
xml = self.doUtils('{server}/library/metadata/%s' % plexid)
|
||||
try:
|
||||
result[0].attrib
|
||||
xml[0].attrib
|
||||
except:
|
||||
log.error('Did not receive a valid XML for plexid %s.' % plexid)
|
||||
return False
|
||||
# Setup stuff, because playback was started by Kodi, not PKC
|
||||
pbutils.PlaybackUtils(result[0]).setProperties(
|
||||
currentFile, xbmcgui.ListItem())
|
||||
api = API(xml[0])
|
||||
listitem = api.CreateListItemFromPlexItem()
|
||||
api.set_playback_win_props(currentFile, listitem)
|
||||
if type == "song" and settings('streamMusic') == "true":
|
||||
window('emby_%s.playmethod' % currentFile, value="DirectStream")
|
||||
window('plex_%s.playmethod' % currentFile, value="DirectStream")
|
||||
else:
|
||||
window('emby_%s.playmethod' % currentFile, value="DirectPlay")
|
||||
window('plex_%s.playmethod' % currentFile, value="DirectPlay")
|
||||
log.debug('Window properties set for direct paths!')
|
||||
|
|
|
@ -11,7 +11,7 @@ import xbmc
|
|||
import xbmcgui
|
||||
import xbmcvfs
|
||||
|
||||
from utils import window, settings, getUnixTimestamp, kodiSQL, sourcesXML,\
|
||||
from utils import window, settings, getUnixTimestamp, sourcesXML,\
|
||||
ThreadMethods, ThreadMethodsAdditionalStop, LogTime, getScreensaver,\
|
||||
setScreensaver, playlistXSP, language as lang, DateToKodi, reset,\
|
||||
advancedSettingsXML, getKodiVideoDBPath, tryDecode, deletePlaylists,\
|
||||
|
@ -19,7 +19,7 @@ from utils import window, settings, getUnixTimestamp, kodiSQL, sourcesXML,\
|
|||
import clientinfo
|
||||
import downloadutils
|
||||
import itemtypes
|
||||
import embydb_functions as embydb
|
||||
import plexdb_functions as plexdb
|
||||
import kodidb_functions as kodidb
|
||||
import userclient
|
||||
import videonodes
|
||||
|
@ -302,9 +302,9 @@ class ProcessFanartThread(Thread):
|
|||
# Leave the Plex art untouched
|
||||
allartworks = None
|
||||
else:
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
try:
|
||||
kodiId = emby_db.getItem_byId(item['itemId'])[0]
|
||||
kodiId = plex_db.getItem_byId(item['itemId'])[0]
|
||||
except TypeError:
|
||||
log.error('Could not get Kodi id for plex id %s'
|
||||
% item['itemId'])
|
||||
|
@ -356,19 +356,10 @@ class ProcessFanartThread(Thread):
|
|||
@ThreadMethods
|
||||
class LibrarySync(Thread):
|
||||
"""
|
||||
librarysync.LibrarySync(queue)
|
||||
|
||||
where (communication with websockets)
|
||||
queue: Queue object for background sync
|
||||
"""
|
||||
# Borg, even though it's planned to only have 1 instance up and running!
|
||||
_shared_state = {}
|
||||
def __init__(self, callback=None):
|
||||
self.mgr = callback
|
||||
|
||||
def __init__(self, queue):
|
||||
self.__dict__ = self._shared_state
|
||||
|
||||
# Communication with websockets
|
||||
self.queue = queue
|
||||
self.itemsToProcess = []
|
||||
self.sessionKeys = []
|
||||
self.fanartqueue = Queue.Queue()
|
||||
|
@ -455,7 +446,9 @@ class LibrarySync(Thread):
|
|||
return False
|
||||
|
||||
plexId = None
|
||||
for mediatype in ('movie', 'show', 'artist'):
|
||||
for mediatype in (PF.PLEX_TYPE_MOVIE,
|
||||
PF.PLEX_TYPE_SHOW,
|
||||
PF.PLEX_TYPE_ARTIST):
|
||||
if plexId is not None:
|
||||
break
|
||||
for view in sections:
|
||||
|
@ -539,24 +532,34 @@ class LibrarySync(Thread):
|
|||
|
||||
def initializeDBs(self):
|
||||
"""
|
||||
Run once during startup to verify that emby db exists.
|
||||
Run once during startup to verify that plex db exists.
|
||||
"""
|
||||
embyconn = kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
# Create the tables for the emby database
|
||||
# emby, view, version
|
||||
embycursor.execute(
|
||||
"""CREATE TABLE IF NOT EXISTS emby(
|
||||
emby_id TEXT UNIQUE, media_folder TEXT, emby_type TEXT, media_type TEXT, kodi_id INTEGER,
|
||||
kodi_fileid INTEGER, kodi_pathid INTEGER, parent_id INTEGER, checksum INTEGER)""")
|
||||
embycursor.execute(
|
||||
"""CREATE TABLE IF NOT EXISTS view(
|
||||
view_id TEXT UNIQUE, view_name TEXT, media_type TEXT, kodi_tagid INTEGER)""")
|
||||
embycursor.execute("CREATE TABLE IF NOT EXISTS version(idVersion TEXT)")
|
||||
embyconn.commit()
|
||||
|
||||
# content sync: movies, tvshows, musicvideos, music
|
||||
embyconn.close()
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
# Create the tables for the plex database
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS plex(
|
||||
plex_id TEXT UNIQUE,
|
||||
view_id TEXT,
|
||||
plex_type TEXT,
|
||||
kodi_type TEXT,
|
||||
kodi_id INTEGER,
|
||||
kodi_fileid INTEGER,
|
||||
kodi_pathid INTEGER,
|
||||
parent_id INTEGER,
|
||||
checksum INTEGER)
|
||||
''')
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS view(
|
||||
view_id TEXT UNIQUE,
|
||||
view_name TEXT,
|
||||
kodi_type TEXT,
|
||||
kodi_tagid INTEGER)
|
||||
''')
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS version(idVersion TEXT)
|
||||
''')
|
||||
# Create an index for actors to speed up sync
|
||||
create_actor_db_index()
|
||||
|
||||
# Create an index for actors to speed up sync
|
||||
create_actor_db_index()
|
||||
|
@ -643,12 +646,13 @@ class LibrarySync(Thread):
|
|||
log.error('Path hack failed with error message: %s' % str(e))
|
||||
return True
|
||||
|
||||
def processView(self, folderItem, kodi_db, emby_db, totalnodes):
|
||||
def processView(self, folderItem, kodi_db, plex_db, totalnodes):
|
||||
vnodes = self.vnodes
|
||||
folder = folderItem.attrib
|
||||
mediatype = folder['type']
|
||||
# Only process supported formats
|
||||
if mediatype not in ('movie', 'show', 'artist', 'photo'):
|
||||
if mediatype not in (PF.PLEX_TYPE_MOVIE, PF.PLEX_TYPE_SHOW,
|
||||
PF.PLEX_TYPE_ARTIST, PF.PLEX_TYPE_PHOTO):
|
||||
return totalnodes
|
||||
|
||||
# Prevent duplicate for nodes of the same type
|
||||
|
@ -661,8 +665,8 @@ class LibrarySync(Thread):
|
|||
foldername = folder['title']
|
||||
viewtype = folder['type']
|
||||
|
||||
# Get current media folders from emby database
|
||||
view = emby_db.getView_byId(folderid)
|
||||
# Get current media folders from plex database
|
||||
view = plex_db.getView_byId(folderid)
|
||||
try:
|
||||
current_viewname = view[0]
|
||||
current_viewtype = view[1]
|
||||
|
@ -672,12 +676,12 @@ class LibrarySync(Thread):
|
|||
tagid = kodi_db.createTag(foldername)
|
||||
# Create playlist for the video library
|
||||
if (foldername not in playlists and
|
||||
mediatype in ('movie', 'show', 'musicvideos')):
|
||||
mediatype in (PF.PLEX_TYPE_MOVIE, PF.PLEX_TYPE_SHOW)):
|
||||
playlistXSP(mediatype, foldername, folderid, viewtype)
|
||||
playlists.append(foldername)
|
||||
# Create the video node
|
||||
if (foldername not in nodes and
|
||||
mediatype not in ("musicvideos", "artist")):
|
||||
mediatype != PF.PLEX_TYPE_ARTIST):
|
||||
vnodes.viewNode(sorted_views.index(foldername),
|
||||
foldername,
|
||||
mediatype,
|
||||
|
@ -685,8 +689,8 @@ class LibrarySync(Thread):
|
|||
folderid)
|
||||
nodes.append(foldername)
|
||||
totalnodes += 1
|
||||
# Add view to emby database
|
||||
emby_db.addView(folderid, foldername, viewtype, tagid)
|
||||
# Add view to plex database
|
||||
plex_db.addView(folderid, foldername, viewtype, tagid)
|
||||
else:
|
||||
log.info(' '.join((
|
||||
"Found viewid: %s" % folderid,
|
||||
|
@ -708,10 +712,10 @@ class LibrarySync(Thread):
|
|||
tagid = kodi_db.createTag(foldername)
|
||||
|
||||
# Update view with new info
|
||||
emby_db.updateView(foldername, tagid, folderid)
|
||||
plex_db.updateView(foldername, tagid, folderid)
|
||||
|
||||
if mediatype != "artist":
|
||||
if emby_db.getView_byName(current_viewname) is None:
|
||||
if plex_db.getView_byName(current_viewname) is None:
|
||||
# The tag could be a combined view. Ensure there's
|
||||
# no other tags with the same name before deleting
|
||||
# playlist.
|
||||
|
@ -731,7 +735,7 @@ class LibrarySync(Thread):
|
|||
delete=True)
|
||||
# Added new playlist
|
||||
if (foldername not in playlists and
|
||||
mediatype in ('movie', 'show', 'musicvideos')):
|
||||
mediatype in (PF.PLEX_TYPE_MOVIE, PF.PLEX_TYPE_SHOW)):
|
||||
playlistXSP(mediatype,
|
||||
foldername,
|
||||
folderid,
|
||||
|
@ -748,16 +752,16 @@ class LibrarySync(Thread):
|
|||
totalnodes += 1
|
||||
|
||||
# Update items with new tag
|
||||
items = emby_db.getItem_byView(folderid)
|
||||
items = plex_db.getItem_byView(folderid)
|
||||
for item in items:
|
||||
# Remove the "s" from viewtype for tags
|
||||
kodi_db.updateTag(
|
||||
current_tagid, tagid, item[0], current_viewtype[:-1])
|
||||
else:
|
||||
# Validate the playlist exists or recreate it
|
||||
if mediatype != "artist":
|
||||
if mediatype != PF.PLEX_TYPE_ARTIST:
|
||||
if (foldername not in playlists and
|
||||
mediatype in ('movie', 'show', 'musicvideos')):
|
||||
mediatype in (PF.PLEX_TYPE_MOVIE, PF.PLEX_TYPE_SHOW)):
|
||||
playlistXSP(mediatype,
|
||||
foldername,
|
||||
folderid,
|
||||
|
@ -792,22 +796,22 @@ class LibrarySync(Thread):
|
|||
|
||||
# For whatever freaking reason, .copy() or dict() does NOT work?!?!?!
|
||||
self.nodes = {
|
||||
'movie': [],
|
||||
'show': [],
|
||||
'artist': [],
|
||||
'photo': []
|
||||
PF.PLEX_TYPE_MOVIE: [],
|
||||
PF.PLEX_TYPE_SHOW: [],
|
||||
PF.PLEX_TYPE_ARTIST: [],
|
||||
PF.PLEX_TYPE_PHOTO: []
|
||||
}
|
||||
self.playlists = {
|
||||
'movie': [],
|
||||
'show': [],
|
||||
'artist': [],
|
||||
'photo': []
|
||||
PF.PLEX_TYPE_MOVIE: [],
|
||||
PF.PLEX_TYPE_SHOW: [],
|
||||
PF.PLEX_TYPE_ARTIST: [],
|
||||
PF.PLEX_TYPE_PHOTO: []
|
||||
}
|
||||
self.sorted_views = []
|
||||
|
||||
for view in sections:
|
||||
itemType = view.attrib['type']
|
||||
if itemType in ('movie', 'show', 'photo'): # NOT artist for now
|
||||
if itemType in (PF.PLEX_TYPE_MOVIE, PF.PLEX_TYPE_SHOW, PF.PLEX_TYPE_PHOTO): # NOT artist for now
|
||||
self.sorted_views.append(view.attrib['title'])
|
||||
log.debug('Sorted views: %s' % self.sorted_views)
|
||||
|
||||
|
@ -815,15 +819,15 @@ class LibrarySync(Thread):
|
|||
vnodes.clearProperties()
|
||||
totalnodes = len(self.sorted_views)
|
||||
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
# Backup old views to delete them later, if needed (at the end
|
||||
# of this method, only unused views will be left in oldviews)
|
||||
self.old_views = emby_db.getViews()
|
||||
self.old_views = plex_db.getViews()
|
||||
with kodidb.GetKodiDB('video') as kodi_db:
|
||||
for folderItem in sections:
|
||||
totalnodes = self.processView(folderItem,
|
||||
kodi_db,
|
||||
emby_db,
|
||||
plex_db,
|
||||
totalnodes)
|
||||
# Add video nodes listings
|
||||
# Plex: there seem to be no favorites/favorites tag
|
||||
|
@ -842,19 +846,17 @@ class LibrarySync(Thread):
|
|||
# "movies",
|
||||
# "channels")
|
||||
# totalnodes += 1
|
||||
with kodidb.GetKodiDB('music') as kodi_db:
|
||||
pass
|
||||
|
||||
# Save total
|
||||
window('Plex.nodes.total', str(totalnodes))
|
||||
|
||||
# Reopen DB connection to ensure that changes were commited before
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
log.info("Removing views: %s" % self.old_views)
|
||||
for view in self.old_views:
|
||||
emby_db.removeView(view)
|
||||
plex_db.removeView(view)
|
||||
# update views for all:
|
||||
self.views = emby_db.getAllViewInfo()
|
||||
self.views = plex_db.getAllViewInfo()
|
||||
|
||||
log.info("Finished processing views. Views saved: %s" % self.views)
|
||||
return True
|
||||
|
@ -1038,9 +1040,10 @@ class LibrarySync(Thread):
|
|||
if (settings('FanartTV') == 'true' and
|
||||
itemType in ('Movies', 'TVShows')):
|
||||
# Save to queue for later processing
|
||||
typus = {'Movies': 'movie', 'TVShows': 'tvshow'}[itemType]
|
||||
typus = {'Movies': PF.KODI_TYPE_MOVIE,
|
||||
'TVShows': PF.KODI_TYPE_SHOW}[itemType]
|
||||
for item in self.updatelist:
|
||||
if item['mediaType'] in ('movie', 'show'):
|
||||
if item['mediaType'] in (PF.KODI_TYPE_MOVIE, PF.KODI_TYPE_SHOW):
|
||||
self.fanartqueue.put({
|
||||
'itemId': item['itemId'],
|
||||
'class': itemType,
|
||||
|
@ -1056,16 +1059,17 @@ class LibrarySync(Thread):
|
|||
|
||||
itemType = 'Movies'
|
||||
|
||||
views = [x for x in self.views if x['itemtype'] == 'movie']
|
||||
views = [x for x in self.views if x['itemtype'] == PF.KODI_TYPE_MOVIE]
|
||||
log.info("Processing Plex %s. Libraries: %s" % (itemType, views))
|
||||
|
||||
self.allKodiElementsId = {}
|
||||
if self.compare:
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
# Get movies from Plex server
|
||||
# Pull the list of movies and boxsets in Kodi
|
||||
try:
|
||||
self.allKodiElementsId = dict(emby_db.getChecksum('Movie'))
|
||||
self.allKodiElementsId = dict(
|
||||
plex_db.getChecksum(PF.PLEX_TYPE_MOVIE))
|
||||
except ValueError:
|
||||
self.allKodiElementsId = {}
|
||||
|
||||
|
@ -1148,11 +1152,13 @@ class LibrarySync(Thread):
|
|||
|
||||
self.allKodiElementsId = {}
|
||||
if self.compare:
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
with plexdb.Get_Plex_DB() as plex:
|
||||
# Pull the list of TV shows already in Kodi
|
||||
for kind in ('Series', 'Season', 'Episode'):
|
||||
for kind in (PF.PLEX_TYPE_SHOW,
|
||||
PF.PLEX_TYPE_SEASON,
|
||||
PF.PLEX_TYPE_EPISODE):
|
||||
try:
|
||||
elements = dict(emby_db.getChecksum(kind))
|
||||
elements = dict(plex.getChecksum(kind))
|
||||
self.allKodiElementsId.update(elements)
|
||||
# Yet empty/not yet synched
|
||||
except ValueError:
|
||||
|
@ -1270,22 +1276,24 @@ class LibrarySync(Thread):
|
|||
def PlexMusic(self):
|
||||
itemType = 'Music'
|
||||
|
||||
views = [x for x in self.views if x['itemtype'] == 'artist']
|
||||
views = [x for x in self.views if x['itemtype'] == PF.PLEX_TYPE_ARTIST]
|
||||
log.info("Media folders for %s: %s" % (itemType, views))
|
||||
|
||||
methods = {
|
||||
'MusicArtist': 'add_updateArtist',
|
||||
'MusicAlbum': 'add_updateAlbum',
|
||||
'Audio': 'add_updateSong'
|
||||
PF.PLEX_TYPE_ARTIST: 'add_updateArtist',
|
||||
PF.PLEX_TYPE_ALBUM: 'add_updateAlbum',
|
||||
PF.PLEX_TYPE_SONG: 'add_updateSong'
|
||||
}
|
||||
urlArgs = {
|
||||
'MusicArtist': {'type': 8},
|
||||
'MusicAlbum': {'type': 9},
|
||||
'Audio': {'type': 10}
|
||||
PF.PLEX_TYPE_ARTIST: {'type': 8},
|
||||
PF.PLEX_TYPE_ALBUM: {'type': 9},
|
||||
PF.PLEX_TYPE_SONG: {'type': 10}
|
||||
}
|
||||
|
||||
# Process artist, then album and tracks last to minimize overhead
|
||||
for kind in ('MusicArtist', 'MusicAlbum', 'Audio'):
|
||||
for kind in (PF.PLEX_TYPE_ARTIST,
|
||||
PF.PLEX_TYPE_ALBUM,
|
||||
PF.PLEX_TYPE_SONG):
|
||||
if self.threadStopped():
|
||||
return False
|
||||
log.debug("Start processing music %s" % kind)
|
||||
|
@ -1318,10 +1326,10 @@ class LibrarySync(Thread):
|
|||
|
||||
# Get a list of items already existing in Kodi db
|
||||
if self.compare:
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
# Pull the list of items already in Kodi
|
||||
try:
|
||||
elements = dict(emby_db.getChecksum(kind))
|
||||
elements = dict(plex_db.getChecksum(kind))
|
||||
self.allKodiElementsId.update(elements)
|
||||
# Yet empty/nothing yet synched
|
||||
except ValueError:
|
||||
|
@ -1569,14 +1577,14 @@ class LibrarySync(Thread):
|
|||
where
|
||||
"""
|
||||
items = []
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
for item in data:
|
||||
# Drop buffering messages immediately
|
||||
state = item.get('state')
|
||||
if state == 'buffering':
|
||||
continue
|
||||
ratingKey = item.get('ratingKey')
|
||||
kodiInfo = emby_db.getItem_byId(ratingKey)
|
||||
kodiInfo = plex_db.getItem_byId(ratingKey)
|
||||
if kodiInfo is None:
|
||||
# Item not (yet) in Kodi library
|
||||
continue
|
||||
|
@ -1663,7 +1671,7 @@ class LibrarySync(Thread):
|
|||
# Now tell Kodi where we are
|
||||
for item in items:
|
||||
itemFkt = getattr(itemtypes,
|
||||
PF.ITEMTYPE_FROM_PLEXTYPE[item['kodi_type']])
|
||||
PF.ITEMTYPE_FROM_KODITYPE[item['kodi_type']])
|
||||
with itemFkt() as Fkt:
|
||||
Fkt.updatePlaystate(item)
|
||||
|
||||
|
@ -1675,12 +1683,12 @@ class LibrarySync(Thread):
|
|||
"""
|
||||
items = []
|
||||
typus = {
|
||||
'Movie': 'Movies',
|
||||
'Series': 'TVShows'
|
||||
PF.PLEX_TYPE_MOVIE: 'Movies',
|
||||
PF.PLEX_TYPE_SHOW: 'TVShows'
|
||||
}
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
for plextype in typus:
|
||||
items.extend(emby_db.itemsByType(plextype))
|
||||
items.extend(plex_db.itemsByType(plextype))
|
||||
# Shuffle the list to not always start out identically
|
||||
shuffle(items)
|
||||
for item in items:
|
||||
|
@ -1720,7 +1728,8 @@ class LibrarySync(Thread):
|
|||
|
||||
xbmcplayer = xbmc.Player()
|
||||
|
||||
queue = self.queue
|
||||
# Link to Websocket queue
|
||||
queue = self.mgr.ws.queue
|
||||
|
||||
startupComplete = False
|
||||
self.views = []
|
||||
|
|
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
|
100
resources/lib/playback_starter.py
Normal file
100
resources/lib/playback_starter.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
# -*- 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, PLEX_TYPE_PHOTO, \
|
||||
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE
|
||||
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)
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (TypeError, AttributeError):
|
||||
log.error('Could not get a PMS xml for plex id %s' % plex_id)
|
||||
return
|
||||
api = API(xml[0])
|
||||
if api.getType() == PLEX_TYPE_PHOTO:
|
||||
# Photo
|
||||
result = Playback_Successful()
|
||||
listitem = PKC_ListItem()
|
||||
listitem = api.CreateListItemFromPlexItem(listitem)
|
||||
api.AddStreamInfo(listitem)
|
||||
api.set_listitem_artwork(listitem)
|
||||
result.listitem = listitem
|
||||
else:
|
||||
# Video and Music
|
||||
playqueue = self.playqueue.get_playqueue_from_type(
|
||||
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()])
|
||||
with lock:
|
||||
result = PlaybackUtils(xml, playqueue).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,20 +3,25 @@
|
|||
###############################################################################
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from urllib import urlencode
|
||||
from threading import Thread
|
||||
|
||||
import xbmc
|
||||
from xbmc import getCondVisibility, Player
|
||||
import xbmcgui
|
||||
import xbmcplugin
|
||||
|
||||
import playutils as putils
|
||||
import playlist
|
||||
from utils import window, settings, tryEncode, tryDecode
|
||||
import downloadutils
|
||||
|
||||
import PlexAPI
|
||||
import PlexFunctions as PF
|
||||
from PlexAPI import API
|
||||
from PlexFunctions import GetPlexPlaylist, KODITYPE_FROM_PLEXTYPE, \
|
||||
PLEX_TYPE_CLIP, PLEX_TYPE_MOVIE
|
||||
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 pickler import Playback_Successful
|
||||
from plexdb_functions import Get_Plex_DB
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -29,74 +34,72 @@ addonName = "PlexKodiConnect"
|
|||
|
||||
class PlaybackUtils():
|
||||
|
||||
def __init__(self, item):
|
||||
def __init__(self, xml, playqueue):
|
||||
self.xml = xml
|
||||
self.playqueue = playqueue
|
||||
|
||||
self.item = item
|
||||
self.API = PlexAPI.API(item)
|
||||
|
||||
self.userid = window('currUserId')
|
||||
self.server = window('pms_server')
|
||||
|
||||
if self.API.getType() == 'track':
|
||||
self.pl = playlist.Playlist(typus='music')
|
||||
else:
|
||||
self.pl = playlist.Playlist(typus='video')
|
||||
|
||||
def play(self, itemid, dbid=None):
|
||||
|
||||
item = self.item
|
||||
# Hack to get only existing entry in PMS response for THIS instance of
|
||||
# playbackutils :-)
|
||||
self.API = PlexAPI.API(item[0])
|
||||
API = self.API
|
||||
listitem = xbmcgui.ListItem()
|
||||
playutils = putils.PlayUtils(item[0])
|
||||
|
||||
log.info("Play called.")
|
||||
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.xml[0]
|
||||
api = API(item)
|
||||
playqueue = self.playqueue
|
||||
xml = None
|
||||
result = Playback_Successful()
|
||||
listitem = ListItem()
|
||||
playutils = putils.PlayUtils(item)
|
||||
playurl = playutils.getPlayUrl()
|
||||
if not playurl:
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||
log.error('No playurl found, aborting')
|
||||
return
|
||||
|
||||
if dbid in (None, 'plextrailer', 'plexnode'):
|
||||
# Item is not in Kodi database, is a trailer or plex redirect
|
||||
if kodi_id in (None, 'plextrailer', 'plexnode'):
|
||||
# Item is not in Kodi database, is a trailer/clip or plex redirect
|
||||
# e.g. plex.tv watch later
|
||||
API.CreateListItemFromPlexItem(listitem)
|
||||
self.setArtwork(listitem)
|
||||
if dbid == 'plexnode':
|
||||
api.CreateListItemFromPlexItem(listitem)
|
||||
api.set_listitem_artwork(listitem)
|
||||
if kodi_id == 'plexnode':
|
||||
# Need to get yet another xml to get final url
|
||||
window('emby_%s.playmethod' % playurl, clear=True)
|
||||
window('plex_%s.playmethod' % playurl, clear=True)
|
||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||
'{server}%s' % item[0][0][0].attrib.get('key'))
|
||||
if xml in (None, 401):
|
||||
'{server}%s' % item[0][0].attrib.get('key'))
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (TypeError, AttributeError):
|
||||
log.error('Could not download %s'
|
||||
% item[0][0][0].attrib.get('key'))
|
||||
return xbmcplugin.setResolvedUrl(
|
||||
int(sys.argv[1]), False, listitem)
|
||||
% item[0][0].attrib.get('key'))
|
||||
return
|
||||
playurl = tryEncode(xml[0].attrib.get('key'))
|
||||
window('emby_%s.playmethod' % playurl, value='DirectStream')
|
||||
window('plex_%s.playmethod' % playurl, value='DirectStream')
|
||||
|
||||
playmethod = window('emby_%s.playmethod' % playurl)
|
||||
playmethod = window('plex_%s.playmethod' % playurl)
|
||||
if playmethod == "Transcode":
|
||||
window('emby_%s.playmethod' % playurl, clear=True)
|
||||
window('plex_%s.playmethod' % playurl, clear=True)
|
||||
playurl = tryEncode(playutils.audioSubsPref(
|
||||
listitem, tryDecode(playurl)))
|
||||
window('emby_%s.playmethod' % playurl, "Transcode")
|
||||
window('plex_%s.playmethod' % playurl, "Transcode")
|
||||
listitem.setPath(playurl)
|
||||
self.setProperties(playurl, listitem)
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
api.set_playback_win_props(playurl, listitem)
|
||||
result.listitem = listitem
|
||||
return result
|
||||
|
||||
############### ORGANIZE CURRENT PLAYLIST ################
|
||||
kodi_type = KODITYPE_FROM_PLEXTYPE[api.getType()]
|
||||
kodi_id = int(kodi_id)
|
||||
|
||||
# ORGANIZE CURRENT PLAYLIST ################
|
||||
contextmenu_play = window('plex_contextplay') == 'true'
|
||||
window('plex_contextplay', clear=True)
|
||||
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
|
||||
kodiPl = self.pl.playlist
|
||||
sizePlaylist = kodiPl.size()
|
||||
homeScreen = getCondVisibility('Window.IsActive(home)')
|
||||
sizePlaylist = len(playqueue.items)
|
||||
if contextmenu_play:
|
||||
# Need to start with the items we're inserting here
|
||||
startPos = sizePlaylist
|
||||
else:
|
||||
# Can return -1
|
||||
startPos = max(kodiPl.getposition(), 0)
|
||||
startPos = max(playqueue.kodi_pl.getposition(), 0)
|
||||
self.currentPosition = startPos
|
||||
|
||||
propertiesPlayback = window('plex_playbackProps') == "true"
|
||||
|
@ -108,72 +111,106 @@ class PlaybackUtils():
|
|||
log.info("Playlist plugin position: %s" % self.currentPosition)
|
||||
log.info("Playlist size: %s" % sizePlaylist)
|
||||
|
||||
############### RESUME POINT ################
|
||||
seektime, runtime = API.getRuntime()
|
||||
# RESUME POINT ################
|
||||
seektime, runtime = api.getRuntime()
|
||||
if window('plex_customplaylist.seektime'):
|
||||
# Already got seektime, e.g. from playqueue & Plex companion
|
||||
seektime = int(window('plex_customplaylist.seektime'))
|
||||
|
||||
# We need to ensure we add the intro and additional parts only once.
|
||||
# Otherwise we get a loop.
|
||||
if not propertiesPlayback:
|
||||
|
||||
window('plex_playbackProps', value="true")
|
||||
log.info("Setting up properties in playlist.")
|
||||
# Where will the player need to start?
|
||||
# Do we need to get trailers?
|
||||
trailers = False
|
||||
if (api.getType() == PLEX_TYPE_MOVIE and
|
||||
not seektime and
|
||||
sizePlaylist < 2 and
|
||||
settings('enableCinema') == "true"):
|
||||
if settings('askCinema') == "true":
|
||||
trailers = xbmcgui.Dialog().yesno(
|
||||
addonName,
|
||||
"Play trailers?")
|
||||
else:
|
||||
trailers = True
|
||||
# Post to the PMS. REUSE THE PLAYQUEUE!
|
||||
xml = GetPlexPlaylist(
|
||||
plex_id,
|
||||
plex_lib_UUID,
|
||||
mediatype=api.getType(),
|
||||
trailers=trailers)
|
||||
get_playlist_details_from_xml(playqueue, xml=xml)
|
||||
|
||||
if (not homeScreen and not seektime and
|
||||
if (not homeScreen and not seektime and sizePlaylist < 2 and
|
||||
window('plex_customplaylist') != "true" and
|
||||
not contextmenu_play):
|
||||
# Need to add a dummy file because the first item will fail
|
||||
log.debug("Adding dummy file to playlist.")
|
||||
dummyPlaylist = True
|
||||
kodiPl.add(playurl, listitem, index=startPos)
|
||||
add_listitem_to_Kodi_playlist(
|
||||
playqueue,
|
||||
startPos,
|
||||
xbmcgui.ListItem(),
|
||||
playurl,
|
||||
xml[0])
|
||||
# Remove the original item from playlist
|
||||
self.pl.removefromPlaylist(startPos+1)
|
||||
# Readd the original item to playlist - via jsonrpc so we have full metadata
|
||||
self.pl.insertintoPlaylist(
|
||||
remove_from_Kodi_playlist(
|
||||
playqueue,
|
||||
startPos+1)
|
||||
# Readd the original item to playlist - via jsonrpc so we have
|
||||
# full metadata
|
||||
add_item_to_kodi_playlist(
|
||||
playqueue,
|
||||
self.currentPosition+1,
|
||||
dbid,
|
||||
PF.KODITYPE_FROM_PLEXTYPE[API.getType()])
|
||||
kodi_id=kodi_id,
|
||||
kodi_type=kodi_type,
|
||||
file=playurl)
|
||||
self.currentPosition += 1
|
||||
|
||||
############### -- CHECK FOR INTROS ################
|
||||
if (settings('enableCinema') == "true" and not seektime):
|
||||
# if we have any play them when the movie/show is not being resumed
|
||||
xml = PF.GetPlexPlaylist(
|
||||
itemid,
|
||||
item.attrib.get('librarySectionUUID'),
|
||||
mediatype=API.getType())
|
||||
introsPlaylist = self.AddTrailers(xml)
|
||||
|
||||
############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ##############
|
||||
# -- ADD TRAILERS ################
|
||||
if trailers:
|
||||
for i, item in enumerate(xml):
|
||||
if i == len(xml) - 1:
|
||||
# Don't add the main movie itself
|
||||
break
|
||||
self.add_trailer(item)
|
||||
introsPlaylist = True
|
||||
|
||||
# -- ADD MAIN ITEM ONLY FOR HOMESCREEN ##############
|
||||
if homeScreen and not seektime and not sizePlaylist:
|
||||
# Extend our current playlist with the actual item to play
|
||||
# only if there's no playlist first
|
||||
log.info("Adding main item to playlist.")
|
||||
self.pl.addtoPlaylist(
|
||||
dbid,
|
||||
PF.KODITYPE_FROM_PLEXTYPE[API.getType()])
|
||||
add_item_to_kodi_playlist(
|
||||
playqueue,
|
||||
self.currentPosition,
|
||||
kodi_id,
|
||||
kodi_type)
|
||||
|
||||
elif contextmenu_play:
|
||||
if window('useDirectPaths') == 'true':
|
||||
# Cannot add via JSON with full metadata because then we
|
||||
# Would be using the direct path
|
||||
log.debug("Adding contextmenu item for direct paths")
|
||||
if window('emby_%s.playmethod' % playurl) == "Transcode":
|
||||
window('emby_%s.playmethod' % playurl,
|
||||
if window('plex_%s.playmethod' % playurl) == "Transcode":
|
||||
window('plex_%s.playmethod' % playurl,
|
||||
clear=True)
|
||||
playurl = tryEncode(playutils.audioSubsPref(
|
||||
listitem, tryDecode(playurl)))
|
||||
window('emby_%s.playmethod' % playurl,
|
||||
window('plex_%s.playmethod' % playurl,
|
||||
value="Transcode")
|
||||
self.setProperties(playurl, listitem)
|
||||
self.setArtwork(listitem)
|
||||
API.CreateListItemFromPlexItem(listitem)
|
||||
api.CreateListItemFromPlexItem(listitem)
|
||||
api.set_playback_win_props(playurl, listitem)
|
||||
api.set_listitem_artwork(listitem)
|
||||
kodiPl.add(playurl, listitem, index=self.currentPosition+1)
|
||||
else:
|
||||
# Full metadata
|
||||
self.pl.insertintoPlaylist(
|
||||
self.currentPosition+1,
|
||||
dbid,
|
||||
PF.KODITYPE_FROM_PLEXTYPE[API.getType()])
|
||||
kodi_id,
|
||||
kodi_type)
|
||||
self.currentPosition += 1
|
||||
if seektime:
|
||||
window('plex_customplaylist.seektime', value=str(seektime))
|
||||
|
@ -181,177 +218,145 @@ class PlaybackUtils():
|
|||
# Ensure that additional parts are played after the main item
|
||||
self.currentPosition += 1
|
||||
|
||||
############### -- CHECK FOR ADDITIONAL PARTS ################
|
||||
if len(item[0][0]) > 1:
|
||||
# Only add to the playlist after intros have played
|
||||
for counter, part in enumerate(item[0][0]):
|
||||
# Never add first part
|
||||
if counter == 0:
|
||||
continue
|
||||
# Set listitem and properties for each additional parts
|
||||
API.setPartNumber(counter)
|
||||
additionalListItem = xbmcgui.ListItem()
|
||||
additionalPlayurl = playutils.getPlayUrl(
|
||||
partNumber=counter)
|
||||
log.debug("Adding additional part: %s, url: %s"
|
||||
% (counter, additionalPlayurl))
|
||||
|
||||
self.setProperties(additionalPlayurl, additionalListItem)
|
||||
self.setArtwork(additionalListItem)
|
||||
# NEW to Plex
|
||||
API.CreateListItemFromPlexItem(additionalListItem)
|
||||
|
||||
kodiPl.add(additionalPlayurl, additionalListItem,
|
||||
index=self.currentPosition)
|
||||
self.pl.verifyPlaylist()
|
||||
self.currentPosition += 1
|
||||
API.setPartNumber(0)
|
||||
# -- CHECK FOR ADDITIONAL PARTS ################
|
||||
if len(item[0]) > 1:
|
||||
self.add_part(item, api, kodi_id, kodi_type)
|
||||
|
||||
if dummyPlaylist:
|
||||
# Added a dummy file to the playlist,
|
||||
# because the first item is going to fail automatically.
|
||||
log.info("Processed as a playlist. First item is skipped.")
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||
# Delete the item that's gonna fail!
|
||||
del playqueue.items[startPos]
|
||||
# Don't attach listitem
|
||||
return result
|
||||
|
||||
# We just skipped adding properties. Reset flag for next time.
|
||||
elif propertiesPlayback:
|
||||
log.debug("Resetting properties playback flag.")
|
||||
window('plex_playbackProps', clear=True)
|
||||
|
||||
#self.pl.verifyPlaylist()
|
||||
########## SETUP MAIN ITEM ##########
|
||||
# SETUP MAIN ITEM ##########
|
||||
# For transcoding only, ask for audio/subs pref
|
||||
if (window('emby_%s.playmethod' % playurl) == "Transcode" and
|
||||
if (window('plex_%s.playmethod' % playurl) == "Transcode" and
|
||||
not contextmenu_play):
|
||||
window('emby_%s.playmethod' % playurl, clear=True)
|
||||
window('plex_%s.playmethod' % playurl, clear=True)
|
||||
playurl = tryEncode(playutils.audioSubsPref(
|
||||
listitem, tryDecode(playurl)))
|
||||
window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||
window('plex_%s.playmethod' % playurl, value="Transcode")
|
||||
|
||||
listitem.setPath(playurl)
|
||||
self.setProperties(playurl, listitem)
|
||||
api.set_playback_win_props(playurl, listitem)
|
||||
api.set_listitem_artwork(listitem)
|
||||
|
||||
############### PLAYBACK ################
|
||||
# PLAYBACK ################
|
||||
if (homeScreen and seektime and window('plex_customplaylist') != "true"
|
||||
and not contextmenu_play):
|
||||
log.info("Play as a widget item.")
|
||||
API.CreateListItemFromPlexItem(listitem)
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
log.info("Play as a widget item")
|
||||
api.CreateListItemFromPlexItem(listitem)
|
||||
result.listitem = listitem
|
||||
return result
|
||||
|
||||
elif ((introsPlaylist and window('plex_customplaylist') == "true") or
|
||||
(homeScreen and not sizePlaylist) or
|
||||
contextmenu_play):
|
||||
# Playlist was created just now, play it.
|
||||
# Contextmenu plays always need this
|
||||
log.info("Play playlist.")
|
||||
xbmcplugin.endOfDirectory(int(sys.argv[1]), True, False, False)
|
||||
xbmc.Player().play(kodiPl, startpos=startPos)
|
||||
|
||||
log.info("Play playlist from starting position %s" % startPos)
|
||||
# Need a separate thread because Player won't return in time
|
||||
thread = Thread(target=Player().play,
|
||||
args=(playqueue.kodi_pl, None, False, startPos))
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
# Don't attach listitem
|
||||
return result
|
||||
else:
|
||||
log.info("Play as a regular item.")
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
log.info("Play as a regular item")
|
||||
result.listitem = listitem
|
||||
return result
|
||||
|
||||
def AddTrailers(self, xml):
|
||||
def play_all(self):
|
||||
"""
|
||||
Adds trailers to a movie, if applicable. Returns True if trailers were
|
||||
added
|
||||
Play all items contained in the xml passed in. Called by Plex Companion
|
||||
"""
|
||||
# Failure when downloading trailer playQueue
|
||||
if xml in (None, 401):
|
||||
return False
|
||||
# Failure when getting trailers, e.g. when no plex pass
|
||||
if xml.attrib.get('size') == '1':
|
||||
return False
|
||||
|
||||
if settings('askCinema') == "true":
|
||||
resp = xbmcgui.Dialog().yesno(addonName, "Play trailers?")
|
||||
if not resp:
|
||||
# User selected to not play trailers
|
||||
log.info("Skip trailers.")
|
||||
return False
|
||||
log.info("Playbackutils play_all called")
|
||||
window('plex_playbackProps', value="true")
|
||||
self.currentPosition = 0
|
||||
for item in self.xml:
|
||||
api = API(item)
|
||||
if api.getType() == PLEX_TYPE_CLIP:
|
||||
self.add_trailer(item)
|
||||
continue
|
||||
with Get_Plex_DB() as plex_db:
|
||||
db_item = plex_db.getItem_byId(api.getRatingKey())
|
||||
try:
|
||||
add_item_to_kodi_playlist(self.playqueue,
|
||||
self.currentPosition,
|
||||
kodi_id=db_item[0],
|
||||
kodi_type=db_item[4])
|
||||
self.currentPosition += 1
|
||||
if len(item[0]) > 1:
|
||||
self.add_part(item,
|
||||
api,
|
||||
db_item[0],
|
||||
db_item[4])
|
||||
except TypeError:
|
||||
# Item not in Kodi DB
|
||||
self.add_trailer(item)
|
||||
continue
|
||||
|
||||
def add_trailer(self, item):
|
||||
# Playurl needs to point back so we can get metadata!
|
||||
path = "plugin://plugin.video.plexkodiconnect.movies/"
|
||||
path = "plugin://plugin.video.plexkodiconnect/movies/"
|
||||
params = {
|
||||
'mode': "play",
|
||||
'dbid': 'plextrailer'
|
||||
}
|
||||
for counter, intro in enumerate(xml):
|
||||
# Don't process the last item - it's the original movie
|
||||
if counter == len(xml)-1:
|
||||
break
|
||||
# The server randomly returns intros, process them.
|
||||
# introListItem = xbmcgui.ListItem()
|
||||
# introPlayurl = putils.PlayUtils(intro).getPlayUrl()
|
||||
introAPI = PlexAPI.API(intro)
|
||||
introAPI = API(item)
|
||||
listitem = introAPI.CreateListItemFromPlexItem()
|
||||
params['id'] = introAPI.getRatingKey()
|
||||
params['filename'] = introAPI.getKey()
|
||||
introPlayurl = path + '?' + urlencode(params)
|
||||
log.info("Adding Intro: %s" % introPlayurl)
|
||||
|
||||
self.pl.insertintoPlaylist(self.currentPosition, url=introPlayurl)
|
||||
introAPI.set_listitem_artwork(listitem)
|
||||
# Overwrite the Plex url
|
||||
listitem.setPath(introPlayurl)
|
||||
log.info("Adding Plex trailer: %s" % introPlayurl)
|
||||
add_listitem_to_Kodi_playlist(
|
||||
self.playqueue,
|
||||
self.currentPosition,
|
||||
listitem,
|
||||
introPlayurl,
|
||||
xml_video_element=item)
|
||||
self.currentPosition += 1
|
||||
|
||||
return True
|
||||
|
||||
def setProperties(self, playurl, listitem):
|
||||
# Set all properties necessary for plugin path playback
|
||||
itemid = self.API.getRatingKey()
|
||||
itemtype = self.API.getType()
|
||||
userdata = self.API.getUserData()
|
||||
|
||||
embyitem = "emby_%s" % playurl
|
||||
window('%s.runtime' % embyitem, value=str(userdata['Runtime']))
|
||||
window('%s.type' % embyitem, value=itemtype)
|
||||
window('%s.itemid' % embyitem, value=itemid)
|
||||
window('%s.playcount' % embyitem, value=str(userdata['PlayCount']))
|
||||
|
||||
if itemtype == "episode":
|
||||
window('%s.refreshid' % embyitem,
|
||||
value=self.API.getParentRatingKey())
|
||||
else:
|
||||
window('%s.refreshid' % embyitem, value=itemid)
|
||||
|
||||
# Append external subtitles to stream
|
||||
playmethod = window('%s.playmethod' % embyitem)
|
||||
if playmethod in ("DirectStream", "DirectPlay"):
|
||||
subtitles = self.API.externalSubs(playurl)
|
||||
listitem.setSubtitles(subtitles)
|
||||
|
||||
self.setArtwork(listitem)
|
||||
|
||||
def setArtwork(self, listItem):
|
||||
allartwork = self.API.getAllArtwork(parentInfo=True)
|
||||
arttypes = {
|
||||
'poster': "Primary",
|
||||
'tvshow.poster': "Thumb",
|
||||
'clearart': "Art",
|
||||
'tvshow.clearart': "Art",
|
||||
'clearart': "Primary",
|
||||
'tvshow.clearart': "Primary",
|
||||
'clearlogo': "Logo",
|
||||
'tvshow.clearlogo': "Logo",
|
||||
'discart': "Disc",
|
||||
'fanart_image': "Backdrop",
|
||||
'landscape': "Backdrop",
|
||||
"banner": "Banner"
|
||||
}
|
||||
for arttype in arttypes:
|
||||
art = arttypes[arttype]
|
||||
if art == "Backdrop":
|
||||
try:
|
||||
# Backdrop is a list, grab the first backdrop
|
||||
self.setArtProp(listItem, arttype, allartwork[art][0])
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
self.setArtProp(listItem, arttype, allartwork[art])
|
||||
|
||||
def setArtProp(self, listItem, arttype, path):
|
||||
if arttype in (
|
||||
'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
|
||||
'medium_landscape', 'medium_poster', 'small_fanartimage',
|
||||
'medium_fanartimage', 'fanart_noindicators'):
|
||||
listItem.setProperty(arttype, path)
|
||||
else:
|
||||
listItem.setArt({arttype: path})
|
||||
def add_part(self, item, api, kodi_id, kodi_type):
|
||||
"""
|
||||
Adds an additional part to the playlist
|
||||
"""
|
||||
# Only add to the playlist after intros have played
|
||||
for counter, part in enumerate(item[0]):
|
||||
# Never add first part
|
||||
if counter == 0:
|
||||
continue
|
||||
# Set listitem and properties for each additional parts
|
||||
api.setPartNumber(counter)
|
||||
additionalListItem = xbmcgui.ListItem()
|
||||
playutils = putils.PlayUtils(item)
|
||||
additionalPlayurl = playutils.getPlayUrl(
|
||||
partNumber=counter)
|
||||
log.debug("Adding additional part: %s, url: %s"
|
||||
% (counter, additionalPlayurl))
|
||||
api.CreateListItemFromPlexItem(additionalListItem)
|
||||
api.set_playback_win_props(additionalPlayurl,
|
||||
additionalListItem)
|
||||
api.set_listitem_artwork(additionalListItem)
|
||||
add_listitem_to_playlist(
|
||||
self.playqueue,
|
||||
self.currentPosition,
|
||||
additionalListItem,
|
||||
kodi_id=kodi_id,
|
||||
kodi_type=kodi_type,
|
||||
plex_id=api.getRatingKey(),
|
||||
file=additionalPlayurl)
|
||||
self.currentPosition += 1
|
||||
api.setPartNumber(0)
|
||||
|
|
|
@ -11,8 +11,9 @@ from utils import window, settings, language as lang, DateToKodi, \
|
|||
getUnixTimestamp
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import embydb_functions as embydb
|
||||
import plexdb_functions as plexdb
|
||||
import kodidb_functions as kodidb
|
||||
from PlexFunctions import KODI_TYPE_MOVIE, KODI_TYPE_EPISODE
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -76,11 +77,11 @@ class Player(xbmc.Player):
|
|||
self.currentFile = currentFile
|
||||
window('plex_lastPlayedFiled', value=currentFile)
|
||||
# We may need to wait for info to be set in kodi monitor
|
||||
itemId = window("emby_%s.itemid" % currentFile)
|
||||
itemId = window("plex_%s.itemid" % currentFile)
|
||||
count = 0
|
||||
while not itemId:
|
||||
xbmc.sleep(200)
|
||||
itemId = window("emby_%s.itemid" % currentFile)
|
||||
itemId = window("plex_%s.itemid" % currentFile)
|
||||
if count == 5:
|
||||
log.warn("Could not find itemId, cancelling playback report!")
|
||||
return
|
||||
|
@ -88,16 +89,16 @@ class Player(xbmc.Player):
|
|||
|
||||
log.info("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId))
|
||||
|
||||
embyitem = "emby_%s" % currentFile
|
||||
runtime = window("%s.runtime" % embyitem)
|
||||
refresh_id = window("%s.refreshid" % embyitem)
|
||||
playMethod = window("%s.playmethod" % embyitem)
|
||||
itemType = window("%s.type" % embyitem)
|
||||
plexitem = "plex_%s" % currentFile
|
||||
runtime = window("%s.runtime" % plexitem)
|
||||
refresh_id = window("%s.refreshid" % plexitem)
|
||||
playMethod = window("%s.playmethod" % plexitem)
|
||||
itemType = window("%s.type" % plexitem)
|
||||
try:
|
||||
playcount = int(window("%s.playcount" % embyitem))
|
||||
playcount = int(window("%s.playcount" % plexitem))
|
||||
except ValueError:
|
||||
playcount = 0
|
||||
window('emby_skipWatched%s' % itemId, value="true")
|
||||
window('plex_skipWatched%s' % itemId, value="true")
|
||||
|
||||
log.debug("Playing itemtype is: %s" % itemType)
|
||||
|
||||
|
@ -134,7 +135,7 @@ class Player(xbmc.Player):
|
|||
volume = result.get('volume')
|
||||
muted = result.get('muted')
|
||||
|
||||
# Postdata structure to send to Emby server
|
||||
# Postdata structure to send to plex server
|
||||
url = "{server}/:/timeline?"
|
||||
postdata = {
|
||||
|
||||
|
@ -154,7 +155,7 @@ class Player(xbmc.Player):
|
|||
postdata['AudioStreamIndex'] = window("%sAudioStreamIndex" % currentFile)
|
||||
postdata['SubtitleStreamIndex'] = window("%sSubtitleStreamIndex" % currentFile)
|
||||
else:
|
||||
# Get the current kodi audio and subtitles and convert to Emby equivalent
|
||||
# Get the current kodi audio and subtitles and convert to plex equivalent
|
||||
tracks_query = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
|
@ -190,9 +191,9 @@ class Player(xbmc.Player):
|
|||
# Postdata for the subtitles
|
||||
if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
|
||||
|
||||
# Number of audiotracks to help get Emby Index
|
||||
# Number of audiotracks to help get plex Index
|
||||
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
|
||||
mapping = window("%s.indexMapping" % embyitem)
|
||||
mapping = window("%s.indexMapping" % plexitem)
|
||||
|
||||
if mapping: # Set in playbackutils.py
|
||||
|
||||
|
@ -229,10 +230,10 @@ class Player(xbmc.Player):
|
|||
log.error('Could not get kodi runtime, setting to zero')
|
||||
runtime = 0
|
||||
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
emby_dbitem = emby_db.getItem_byId(itemId)
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
plex_dbitem = plex_db.getItem_byId(itemId)
|
||||
try:
|
||||
fileid = emby_dbitem[1]
|
||||
fileid = plex_dbitem[1]
|
||||
except TypeError:
|
||||
log.info("Could not find fileid in plex db.")
|
||||
fileid = None
|
||||
|
@ -338,7 +339,7 @@ class Player(xbmc.Player):
|
|||
playMethod = data['playmethod']
|
||||
|
||||
# Prevent manually mark as watched in Kodi monitor
|
||||
window('emby_skipWatched%s' % itemid, value="true")
|
||||
window('plex_skipWatched%s' % itemid, value="true")
|
||||
|
||||
if currentPosition and runtime:
|
||||
try:
|
||||
|
@ -353,7 +354,7 @@ class Player(xbmc.Player):
|
|||
if percentComplete >= markPlayed:
|
||||
# Tell Kodi that we've finished watching (Plex knows)
|
||||
if (data['fileid'] is not None and
|
||||
data['itemType'] in ('movie', 'episode')):
|
||||
data['itemType'] in (KODI_TYPE_MOVIE, KODI_TYPE_EPISODE)):
|
||||
with kodidb.GetKodiDB('video') as kodi_db:
|
||||
kodi_db.addPlaystate(
|
||||
data['fileid'],
|
||||
|
@ -391,13 +392,13 @@ class Player(xbmc.Player):
|
|||
# Clean the WINDOW properties
|
||||
for filename in self.played_info:
|
||||
cleanup = (
|
||||
'emby_%s.itemid' % filename,
|
||||
'emby_%s.runtime' % filename,
|
||||
'emby_%s.refreshid' % filename,
|
||||
'emby_%s.playmethod' % filename,
|
||||
'emby_%s.type' % filename,
|
||||
'emby_%s.runtime' % filename,
|
||||
'emby_%s.playcount' % filename,
|
||||
'plex_%s.itemid' % filename,
|
||||
'plex_%s.runtime' % filename,
|
||||
'plex_%s.refreshid' % filename,
|
||||
'plex_%s.playmethod' % filename,
|
||||
'plex_%s.type' % filename,
|
||||
'plex_%s.runtime' % filename,
|
||||
'plex_%s.playcount' % filename,
|
||||
'plex_%s.playlistPosition' % filename
|
||||
)
|
||||
for item in cleanup:
|
||||
|
|
|
@ -1,337 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
import json
|
||||
from urllib import urlencode
|
||||
from threading import Lock
|
||||
from functools import wraps
|
||||
|
||||
import xbmc
|
||||
|
||||
import embydb_functions as embydb
|
||||
from utils import window, tryEncode
|
||||
import playbackutils
|
||||
import PlexFunctions
|
||||
import PlexAPI
|
||||
|
||||
###############################################################################
|
||||
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
class lockMethod:
|
||||
"""
|
||||
Decorator for class methods to lock hem completely. Same lock is used for
|
||||
every single decorator and instance used!
|
||||
|
||||
Here only used for Playlist()
|
||||
"""
|
||||
lock = Lock()
|
||||
|
||||
@classmethod
|
||||
def decorate(cls, func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
with cls.lock:
|
||||
result = func(*args, **kwargs)
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
|
||||
class Playlist():
|
||||
"""
|
||||
Initiate with Playlist(typus='video' or 'music')
|
||||
"""
|
||||
# Borg - multiple instances, shared state
|
||||
_shared_state = {}
|
||||
|
||||
typus = None
|
||||
queueId = None
|
||||
playQueueVersion = None
|
||||
guid = None
|
||||
playlistId = None
|
||||
player = xbmc.Player()
|
||||
# "interal" PKC playlist
|
||||
items = []
|
||||
|
||||
@lockMethod.decorate
|
||||
def __init__(self, typus=None):
|
||||
# Borg
|
||||
self.__dict__ = self._shared_state
|
||||
|
||||
self.userid = window('currUserId')
|
||||
self.server = window('pms_server')
|
||||
# Construct the Kodi playlist instance
|
||||
if self.typus == typus:
|
||||
return
|
||||
if typus == 'video':
|
||||
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
self.typus = 'video'
|
||||
log.info('Initiated video playlist')
|
||||
elif typus == 'music':
|
||||
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
||||
self.typus = 'music'
|
||||
log.info('Initiated music playlist')
|
||||
else:
|
||||
self.playlist = None
|
||||
self.typus = None
|
||||
log.info('Empty playlist initiated')
|
||||
if self.playlist is not None:
|
||||
self.playlistId = self.playlist.getPlayListId()
|
||||
|
||||
@lockMethod.decorate
|
||||
def getQueueIdFromPosition(self, playlistPosition):
|
||||
return self.items[playlistPosition]['playQueueItemID']
|
||||
|
||||
@lockMethod.decorate
|
||||
def Typus(self, value=None):
|
||||
if value:
|
||||
self.typus = value
|
||||
else:
|
||||
return self.typus
|
||||
|
||||
@lockMethod.decorate
|
||||
def PlayQueueVersion(self, value=None):
|
||||
if value:
|
||||
self.playQueueVersion = value
|
||||
else:
|
||||
return self.playQueueVersion
|
||||
|
||||
@lockMethod.decorate
|
||||
def QueueId(self, value=None):
|
||||
if value:
|
||||
self.queueId = value
|
||||
else:
|
||||
return self.queueId
|
||||
|
||||
@lockMethod.decorate
|
||||
def Guid(self, value=None):
|
||||
if value:
|
||||
self.guid = value
|
||||
else:
|
||||
return self.guid
|
||||
|
||||
@lockMethod.decorate
|
||||
def clear(self):
|
||||
"""
|
||||
Empties current Kodi playlist and associated variables
|
||||
"""
|
||||
log.info('Clearing playlist')
|
||||
self.playlist.clear()
|
||||
self.items = []
|
||||
self.queueId = None
|
||||
self.playQueueVersion = None
|
||||
self.guid = None
|
||||
|
||||
def _initiatePlaylist(self):
|
||||
log.info('Initiating playlist')
|
||||
playlist = None
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
for item in self.items:
|
||||
itemid = item['plexId']
|
||||
embydb_item = emby_db.getItem_byId(itemid)
|
||||
try:
|
||||
mediatype = embydb_item[4]
|
||||
except TypeError:
|
||||
log.info('Couldnt find item %s in Kodi db' % itemid)
|
||||
item = PlexFunctions.GetPlexMetadata(itemid)
|
||||
if item in (None, 401):
|
||||
log.info('Couldnt find item %s on PMS, trying next'
|
||||
% itemid)
|
||||
continue
|
||||
if PlexAPI.API(item[0]).getType() == 'track':
|
||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
||||
log.info('Music playlist initiated')
|
||||
self.typus = 'music'
|
||||
else:
|
||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
log.info('Video playlist initiated')
|
||||
self.typus = 'video'
|
||||
else:
|
||||
if mediatype == 'song':
|
||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
||||
log.info('Music playlist initiated')
|
||||
self.typus = 'music'
|
||||
else:
|
||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
log.info('Video playlist initiated')
|
||||
self.typus = 'video'
|
||||
break
|
||||
self.playlist = playlist
|
||||
if self.playlist is not None:
|
||||
self.playlistId = self.playlist.getPlayListId()
|
||||
|
||||
def _processItems(self, startitem, startPlayer=False):
|
||||
startpos = None
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
for pos, item in enumerate(self.items):
|
||||
kodiId = None
|
||||
plexId = item['plexId']
|
||||
embydb_item = emby_db.getItem_byId(plexId)
|
||||
try:
|
||||
kodiId = embydb_item[0]
|
||||
mediatype = embydb_item[4]
|
||||
except TypeError:
|
||||
log.info('Couldnt find item %s in Kodi db' % plexId)
|
||||
xml = PlexFunctions.GetPlexMetadata(plexId)
|
||||
if xml in (None, 401):
|
||||
log.error('Could not download plexId %s' % plexId)
|
||||
else:
|
||||
log.debug('Downloaded xml metadata, adding now')
|
||||
self._addtoPlaylist_xbmc(xml[0])
|
||||
else:
|
||||
# Add to playlist
|
||||
log.debug("Adding %s PlexId %s, KodiId %s to playlist."
|
||||
% (mediatype, plexId, kodiId))
|
||||
self._addtoPlaylist(kodiId, mediatype)
|
||||
# Add the kodiId
|
||||
if kodiId is not None:
|
||||
item['kodiId'] = str(kodiId)
|
||||
if (startpos is None and startitem[1] == item[startitem[0]]):
|
||||
startpos = pos
|
||||
|
||||
if startPlayer is True and len(self.playlist) > 0:
|
||||
if startpos is not None:
|
||||
self.player.play(self.playlist, startpos=startpos)
|
||||
else:
|
||||
log.info('Never received a starting item for playlist, '
|
||||
'starting with the first entry')
|
||||
self.player.play(self.playlist)
|
||||
|
||||
@lockMethod.decorate
|
||||
def playAll(self, items, startitem, offset):
|
||||
"""
|
||||
items: list of dicts of the form
|
||||
{
|
||||
'playQueueItemID': Plex playQueueItemID, e.g. '29175'
|
||||
'plexId': Plex ratingKey, e.g. '125'
|
||||
'kodiId': Kodi's db id of the same item
|
||||
}
|
||||
|
||||
startitem: tuple (typus, id), where typus is either
|
||||
'playQueueItemID' or 'plexId' and id is the corresponding
|
||||
id as a string
|
||||
offset: First item's time offset to play in Kodi time (an int)
|
||||
"""
|
||||
log.info("---*** PLAY ALL ***---")
|
||||
log.debug('Startitem: %s, offset: %s, items: %s'
|
||||
% (startitem, offset, items))
|
||||
self.items = items
|
||||
if self.playlist is None:
|
||||
self._initiatePlaylist()
|
||||
if self.playlist is None:
|
||||
log.error('Could not create playlist, abort')
|
||||
return
|
||||
|
||||
window('plex_customplaylist', value="true")
|
||||
if offset != 0:
|
||||
# Seek to the starting position
|
||||
window('plex_customplaylist.seektime', str(offset))
|
||||
self._processItems(startitem, startPlayer=True)
|
||||
# Log playlist
|
||||
self._verifyPlaylist()
|
||||
log.debug('Internal playlist: %s' % self.items)
|
||||
|
||||
@lockMethod.decorate
|
||||
def modifyPlaylist(self, itemids):
|
||||
log.info("---*** MODIFY PLAYLIST ***---")
|
||||
log.debug("Items: %s" % itemids)
|
||||
|
||||
self._initiatePlaylist(itemids)
|
||||
self._processItems(itemids, startPlayer=True)
|
||||
|
||||
self._verifyPlaylist()
|
||||
|
||||
@lockMethod.decorate
|
||||
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
|
||||
"""
|
||||
mediatype: Kodi type: 'movie', 'episode', 'musicvideo', 'artist',
|
||||
'album', 'song', 'genre'
|
||||
"""
|
||||
self._addtoPlaylist(dbid, mediatype, url)
|
||||
|
||||
def _addtoPlaylist(self, dbid=None, mediatype=None, url=None):
|
||||
pl = {
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.Add",
|
||||
'params': {
|
||||
'playlistid': self.playlistId
|
||||
}
|
||||
}
|
||||
if dbid is not None:
|
||||
pl['params']['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
|
||||
else:
|
||||
pl['params']['item'] = {'file': url}
|
||||
log.debug(xbmc.executeJSONRPC(json.dumps(pl)))
|
||||
|
||||
def _addtoPlaylist_xbmc(self, item):
|
||||
API = PlexAPI.API(item)
|
||||
params = {
|
||||
'mode': "play",
|
||||
'dbid': 'plextrailer',
|
||||
'id': API.getRatingKey(),
|
||||
'filename': API.getKey()
|
||||
}
|
||||
playurl = "plugin://plugin.video.plexkodiconnect.movies/?%s" \
|
||||
% urlencode(params)
|
||||
|
||||
listitem = API.CreateListItemFromPlexItem()
|
||||
playbackutils.PlaybackUtils(item).setArtwork(listitem)
|
||||
|
||||
self.playlist.add(playurl, listitem)
|
||||
|
||||
@lockMethod.decorate
|
||||
def insertintoPlaylist(self,
|
||||
position,
|
||||
dbid=None,
|
||||
mediatype=None,
|
||||
url=None):
|
||||
pl = {
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.Insert",
|
||||
'params': {
|
||||
'playlistid': self.playlistId,
|
||||
'position': position
|
||||
}
|
||||
}
|
||||
if dbid is not None:
|
||||
pl['params']['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
|
||||
else:
|
||||
pl['params']['item'] = {'file': url}
|
||||
|
||||
log.debug(xbmc.executeJSONRPC(json.dumps(pl)))
|
||||
|
||||
@lockMethod.decorate
|
||||
def verifyPlaylist(self):
|
||||
self._verifyPlaylist()
|
||||
|
||||
def _verifyPlaylist(self):
|
||||
pl = {
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.GetItems",
|
||||
'params': {
|
||||
'playlistid': self.playlistId,
|
||||
'properties': ['title', 'file']
|
||||
}
|
||||
}
|
||||
log.debug(xbmc.executeJSONRPC(json.dumps(pl)))
|
||||
|
||||
@lockMethod.decorate
|
||||
def removefromPlaylist(self, position):
|
||||
pl = {
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.Remove",
|
||||
'params': {
|
||||
'playlistid': self.playlistId,
|
||||
'position': position
|
||||
}
|
||||
}
|
||||
log.debug(xbmc.executeJSONRPC(json.dumps(pl)))
|
519
resources/lib/playlist_func.py
Normal file
519
resources/lib/playlist_func.py
Normal file
|
@ -0,0 +1,519 @@
|
|||
import logging
|
||||
from urllib import quote
|
||||
|
||||
import plexdb_functions as plexdb
|
||||
from downloadutils import DownloadUtils as DU
|
||||
from utils import JSONRPC, tryEncode
|
||||
from PlexAPI import API
|
||||
|
||||
###############################################################################
|
||||
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
# kodi_item dict:
|
||||
# {u'type': u'movie', u'id': 3, 'file': path-to-file}
|
||||
|
||||
|
||||
class Playlist_Object_Baseclase(object):
|
||||
playlistid = None # Kodi playlist ID, [int]
|
||||
type = None # Kodi type: 'audio', 'video', 'picture'
|
||||
kodi_pl = None # Kodi xbmc.PlayList object
|
||||
items = [] # list of PLAYLIST_ITEMS
|
||||
old_kodi_pl = [] # to store old Kodi JSON result with all pl items
|
||||
ID = None # Plex id, e.g. playQueueID
|
||||
version = None # Plex version, [int]
|
||||
selectedItemID = None
|
||||
selectedItemOffset = None
|
||||
shuffled = 0 # [int], 0: not shuffled, 1: ??? 2: ???
|
||||
repeat = 0 # [int], 0: not repeated, 1: ??? 2: ???
|
||||
|
||||
def __repr__(self):
|
||||
answ = "<%s: " % (self.__class__.__name__)
|
||||
# For some reason, can't use dir directly
|
||||
answ += "ID: %s, " % self.ID
|
||||
answ += "items: %s, " % self.items
|
||||
for key in self.__dict__:
|
||||
if key not in ("ID", 'items'):
|
||||
answ += '%s: %s, ' % (key, getattr(self, key))
|
||||
return answ[:-2] + ">"
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Resets the playlist object to an empty playlist
|
||||
"""
|
||||
# Clear Kodi playlist object
|
||||
self.kodi_pl.clear()
|
||||
self.items = []
|
||||
self.old_kodi_pl = []
|
||||
self.ID = None
|
||||
self.version = None
|
||||
self.selectedItemID = None
|
||||
self.selectedItemOffset = None
|
||||
self.shuffled = 0
|
||||
self.repeat = 0
|
||||
log.debug('Playlist cleared: %s' % self)
|
||||
|
||||
def log_Kodi_playlist(self):
|
||||
log.debug('Current Kodi playlist: %s' % get_kodi_playlist_items(self))
|
||||
|
||||
|
||||
class Playlist_Object(Playlist_Object_Baseclase):
|
||||
kind = 'playList'
|
||||
|
||||
|
||||
class Playqueue_Object(Playlist_Object_Baseclase):
|
||||
kind = 'playQueue'
|
||||
|
||||
|
||||
class Playlist_Item(object):
|
||||
ID = None # Plex playlist/playqueue id, e.g. playQueueItemID
|
||||
plex_id = None # Plex unique item id, "ratingKey"
|
||||
plex_UUID = None # Plex librarySectionUUID
|
||||
kodi_id = None # Kodi unique kodi id (unique only within type!)
|
||||
kodi_type = None # Kodi type: 'movie'
|
||||
file = None # Path to the item's file
|
||||
uri = None # Weird Plex uri path involving plex_UUID
|
||||
guid = None # Weird Plex guid
|
||||
|
||||
def __repr__(self):
|
||||
answ = "<%s: " % (self.__class__.__name__)
|
||||
for key in self.__dict__:
|
||||
answ += '%s: %s, ' % (key, getattr(self, key))
|
||||
return answ[:-2] + ">"
|
||||
|
||||
|
||||
def playlist_item_from_kodi(kodi_item):
|
||||
"""
|
||||
Turns the JSON answer from Kodi into a playlist element
|
||||
|
||||
Supply with data['item'] as returned from Kodi JSON-RPC interface.
|
||||
kodi_item dict contains keys 'id', 'type', 'file' (if applicable)
|
||||
"""
|
||||
item = Playlist_Item()
|
||||
item.kodi_id = kodi_item.get('id')
|
||||
if item.kodi_id:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
plex_dbitem = plex_db.getItem_byKodiId(kodi_item['id'],
|
||||
kodi_item['type'])
|
||||
try:
|
||||
item.plex_id = plex_dbitem[0]
|
||||
item.plex_UUID = plex_dbitem[0] # we dont need the uuid yet :-)
|
||||
except TypeError:
|
||||
pass
|
||||
item.file = kodi_item.get('file')
|
||||
item.kodi_type = kodi_item.get('type')
|
||||
if item.plex_id is None:
|
||||
item.uri = 'library://whatever/item/%s' % quote(item.file, safe='')
|
||||
else:
|
||||
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
|
||||
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
||||
(item.plex_UUID, item.plex_id))
|
||||
return item
|
||||
|
||||
|
||||
def playlist_item_from_plex(plex_id):
|
||||
"""
|
||||
Returns a playlist element providing the plex_id ("ratingKey")
|
||||
|
||||
Returns a Playlist_Item
|
||||
"""
|
||||
item = Playlist_Item()
|
||||
item.plex_id = plex_id
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
plex_dbitem = plex_db.getItem_byId(plex_id)
|
||||
try:
|
||||
item.kodi_id = plex_dbitem[0]
|
||||
item.kodi_type = plex_dbitem[4]
|
||||
except:
|
||||
raise KeyError('Could not find plex_id %s in database' % plex_id)
|
||||
return item
|
||||
|
||||
|
||||
def playlist_item_from_xml(playlist, xml_video_element):
|
||||
"""
|
||||
Returns a playlist element for the playqueue using the Plex xml
|
||||
"""
|
||||
item = Playlist_Item()
|
||||
api = API(xml_video_element)
|
||||
item.plex_id = api.getRatingKey()
|
||||
item.ID = xml_video_element.attrib['%sItemID' % playlist.kind]
|
||||
item.guid = xml_video_element.attrib.get('guid')
|
||||
if item.plex_id:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
db_element = plex_db.getItem_byId(item.plex_id)
|
||||
try:
|
||||
item.kodi_id, item.kodi_type = int(db_element[0]), db_element[4]
|
||||
except TypeError:
|
||||
pass
|
||||
log.debug('Created new playlist item from xml: %s' % item)
|
||||
return item
|
||||
|
||||
|
||||
def _log_xml(xml):
|
||||
try:
|
||||
xml.attrib
|
||||
except AttributeError:
|
||||
log.error('Did not receive an XML. Answer was: %s' % xml)
|
||||
else:
|
||||
from xml.etree.ElementTree import dump
|
||||
log.error('XML received from the PMS:')
|
||||
dump(xml)
|
||||
|
||||
|
||||
def _get_playListVersion_from_xml(playlist, xml):
|
||||
"""
|
||||
Takes a PMS xml as input to overwrite the playlist version (e.g. Plex
|
||||
playQueueVersion). Returns True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
playlist.version = int(xml.attrib['%sVersion' % playlist.kind])
|
||||
except (TypeError, AttributeError, KeyError):
|
||||
log.error('Could not get new playlist Version for playlist %s'
|
||||
% playlist)
|
||||
_log_xml(xml)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_playlist_details_from_xml(playlist, xml):
|
||||
"""
|
||||
Takes a PMS xml as input and overwrites all the playlist's details, e.g.
|
||||
playlist.ID with the XML's playQueueID
|
||||
"""
|
||||
try:
|
||||
playlist.ID = xml.attrib['%sID' % playlist.kind]
|
||||
playlist.version = xml.attrib['%sVersion' % playlist.kind]
|
||||
playlist.shuffled = xml.attrib['%sShuffled' % playlist.kind]
|
||||
playlist.selectedItemID = xml.attrib.get(
|
||||
'%sSelectedItemID' % playlist.kind)
|
||||
playlist.selectedItemOffset = xml.attrib.get(
|
||||
'%sSelectedItemOffset' % playlist.kind)
|
||||
except:
|
||||
log.error('Could not parse xml answer from PMS for playlist %s'
|
||||
% playlist)
|
||||
import traceback
|
||||
log.error(traceback.format_exc())
|
||||
_log_xml(xml)
|
||||
raise KeyError
|
||||
log.debug('Updated playlist from xml: %s' % playlist)
|
||||
|
||||
|
||||
def update_playlist_from_PMS(playlist, playlist_id=None, xml=None):
|
||||
"""
|
||||
Updates Kodi playlist using a new PMS playlist. Pass in playlist_id if we
|
||||
need to fetch a new playqueue
|
||||
|
||||
If an xml is passed in, the playlist will be overwritten with its info
|
||||
"""
|
||||
if xml is None:
|
||||
xml = get_PMS_playlist(playlist, playlist_id)
|
||||
try:
|
||||
xml.attrib['%sVersion' % playlist.kind]
|
||||
except:
|
||||
log.error('Could not process 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):
|
||||
"""
|
||||
Initializes the Plex side without changing the Kodi playlists
|
||||
|
||||
WILL ALSO UPDATE OUR PLAYLISTS
|
||||
"""
|
||||
log.debug('Initializing the playlist %s on the Plex side' % playlist)
|
||||
if plex_id:
|
||||
item = playlist_item_from_plex(plex_id)
|
||||
else:
|
||||
item = playlist_item_from_kodi(kodi_item)
|
||||
params = {
|
||||
'next': 0,
|
||||
'type': playlist.type,
|
||||
'uri': item.uri
|
||||
}
|
||||
xml = DU().downloadUrl(url="{server}/%ss" % playlist.kind,
|
||||
action_type="POST",
|
||||
parameters=params)
|
||||
get_playlist_details_from_xml(playlist, xml)
|
||||
playlist.items.append(item)
|
||||
log.debug('Initialized the playlist on the Plex side: %s' % playlist)
|
||||
|
||||
|
||||
def add_listitem_to_playlist(playlist, pos, listitem, kodi_id=None,
|
||||
kodi_type=None, plex_id=None, file=None):
|
||||
"""
|
||||
Adds a listitem to both the Kodi and Plex playlist at position pos [int].
|
||||
|
||||
If file is not None, file will overrule kodi_id!
|
||||
"""
|
||||
log.debug('add_listitem_to_playlist. Playlist before add: %s' % playlist)
|
||||
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
|
||||
if playlist.ID is None:
|
||||
init_Plex_playlist(playlist, plex_id, kodi_item)
|
||||
else:
|
||||
add_item_to_PMS_playlist(playlist, pos, plex_id, kodi_item)
|
||||
if kodi_id is None and playlist.items[pos].kodi_id:
|
||||
kodi_id = playlist.items[pos].kodi_id
|
||||
kodi_type = playlist.items[pos].kodi_type
|
||||
if file is None:
|
||||
file = playlist.items[pos].file
|
||||
# Otherwise we double the item!
|
||||
del playlist.items[pos]
|
||||
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
|
||||
add_listitem_to_Kodi_playlist(playlist,
|
||||
pos,
|
||||
listitem,
|
||||
file,
|
||||
kodi_item=kodi_item)
|
||||
|
||||
|
||||
def add_item_to_playlist(playlist, pos, kodi_id=None, kodi_type=None,
|
||||
plex_id=None, file=None):
|
||||
"""
|
||||
Adds an item to BOTH the Kodi and Plex playlist at position pos [int]
|
||||
"""
|
||||
log.debug('add_item_to_playlist. Playlist before adding: %s' % playlist)
|
||||
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
|
||||
if playlist.ID is None:
|
||||
init_Plex_playlist(playlist, plex_id, kodi_item)
|
||||
else:
|
||||
add_item_to_PMS_playlist(playlist, pos, plex_id, kodi_item)
|
||||
kodi_id = playlist.items[pos].kodi_id
|
||||
kodi_type = playlist.items[pos].kodi_type
|
||||
file = playlist.items[pos].file
|
||||
add_item_to_kodi_playlist(playlist, pos, kodi_id, kodi_type, file)
|
||||
|
||||
|
||||
def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None):
|
||||
"""
|
||||
Adds a new item to the playlist at position pos [int] only on the Plex
|
||||
side of things (e.g. because the user changed the Kodi side)
|
||||
|
||||
WILL ALSO UPDATE OUR PLAYLISTS
|
||||
"""
|
||||
log.debug('Adding new item plex_id: %s, kodi_item: %s on the Plex side at '
|
||||
'position %s for %s' % (plex_id, kodi_item, pos, playlist))
|
||||
if plex_id:
|
||||
item = playlist_item_from_plex(plex_id)
|
||||
else:
|
||||
item = playlist_item_from_kodi(kodi_item)
|
||||
url = "{server}/%ss/%s?uri=%s" % (playlist.kind, playlist.ID, item.uri)
|
||||
# Will always put the new item at the end of the Plex playlist
|
||||
xml = DU().downloadUrl(url, action_type="PUT")
|
||||
try:
|
||||
item.ID = xml[-1].attrib['%sItemID' % playlist.kind]
|
||||
except IndexError:
|
||||
log.info('Could not get playlist children. Adding a dummy')
|
||||
except (TypeError, AttributeError, KeyError):
|
||||
log.error('Could not add item %s to playlist %s'
|
||||
% (kodi_item, playlist))
|
||||
_log_xml(xml)
|
||||
return
|
||||
# Get the guid for this item
|
||||
for plex_item in xml:
|
||||
if plex_item.attrib['%sItemID' % playlist.kind] == item.ID:
|
||||
item.guid = plex_item.attrib['guid']
|
||||
playlist.items.append(item)
|
||||
if pos == len(playlist.items) - 1:
|
||||
# Item was added at the end
|
||||
_get_playListVersion_from_xml(playlist, xml)
|
||||
else:
|
||||
# Move the new item to the correct position
|
||||
move_playlist_item(playlist,
|
||||
len(playlist.items) - 1,
|
||||
pos)
|
||||
log.debug('Successfully added item on the Plex side: %s' % playlist)
|
||||
|
||||
|
||||
def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
|
||||
file=None):
|
||||
"""
|
||||
Adds an item to the KODI playlist only
|
||||
|
||||
WILL ALSO UPDATE OUR PLAYLISTS
|
||||
"""
|
||||
log.debug('Adding new item kodi_id: %s, kodi_type: %s, file: %s to Kodi '
|
||||
'only at position %s for %s'
|
||||
% (kodi_id, kodi_type, file, pos, playlist))
|
||||
params = {
|
||||
'playlistid': playlist.playlistid,
|
||||
'position': pos
|
||||
}
|
||||
if kodi_id is not None:
|
||||
params['item'] = {'%sid' % kodi_type: int(kodi_id)}
|
||||
else:
|
||||
params['item'] = {'file': file}
|
||||
log.debug(JSONRPC('Playlist.Insert').execute(params))
|
||||
playlist.items.insert(pos, playlist_item_from_kodi(
|
||||
{'id': kodi_id, 'type': kodi_type, 'file': file}))
|
||||
|
||||
|
||||
def move_playlist_item(playlist, before_pos, after_pos):
|
||||
"""
|
||||
Moves playlist item from before_pos [int] to after_pos [int] for Plex only.
|
||||
|
||||
WILL ALSO CHANGE OUR PLAYLISTS
|
||||
"""
|
||||
log.debug('Moving item from %s to %s on the Plex side for %s'
|
||||
% (before_pos, after_pos, playlist))
|
||||
if after_pos == 0:
|
||||
url = "{server}/%ss/%s/items/%s/move?after=0" % \
|
||||
(playlist.kind,
|
||||
playlist.ID,
|
||||
playlist.items[before_pos].ID)
|
||||
else:
|
||||
url = "{server}/%ss/%s/items/%s/move?after=%s" % \
|
||||
(playlist.kind,
|
||||
playlist.ID,
|
||||
playlist.items[before_pos].ID,
|
||||
playlist.items[after_pos - 1].ID)
|
||||
xml = DU().downloadUrl(url, action_type="PUT")
|
||||
# We need to increment the playlistVersion
|
||||
_get_playListVersion_from_xml(playlist, xml)
|
||||
# Move our item's position in our internal playlist
|
||||
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
|
||||
log.debug('Done moving for %s' % playlist)
|
||||
|
||||
|
||||
def get_PMS_playlist(playlist, playlist_id=None):
|
||||
"""
|
||||
Fetches the PMS playlist/playqueue as an XML. Pass in playlist_id if we
|
||||
need to fetch a new playlist
|
||||
|
||||
Returns None if something went wrong
|
||||
"""
|
||||
playlist_id = playlist_id if playlist_id else playlist.ID
|
||||
xml = DU().downloadUrl(
|
||||
"{server}/%ss/%s" % (playlist.kind, playlist_id),
|
||||
headerOptions={'Accept': 'application/xml'})
|
||||
try:
|
||||
xml.attrib['%sID' % playlist.kind]
|
||||
except (AttributeError, KeyError):
|
||||
xml = None
|
||||
return xml
|
||||
|
||||
|
||||
def refresh_playlist_from_PMS(playlist):
|
||||
"""
|
||||
Only updates the selected item from the PMS side (e.g.
|
||||
playQueueSelectedItemID). Will NOT check whether items still make sense.
|
||||
"""
|
||||
xml = get_PMS_playlist(playlist)
|
||||
try:
|
||||
xml.attrib['%sVersion' % playlist.kind]
|
||||
except:
|
||||
log.error('Could not download Plex playlist.')
|
||||
return
|
||||
get_playlist_details_from_xml(playlist, xml)
|
||||
|
||||
|
||||
def delete_playlist_item_from_PMS(playlist, pos):
|
||||
"""
|
||||
Delete the item at position pos [int] on the Plex side and our playlists
|
||||
"""
|
||||
log.debug('Deleting position %s for %s on the Plex side' % (pos, playlist))
|
||||
xml = DU().downloadUrl("{server}/%ss/%s/items/%s?repeat=%s" %
|
||||
(playlist.kind,
|
||||
playlist.ID,
|
||||
playlist.items[pos].ID,
|
||||
playlist.repeat),
|
||||
action_type="DELETE")
|
||||
_get_playListVersion_from_xml(playlist, xml)
|
||||
del playlist.items[pos]
|
||||
|
||||
|
||||
def get_kodi_playlist_items(playlist):
|
||||
"""
|
||||
Returns a list of the current Kodi playlist items using JSON
|
||||
|
||||
E.g.:
|
||||
[{u'title': u'3 Idiots', u'type': u'movie', u'id': 3, u'file':
|
||||
u'smb://nas/PlexMovies/3 Idiots 2009 pt1.mkv', u'label': u'3 Idiots'}]
|
||||
"""
|
||||
answ = JSONRPC('Playlist.GetItems').execute({
|
||||
'playlistid': playlist.playlistid,
|
||||
'properties': ["title", "file"]
|
||||
})
|
||||
try:
|
||||
answ = answ['result']['items']
|
||||
except KeyError:
|
||||
answ = []
|
||||
return answ
|
||||
|
||||
|
||||
def get_kodi_playqueues():
|
||||
"""
|
||||
Example return: [{u'playlistid': 0, u'type': u'audio'},
|
||||
{u'playlistid': 1, u'type': u'video'},
|
||||
{u'playlistid': 2, u'type': u'picture'}]
|
||||
"""
|
||||
queues = JSONRPC('Playlist.GetPlaylists').execute()
|
||||
try:
|
||||
queues = queues['result']
|
||||
except KeyError:
|
||||
raise KeyError('Could not get Kodi playqueues. JSON Result was: %s'
|
||||
% queues)
|
||||
return queues
|
||||
|
||||
|
||||
# Functions operating on the Kodi playlist objects ##########
|
||||
|
||||
def add_to_Kodi_playlist(playlist, xml_video_element):
|
||||
"""
|
||||
Adds a new item to the Kodi playlist via JSON (at the end of the playlist).
|
||||
Pass in the PMS xml's video element (one level underneath MediaContainer).
|
||||
|
||||
Returns a Playlist_Item
|
||||
"""
|
||||
item = playlist_item_from_xml(playlist, xml_video_element)
|
||||
params = {
|
||||
'playlistid': playlist.playlistid
|
||||
}
|
||||
if item.kodi_id:
|
||||
params['item'] = {'%sid' % item.kodi_type: item.kodi_id}
|
||||
else:
|
||||
params['item'] = {'file': tryEncode(item.file)}
|
||||
log.debug(JSONRPC('Playlist.Add').execute(params))
|
||||
return item
|
||||
|
||||
|
||||
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.xml_video_element
|
||||
|
||||
WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS
|
||||
"""
|
||||
log.debug('Insert listitem at position %s for Kodi only for %s'
|
||||
% (pos, playlist))
|
||||
# Add the item into Kodi playlist
|
||||
playlist.kodi_pl.add(file, listitem, index=pos)
|
||||
# We need to add this to our internal queue as well
|
||||
if xml_video_element is not None:
|
||||
item = playlist_item_from_xml(playlist, xml_video_element)
|
||||
item.file = file
|
||||
else:
|
||||
item = playlist_item_from_kodi(kodi_item)
|
||||
playlist.items.insert(pos, item)
|
||||
log.debug('Done inserting for %s' % playlist)
|
||||
|
||||
|
||||
def remove_from_Kodi_playlist(playlist, pos):
|
||||
"""
|
||||
Removes the item at position pos from the Kodi playlist using JSON.
|
||||
|
||||
WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS
|
||||
"""
|
||||
log.debug('Removing position %s from Kodi only from %s' % (pos, playlist))
|
||||
log.debug(JSONRPC('Playlist.Remove').execute({
|
||||
'playlistid': playlist.playlistid,
|
||||
'position': pos
|
||||
}))
|
||||
del playlist.items[pos]
|
185
resources/lib/playqueue.py
Normal file
185
resources/lib/playqueue.py
Normal file
|
@ -0,0 +1,185 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
import logging
|
||||
from threading import RLock, Thread
|
||||
|
||||
from xbmc import sleep, Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO
|
||||
|
||||
from utils import window, ThreadMethods, ThreadMethodsAdditionalSuspend
|
||||
import playlist_func as PL
|
||||
from PlexFunctions import ConvertPlexToKodiTime
|
||||
from playbackutils import PlaybackUtils
|
||||
|
||||
###############################################################################
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
# Lock used for playqueue manipulations
|
||||
lock = RLock()
|
||||
###############################################################################
|
||||
|
||||
|
||||
@ThreadMethodsAdditionalSuspend('plex_serverStatus')
|
||||
@ThreadMethods
|
||||
class Playqueue(Thread):
|
||||
"""
|
||||
Monitors Kodi's playqueues for changes on the Kodi side
|
||||
"""
|
||||
# Borg - multiple instances, shared state
|
||||
__shared_state = {}
|
||||
playqueues = None
|
||||
|
||||
def __init__(self, callback=None):
|
||||
self.__dict__ = self.__shared_state
|
||||
if self.playqueues is not None:
|
||||
return
|
||||
self.mgr = callback
|
||||
|
||||
# Initialize Kodi playqueues
|
||||
with lock:
|
||||
self.playqueues = []
|
||||
for queue in PL.get_kodi_playqueues():
|
||||
playqueue = PL.Playqueue_Object()
|
||||
playqueue.playlistid = queue['playlistid']
|
||||
playqueue.type = queue['type']
|
||||
# Initialize each Kodi playlist
|
||||
if playqueue.type == 'audio':
|
||||
playqueue.kodi_pl = PlayList(PLAYLIST_MUSIC)
|
||||
elif playqueue.type == 'video':
|
||||
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
|
||||
else:
|
||||
# Currently, only video or audio playqueues available
|
||||
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
|
||||
self.playqueues.append(playqueue)
|
||||
# sort the list by their playlistid, just in case
|
||||
self.playqueues = sorted(
|
||||
self.playqueues, key=lambda i: i.playlistid)
|
||||
log.debug('Initialized the Kodi play queues: %s' % self.playqueues)
|
||||
Thread.__init__(self)
|
||||
|
||||
def get_playqueue_from_type(self, typus):
|
||||
"""
|
||||
Returns the playqueue according to the typus ('video', 'audio',
|
||||
'picture') passed in
|
||||
"""
|
||||
with lock:
|
||||
for playqueue in self.playqueues:
|
||||
if playqueue.type == typus:
|
||||
break
|
||||
else:
|
||||
raise ValueError('Wrong playlist type passed in: %s' % typus)
|
||||
return playqueue
|
||||
|
||||
def update_playqueue_from_PMS(self,
|
||||
playqueue,
|
||||
playqueue_id=None,
|
||||
repeat=None,
|
||||
offset=None):
|
||||
"""
|
||||
Completely updates the Kodi playqueue with the new Plex playqueue. Pass
|
||||
in playqueue_id if we need to fetch a new playqueue
|
||||
|
||||
repeat = 0, 1, 2
|
||||
offset = time offset in Plextime (milliseconds)
|
||||
"""
|
||||
log.info('New playqueue %s received from Plex companion with offset '
|
||||
'%s, repeat %s' % (playqueue_id, offset, repeat))
|
||||
with lock:
|
||||
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
||||
if xml is None:
|
||||
log.error('Could not get playqueue ID %s' % playqueue_id)
|
||||
return
|
||||
playqueue.clear()
|
||||
PL.get_playlist_details_from_xml(playqueue, xml)
|
||||
PlaybackUtils(xml, playqueue).play_all()
|
||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||
window('plex_customplaylist', value="true")
|
||||
if offset not in (None, "0"):
|
||||
window('plex_customplaylist.seektime',
|
||||
str(ConvertPlexToKodiTime(offset)))
|
||||
for startpos, item in enumerate(playqueue.items):
|
||||
if item.ID == playqueue.selectedItemID:
|
||||
break
|
||||
else:
|
||||
startpos = 0
|
||||
# Start playback. Player does not return in time
|
||||
log.debug('Playqueues after Plex Companion update are now: %s'
|
||||
% self.playqueues)
|
||||
thread = Thread(target=Player().play,
|
||||
args=(playqueue.kodi_pl,
|
||||
None,
|
||||
False,
|
||||
startpos))
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
def _compare_playqueues(self, playqueue, new):
|
||||
"""
|
||||
Used to poll the Kodi playqueue and update the Plex playqueue if needed
|
||||
"""
|
||||
old = list(playqueue.items)
|
||||
index = list(range(0, len(old)))
|
||||
log.debug('Comparing new Kodi playqueue %s with our play queue %s'
|
||||
% (new, old))
|
||||
for i, new_item in enumerate(new):
|
||||
for j, old_item in enumerate(old):
|
||||
if self.threadStopped():
|
||||
# Chances are that we got an empty Kodi playlist due to
|
||||
# Kodi exit
|
||||
return
|
||||
if new_item.get('id') is None:
|
||||
identical = old_item.file == new_item['file']
|
||||
else:
|
||||
identical = (old_item.kodi_id == new_item['id'] and
|
||||
old_item.kodi_type == new_item['type'])
|
||||
if j == 0 and identical:
|
||||
del old[j], index[j]
|
||||
break
|
||||
elif identical:
|
||||
log.debug('Detected playqueue item %s moved to position %s'
|
||||
% (i+j, i))
|
||||
PL.move_playlist_item(playqueue, i + j, i)
|
||||
del old[j], index[j]
|
||||
break
|
||||
else:
|
||||
log.debug('Detected new Kodi element at position %s: %s '
|
||||
% (i, 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)
|
||||
index.insert(i, i)
|
||||
for j in range(i+1, len(index)):
|
||||
index[j] += 1
|
||||
for i in reversed(index):
|
||||
log.debug('Detected deletion of playqueue element at pos %s' % i)
|
||||
PL.delete_playlist_item_from_PMS(playqueue, i)
|
||||
log.debug('Done comparing playqueues')
|
||||
|
||||
def run(self):
|
||||
threadStopped = self.threadStopped
|
||||
threadSuspended = self.threadSuspended
|
||||
log.info("----===## Starting PlayQueue client ##===----")
|
||||
# Initialize the playqueues, if Kodi already got items in them
|
||||
for playqueue in self.playqueues:
|
||||
for i, item in enumerate(PL.get_kodi_playlist_items(playqueue)):
|
||||
if i == 0:
|
||||
PL.init_Plex_playlist(playqueue, kodi_item=item)
|
||||
else:
|
||||
PL.add_item_to_PMS_playlist(playqueue, i, kodi_item=item)
|
||||
while not threadStopped():
|
||||
while threadSuspended():
|
||||
if threadStopped():
|
||||
break
|
||||
sleep(1000)
|
||||
with lock:
|
||||
for playqueue in self.playqueues:
|
||||
kodi_playqueue = PL.get_kodi_playlist_items(playqueue)
|
||||
if playqueue.old_kodi_pl != kodi_playqueue:
|
||||
# compare old and new playqueue
|
||||
self._compare_playqueues(playqueue, kodi_playqueue)
|
||||
playqueue.old_kodi_pl = list(kodi_playqueue)
|
||||
sleep(50)
|
||||
log.info("----===## PlayQueue client stopped ##===----")
|
|
@ -45,14 +45,14 @@ class PlayUtils():
|
|||
log.info("File is direct playing.")
|
||||
playurl = tryEncode(playurl)
|
||||
# Set playmethod property
|
||||
window('emby_%s.playmethod' % playurl, "DirectPlay")
|
||||
window('plex_%s.playmethod' % playurl, "DirectPlay")
|
||||
|
||||
elif self.isDirectStream():
|
||||
log.info("File is direct streaming.")
|
||||
playurl = tryEncode(
|
||||
self.API.getTranscodeVideoPath('DirectStream'))
|
||||
# Set playmethod property
|
||||
window('emby_%s.playmethod' % playurl, "DirectStream")
|
||||
window('plex_%s.playmethod' % playurl, "DirectStream")
|
||||
|
||||
else:
|
||||
log.info("File is transcoding.")
|
||||
|
@ -64,7 +64,7 @@ class PlayUtils():
|
|||
'videoQuality': '100'
|
||||
}))
|
||||
# Set playmethod property
|
||||
window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||
window('plex_%s.playmethod' % playurl, value="Transcode")
|
||||
|
||||
log.info("The playurl is: %s" % playurl)
|
||||
return playurl
|
||||
|
|
|
@ -5,7 +5,7 @@ import string
|
|||
|
||||
import xbmc
|
||||
|
||||
import embydb_functions as embydb
|
||||
import plexdb_functions as plexdb
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -146,11 +146,11 @@ class jsonClass():
|
|||
def skipTo(self, plexId, typus):
|
||||
# playlistId = self.getPlaylistId(tryDecode(xbmc_type(typus)))
|
||||
# playerId = self.
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
embydb_item = emby_db.getItem_byId(plexId)
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
plexdb_item = plex_db.getItem_byId(plexId)
|
||||
try:
|
||||
dbid = embydb_item[0]
|
||||
mediatype = embydb_item[4]
|
||||
dbid = plexdb_item[0]
|
||||
mediatype = plexdb_item[4]
|
||||
except TypeError:
|
||||
log.info('Couldnt find item %s in Kodi db' % plexId)
|
||||
return
|
||||
|
@ -163,7 +163,7 @@ class jsonClass():
|
|||
"Access-Control-Allow-Origin": "*",
|
||||
"X-Plex-Version": self.settings['version'],
|
||||
"X-Plex-Client-Identifier": self.settings['uuid'],
|
||||
"X-Plex-Provides": "player",
|
||||
"X-Plex-Provides": "client,controller,player",
|
||||
"X-Plex-Product": "PlexKodiConnect",
|
||||
"X-Plex-Device-Name": self.settings['client_name'],
|
||||
"X-Plex-Platform": "Kodi",
|
||||
|
|
|
@ -72,7 +72,7 @@ class plexgdm:
|
|||
"Protocol: plex\r\n"
|
||||
"Protocol-Version: 1\r\n"
|
||||
"Protocol-Capabilities: timeline,playback,navigation,"
|
||||
"mirror,playqueues\r\n"
|
||||
"playqueues\r\n"
|
||||
"Device-Class: HTPC"
|
||||
) % (
|
||||
options['uuid'],
|
||||
|
|
|
@ -15,7 +15,7 @@ log = logging.getLogger("PLEX."+__name__)
|
|||
|
||||
|
||||
class SubscriptionManager:
|
||||
def __init__(self, jsonClass, RequestMgr, player, playlist):
|
||||
def __init__(self, jsonClass, RequestMgr, player, mgr):
|
||||
self.serverlist = []
|
||||
self.subscribers = {}
|
||||
self.info = {}
|
||||
|
@ -36,7 +36,7 @@ class SubscriptionManager:
|
|||
self.playerprops = {}
|
||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||
self.xbmcplayer = player
|
||||
self.playlist = playlist
|
||||
self.playqueue = mgr.playqueue
|
||||
|
||||
self.js = jsonClass
|
||||
self.RequestMgr = RequestMgr
|
||||
|
@ -231,6 +231,8 @@ class SubscriptionManager:
|
|||
|
||||
def getPlayerProperties(self, playerid):
|
||||
try:
|
||||
# Get the playqueue
|
||||
playqueue = self.playqueue.playqueues[playerid]
|
||||
# get info from the player
|
||||
props = self.js.jsonrpc(
|
||||
"Player.GetProperties",
|
||||
|
@ -248,18 +250,16 @@ class SubscriptionManager:
|
|||
'shuffle': ("0", "1")[props.get('shuffled', False)],
|
||||
'repeat': pf.getPlexRepeat(props.get('repeat')),
|
||||
}
|
||||
if self.playlist is not None:
|
||||
if self.playlist.QueueId() is not None:
|
||||
info['playQueueID'] = self.playlist.QueueId()
|
||||
info['playQueueVersion'] = self.playlist.PlayQueueVersion()
|
||||
info['guid'] = self.playlist.Guid()
|
||||
if playqueue.ID is not None:
|
||||
info['playQueueID'] = playqueue.ID
|
||||
info['playQueueVersion'] = playqueue.version
|
||||
# Get the playlist position
|
||||
pos = self.js.jsonrpc(
|
||||
"Player.GetProperties",
|
||||
{"playerid": playerid,
|
||||
"properties": ["position"]})
|
||||
info['playQueueItemID'] = \
|
||||
self.playlist.getQueueIdFromPosition(pos['position'])
|
||||
"properties": ["position"]})['position']
|
||||
info['playQueueItemID'] = playqueue.items[pos].ID
|
||||
info['guid'] = playqueue.items[pos].guid
|
||||
except:
|
||||
import traceback
|
||||
log.error("Traceback:\n%s" % traceback.format_exc())
|
||||
|
|
391
resources/lib/plexdb_functions.py
Normal file
391
resources/lib/plexdb_functions.py
Normal file
|
@ -0,0 +1,391 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
###############################################################################
|
||||
|
||||
from utils import kodiSQL
|
||||
import logging
|
||||
|
||||
###############################################################################
|
||||
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
class Get_Plex_DB():
|
||||
"""
|
||||
Usage: with Get_Plex_DB() as plex_db:
|
||||
plex_db.do_something()
|
||||
|
||||
On exiting "with" (no matter what), commits get automatically committed
|
||||
and the db gets closed
|
||||
"""
|
||||
def __enter__(self):
|
||||
self.plexconn = kodiSQL('plex')
|
||||
return Plex_DB_Functions(self.plexconn.cursor())
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.plexconn.commit()
|
||||
self.plexconn.close()
|
||||
|
||||
|
||||
class Plex_DB_Functions():
|
||||
|
||||
def __init__(self, plexcursor):
|
||||
self.plexcursor = plexcursor
|
||||
|
||||
def getViews(self):
|
||||
"""
|
||||
Returns a list of view_id
|
||||
"""
|
||||
views = []
|
||||
query = '''
|
||||
SELECT view_id
|
||||
FROM view
|
||||
'''
|
||||
self.plexcursor.execute(query)
|
||||
rows = self.plexcursor.fetchall()
|
||||
for row in rows:
|
||||
views.append(row[0])
|
||||
return views
|
||||
|
||||
def getAllViewInfo(self):
|
||||
"""
|
||||
Returns a list of dicts:
|
||||
{'id': view_id, 'name': view_name, 'itemtype': kodi_type}
|
||||
"""
|
||||
plexcursor = self.plexcursor
|
||||
views = []
|
||||
query = '''
|
||||
SELECT view_id, view_name, kodi_type
|
||||
FROM view
|
||||
'''
|
||||
plexcursor.execute(query)
|
||||
rows = plexcursor.fetchall()
|
||||
for row in rows:
|
||||
views.append({'id': row[0],
|
||||
'name': row[1],
|
||||
'itemtype': row[2]})
|
||||
return views
|
||||
|
||||
def getView_byId(self, view_id):
|
||||
"""
|
||||
Returns tuple (view_name, kodi_type, kodi_tagid) for view_id
|
||||
"""
|
||||
query = '''
|
||||
SELECT view_name, kodi_type, kodi_tagid
|
||||
FROM view
|
||||
WHERE view_id = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (view_id,))
|
||||
view = self.plexcursor.fetchone()
|
||||
return view
|
||||
|
||||
def getView_byType(self, kodi_type):
|
||||
"""
|
||||
Returns a list of dicts for kodi_type:
|
||||
{'id': view_id, 'name': view_name, 'itemtype': kodi_type}
|
||||
"""
|
||||
views = []
|
||||
query = '''
|
||||
SELECT view_id, view_name, kodi_type
|
||||
FROM view
|
||||
WHERE kodi_type = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (kodi_type,))
|
||||
rows = self.plexcursor.fetchall()
|
||||
for row in rows:
|
||||
views.append({
|
||||
'id': row[0],
|
||||
'name': row[1],
|
||||
'itemtype': row[2]
|
||||
})
|
||||
return views
|
||||
|
||||
def getView_byName(self, view_name):
|
||||
"""
|
||||
Returns the view_id for view_name (or None)
|
||||
"""
|
||||
query = '''
|
||||
SELECT view_id
|
||||
FROM view
|
||||
WHERE view_name = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (view_name,))
|
||||
try:
|
||||
view = self.plexcursor.fetchone()[0]
|
||||
except TypeError:
|
||||
view = None
|
||||
return view
|
||||
|
||||
def addView(self, view_id, view_name, kodi_type, kodi_tagid):
|
||||
"""
|
||||
Appends an entry to the view table
|
||||
"""
|
||||
query = '''
|
||||
INSERT INTO view(
|
||||
view_id, view_name, kodi_type, kodi_tagid)
|
||||
VALUES (?, ?, ?, ?)
|
||||
'''
|
||||
self.plexcursor.execute(query,
|
||||
(view_id, view_name, kodi_type, kodi_tagid))
|
||||
|
||||
def updateView(self, view_name, kodi_tagid, view_id):
|
||||
"""
|
||||
Updates the view_id with view_name and kodi_tagid
|
||||
"""
|
||||
query = '''
|
||||
UPDATE view
|
||||
SET view_name = ?, kodi_tagid = ?
|
||||
WHERE view_id = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (view_name, kodi_tagid, view_id))
|
||||
|
||||
def removeView(self, view_id):
|
||||
query = '''
|
||||
DELETE FROM view
|
||||
WHERE view_id = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (view_id,))
|
||||
|
||||
def getItem_byFileId(self, kodi_fileid, kodi_type):
|
||||
"""
|
||||
Returns plex_id for kodi_fileid and kodi_type
|
||||
|
||||
None if not found
|
||||
"""
|
||||
query = '''
|
||||
SELECT plex_id
|
||||
FROM plex
|
||||
WHERE kodi_fileid = ? AND kodi_type = ?
|
||||
'''
|
||||
try:
|
||||
self.plexcursor.execute(query, (kodi_fileid, kodi_type))
|
||||
item = self.plexcursor.fetchone()[0]
|
||||
return item
|
||||
except:
|
||||
return None
|
||||
|
||||
def getMusicItem_byFileId(self, kodi_id, kodi_type):
|
||||
"""
|
||||
Returns the plex_id for kodi_id and kodi_type
|
||||
|
||||
None if not found
|
||||
"""
|
||||
query = '''
|
||||
SELECT plex_id
|
||||
FROM plex
|
||||
WHERE kodi_id = ? AND kodi_type = ?
|
||||
'''
|
||||
try:
|
||||
self.plexcursor.execute(query, (kodi_id, kodi_type))
|
||||
item = self.plexcursor.fetchone()[0]
|
||||
return item
|
||||
except:
|
||||
return None
|
||||
|
||||
def getItem_byId(self, plex_id):
|
||||
"""
|
||||
For plex_id, returns the tuple
|
||||
(kodi_id, kodi_fileid, kodi_pathid, parent_id, kodi_type, plex_type)
|
||||
|
||||
None if not found
|
||||
"""
|
||||
query = '''
|
||||
SELECT kodi_id, kodi_fileid, kodi_pathid,
|
||||
parent_id, kodi_type, plex_type
|
||||
FROM plex
|
||||
WHERE plex_id = ?
|
||||
'''
|
||||
try:
|
||||
self.plexcursor.execute(query, (plex_id,))
|
||||
item = self.plexcursor.fetchone()
|
||||
return item
|
||||
except:
|
||||
return None
|
||||
|
||||
def getItem_byWildId(self, plex_id):
|
||||
"""
|
||||
Returns a list of tuples (kodi_id, kodi_type) for plex_id (% appended)
|
||||
"""
|
||||
query = '''
|
||||
SELECT kodi_id, kodi_type
|
||||
FROM plex
|
||||
WHERE plex_id LIKE ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (plex_id+"%",))
|
||||
return self.plexcursor.fetchall()
|
||||
|
||||
def getItem_byView(self, view_id):
|
||||
"""
|
||||
Returns kodi_id for view_id
|
||||
"""
|
||||
query = '''
|
||||
SELECT kodi_id
|
||||
FROM plex
|
||||
WHERE view_id = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (view_id,))
|
||||
return self.plexcursor.fetchall()
|
||||
|
||||
def getItem_byKodiId(self, kodi_id, kodi_type):
|
||||
"""
|
||||
Returns the tuple (plex_id, parent_id) for kodi_id and kodi_type
|
||||
"""
|
||||
query = '''
|
||||
SELECT plex_id, parent_id
|
||||
FROM plex
|
||||
WHERE kodi_id = ?
|
||||
AND kodi_type = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (kodi_id, kodi_type,))
|
||||
return self.plexcursor.fetchone()
|
||||
|
||||
def getItem_byParentId(self, parent_id, kodi_type):
|
||||
"""
|
||||
Returns the tuple (plex_id, kodi_id, kodi_fileid) for parent_id,
|
||||
kodi_type
|
||||
"""
|
||||
query = '''
|
||||
SELECT plex_id, kodi_id, kodi_fileid
|
||||
FROM plex
|
||||
WHERE parent_id = ?
|
||||
AND kodi_type = ?"
|
||||
'''
|
||||
self.plexcursor.execute(query, (parent_id, kodi_type,))
|
||||
return self.plexcursor.fetchall()
|
||||
|
||||
def getItemId_byParentId(self, parent_id, kodi_type):
|
||||
"""
|
||||
Returns the tuple (plex_id, kodi_id) for parent_id, kodi_type
|
||||
"""
|
||||
query = '''
|
||||
SELECT plex_id, kodi_id
|
||||
FROM plex
|
||||
WHERE parent_id = ?
|
||||
AND kodi_type = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (parent_id, kodi_type,))
|
||||
return self.plexcursor.fetchall()
|
||||
|
||||
def getChecksum(self, plex_type):
|
||||
"""
|
||||
Returns a list of tuples (plex_id, checksum) for plex_type
|
||||
"""
|
||||
query = '''
|
||||
SELECT plex_id, checksum
|
||||
FROM plex
|
||||
WHERE plex_type = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (plex_type,))
|
||||
return self.plexcursor.fetchall()
|
||||
|
||||
def getMediaType_byId(self, plex_id):
|
||||
"""
|
||||
Returns plex_type for plex_id
|
||||
|
||||
Or None if not found
|
||||
"""
|
||||
query = '''
|
||||
SELECT plex_type
|
||||
FROM plex
|
||||
WHERE plex_id = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (plex_id,))
|
||||
try:
|
||||
itemtype = self.plexcursor.fetchone()[0]
|
||||
except TypeError:
|
||||
itemtype = None
|
||||
return itemtype
|
||||
|
||||
def addReference(self, plex_id, plex_type, kodi_id, kodi_type,
|
||||
kodi_fileid=None, kodi_pathid=None, parent_id=None,
|
||||
checksum=None, view_id=None):
|
||||
"""
|
||||
Appends or replaces an entry into the plex table
|
||||
"""
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO plex(
|
||||
plex_id, kodi_id, kodi_fileid, kodi_pathid, plex_type,
|
||||
kodi_type, parent_id, checksum, view_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
self.plexcursor.execute(query, (plex_id, kodi_id, kodi_fileid,
|
||||
kodi_pathid, plex_type, kodi_type,
|
||||
parent_id, checksum, view_id))
|
||||
|
||||
def updateReference(self, plex_id, checksum):
|
||||
"""
|
||||
Updates checksum for plex_id
|
||||
"""
|
||||
query = "UPDATE plex SET checksum = ? WHERE plex_id = ?"
|
||||
self.plexcursor.execute(query, (checksum, plex_id))
|
||||
|
||||
def updateParentId(self, plexid, parent_kodiid):
|
||||
"""
|
||||
Updates parent_id for plex_id
|
||||
"""
|
||||
query = "UPDATE plex SET parent_id = ? WHERE plex_id = ?"
|
||||
self.plexcursor.execute(query, (parent_kodiid, plexid))
|
||||
|
||||
def removeItems_byParentId(self, parent_id, kodi_type):
|
||||
"""
|
||||
Removes all entries with parent_id and kodi_type
|
||||
"""
|
||||
query = '''
|
||||
DELETE FROM plex
|
||||
WHERE parent_id = ?
|
||||
AND kodi_type = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (parent_id, kodi_type,))
|
||||
|
||||
def removeItem_byKodiId(self, kodi_id, kodi_type):
|
||||
"""
|
||||
Removes the one entry with kodi_id and kodi_type
|
||||
"""
|
||||
query = '''
|
||||
DELETE FROM plex
|
||||
WHERE kodi_id = ?
|
||||
AND kodi_type = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (kodi_id, kodi_type,))
|
||||
|
||||
def removeItem(self, plex_id):
|
||||
"""
|
||||
Removes the one entry with plex_id
|
||||
"""
|
||||
query = "DELETE FROM plex WHERE plex_id = ?"
|
||||
self.plexcursor.execute(query, (plex_id,))
|
||||
|
||||
def removeWildItem(self, plex_id):
|
||||
"""
|
||||
Removes all entries with plex_id with % added
|
||||
"""
|
||||
query = "DELETE FROM plex WHERE plex_id LIKE ?"
|
||||
self.plexcursor.execute(query, (plex_id+"%",))
|
||||
|
||||
def itemsByType(self, plex_type):
|
||||
"""
|
||||
Returns a list of dicts for plex_type:
|
||||
{
|
||||
'plexId': plex_id
|
||||
'kodiId': kodi_id
|
||||
'kodi_type': kodi_type
|
||||
'plex_type': plex_type
|
||||
}
|
||||
"""
|
||||
query = '''
|
||||
SELECT plex_id, kodi_id, kodi_type
|
||||
FROM plex
|
||||
WHERE plex_type = ?
|
||||
'''
|
||||
self.plexcursor.execute(query, (plex_type, ))
|
||||
result = []
|
||||
for row in self.plexcursor.fetchall():
|
||||
result.append({
|
||||
'plexId': row[0],
|
||||
'kodiId': row[1],
|
||||
'kodi_type': row[2],
|
||||
'plex_type': plex_type
|
||||
})
|
||||
return result
|
|
@ -101,7 +101,7 @@ class Read_EmbyServer():
|
|||
viewId = view['Id']
|
||||
|
||||
# Compare to view table in emby database
|
||||
emby = kodiSQL('emby')
|
||||
emby = kodiSQL('plex')
|
||||
cursor_emby = emby.cursor()
|
||||
query = ' '.join((
|
||||
|
||||
|
|
|
@ -32,8 +32,10 @@ class UserClient(threading.Thread):
|
|||
# Borg - multiple instances, shared state
|
||||
__shared_state = {}
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, callback=None):
|
||||
self.__dict__ = self.__shared_state
|
||||
if callback is not None:
|
||||
self.mgr = callback
|
||||
|
||||
self.auth = True
|
||||
self.retry = 0
|
||||
|
|
|
@ -15,7 +15,6 @@ from functools import wraps
|
|||
from calendar import timegm
|
||||
import os
|
||||
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
@ -57,6 +56,24 @@ def window(property, value=None, clear=False, windowid=10000):
|
|||
return tryDecode(win.getProperty(property))
|
||||
|
||||
|
||||
def pickl_window(property, value=None, clear=False, windowid=10000):
|
||||
"""
|
||||
Get or set window property - thread safe! For use with Pickle
|
||||
Property and value must be string
|
||||
"""
|
||||
if windowid != 10000:
|
||||
win = xbmcgui.Window(windowid)
|
||||
else:
|
||||
win = WINDOW
|
||||
|
||||
if clear:
|
||||
win.clearProperty(property)
|
||||
elif value is not None:
|
||||
win.setProperty(property, value)
|
||||
else:
|
||||
return win.getProperty(property)
|
||||
|
||||
|
||||
def settings(setting, value=None):
|
||||
"""
|
||||
Get or add addon setting. Returns unicode
|
||||
|
@ -200,8 +217,8 @@ def getUnixTimestamp(secondsIntoTheFuture=None):
|
|||
|
||||
def kodiSQL(media_type="video"):
|
||||
|
||||
if media_type == "emby":
|
||||
dbPath = tryDecode(xbmc.translatePath("special://database/emby.db"))
|
||||
if media_type == "plex":
|
||||
dbPath = tryDecode(xbmc.translatePath("special://database/plex.db"))
|
||||
elif media_type == "music":
|
||||
dbPath = getKodiMusicDBPath()
|
||||
elif media_type == "texture":
|
||||
|
@ -346,7 +363,7 @@ def reset():
|
|||
|
||||
# Wipe the Plex database
|
||||
log.info("Resetting the Plex database.")
|
||||
connection = kodiSQL('emby')
|
||||
connection = kodiSQL('plex')
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||
rows = cursor.fetchall()
|
||||
|
@ -939,9 +956,33 @@ def ThreadMethods(cls):
|
|||
return cls
|
||||
|
||||
|
||||
class Lock_Function:
|
||||
"""
|
||||
Decorator for class methods and functions to lock them with lock.
|
||||
|
||||
Initialize this class first
|
||||
lockfunction = Lock_Function(lock), where lock is a threading.Lock() object
|
||||
|
||||
To then lock a function or method:
|
||||
|
||||
@lockfunction.lockthis
|
||||
def some_function(args, kwargs)
|
||||
"""
|
||||
def __init__(self, lock):
|
||||
self.lock = lock
|
||||
|
||||
def lockthis(self, func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
with self.lock:
|
||||
result = func(*args, **kwargs)
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
###############################################################################
|
||||
# UNUSED METHODS
|
||||
|
||||
|
||||
def changePlayState(itemType, kodiId, playCount, lastplayed):
|
||||
"""
|
||||
YET UNUSED
|
||||
|
@ -985,3 +1026,27 @@ def changePlayState(itemType, kodiId, playCount, lastplayed):
|
|||
result = json.loads(result)
|
||||
result = result.get('result')
|
||||
log.debug("JSON result was: %s" % result)
|
||||
|
||||
|
||||
class JSONRPC(object):
|
||||
id_ = 1
|
||||
jsonrpc = "2.0"
|
||||
|
||||
def __init__(self, method, **kwargs):
|
||||
self.method = method
|
||||
for arg in kwargs: # id_(int), jsonrpc(str)
|
||||
self.arg = arg
|
||||
|
||||
def _query(self):
|
||||
query = {
|
||||
'jsonrpc': self.jsonrpc,
|
||||
'id': self.id_,
|
||||
'method': self.method,
|
||||
}
|
||||
if self.params is not None:
|
||||
query['params'] = self.params
|
||||
return json.dumps(query)
|
||||
|
||||
def execute(self, params=None):
|
||||
self.params = params
|
||||
return json.loads(xbmc.executeJSONRPC(self._query()))
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
###############################################################################
|
||||
import logging
|
||||
import json
|
||||
import threading
|
||||
import Queue
|
||||
import websocket
|
||||
import ssl
|
||||
from json import loads
|
||||
from threading import Thread
|
||||
from Queue import Queue
|
||||
from ssl import CERT_NONE
|
||||
|
||||
import xbmc
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import window, settings, ThreadMethodsAdditionalSuspend, \
|
||||
ThreadMethods
|
||||
|
@ -22,21 +22,23 @@ log = logging.getLogger("PLEX."+__name__)
|
|||
|
||||
@ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
|
||||
@ThreadMethods
|
||||
class WebSocket(threading.Thread):
|
||||
class WebSocket(Thread):
|
||||
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
|
||||
|
||||
def __init__(self, queue):
|
||||
def __init__(self, callback=None):
|
||||
if callback is not None:
|
||||
self.mgr = callback
|
||||
self.ws = None
|
||||
# Communication with librarysync
|
||||
self.queue = queue
|
||||
threading.Thread.__init__(self)
|
||||
self.queue = Queue()
|
||||
Thread.__init__(self)
|
||||
|
||||
def process(self, opcode, message):
|
||||
if opcode not in self.opcode_data:
|
||||
return False
|
||||
|
||||
try:
|
||||
message = json.loads(message)
|
||||
message = loads(message)
|
||||
except Exception as ex:
|
||||
log.error('Error decoding message from websocket: %s' % ex)
|
||||
log.error(message)
|
||||
|
@ -57,13 +59,8 @@ class WebSocket(threading.Thread):
|
|||
return True
|
||||
|
||||
# Put PMS message on queue and let libsync take care of it
|
||||
try:
|
||||
self.queue.put(message)
|
||||
return True
|
||||
except Queue.Full:
|
||||
# Queue only takes 200 messages. No worries if we miss one or two
|
||||
log.info('Queue is full, dropping PMS message %s' % message)
|
||||
return False
|
||||
|
||||
def receive(self, ws):
|
||||
# Not connected yet
|
||||
|
@ -97,7 +94,7 @@ class WebSocket(threading.Thread):
|
|||
uri += '?X-Plex-Token=%s' % token
|
||||
sslopt = {}
|
||||
if settings('sslverify') == "false":
|
||||
sslopt["cert_reqs"] = ssl.CERT_NONE
|
||||
sslopt["cert_reqs"] = CERT_NONE
|
||||
log.debug("Uri: %s, sslopt: %s" % (uri, sslopt))
|
||||
return uri, sslopt
|
||||
|
||||
|
@ -122,7 +119,7 @@ class WebSocket(threading.Thread):
|
|||
# Abort was requested while waiting. We should exit
|
||||
log.info("##===---- WebSocketClient Stopped ----===##")
|
||||
return
|
||||
xbmc.sleep(1000)
|
||||
sleep(1000)
|
||||
try:
|
||||
self.process(*self.receive(self.ws))
|
||||
except websocket.WebSocketTimeoutException:
|
||||
|
@ -148,11 +145,11 @@ class WebSocket(threading.Thread):
|
|||
"declaring the connection dead")
|
||||
window('plex_online', value='false')
|
||||
counter = 0
|
||||
xbmc.sleep(1000)
|
||||
sleep(1000)
|
||||
except websocket.WebSocketTimeoutException:
|
||||
log.info("timeout while connecting, trying again")
|
||||
self.ws = None
|
||||
xbmc.sleep(1000)
|
||||
sleep(1000)
|
||||
except websocket.WebSocketException as e:
|
||||
log.info('WebSocketException: %s' % e)
|
||||
if 'Handshake Status 401' in e.args:
|
||||
|
@ -162,14 +159,14 @@ class WebSocket(threading.Thread):
|
|||
'WebSocketClient now')
|
||||
break
|
||||
self.ws = None
|
||||
xbmc.sleep(1000)
|
||||
sleep(1000)
|
||||
except Exception as e:
|
||||
log.error("Unknown exception encountered in connecting: %s"
|
||||
% e)
|
||||
import traceback
|
||||
log.error("Traceback:\n%s" % traceback.format_exc())
|
||||
self.ws = None
|
||||
xbmc.sleep(1000)
|
||||
sleep(1000)
|
||||
else:
|
||||
counter = 0
|
||||
handshake_counter = 0
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
|
||||
<category label="30516"><!-- Playback -->
|
||||
<setting type="sep" />
|
||||
<setting id="enableCinema" type="bool" label="30518" default="true" />
|
||||
<setting id="enableCinema" type="bool" label="30518" default="false" />
|
||||
<setting id="askCinema" type="bool" label="30519" default="false" visible="eq(-1,true)" subsetting="true" />
|
||||
<setting id="trailerNumber" type="slider" label="39000" default="3" visible="eq(-2,true)" range="1,1,15" option="int" />
|
||||
<setting id="pickPlexSubtitles" type="bool" label="39075" default="true" />
|
||||
|
|
111
service.py
111
service.py
|
@ -5,7 +5,6 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
import Queue
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
|
@ -33,17 +32,20 @@ sys.path.append(_base_resource)
|
|||
###############################################################################
|
||||
|
||||
from utils import settings, window, language as lang
|
||||
import userclient
|
||||
from userclient import UserClient
|
||||
import clientinfo
|
||||
import initialsetup
|
||||
import kodimonitor
|
||||
import librarysync
|
||||
from kodimonitor import KodiMonitor
|
||||
from librarysync import LibrarySync
|
||||
import videonodes
|
||||
import websocket_client as wsc
|
||||
from websocket_client import WebSocket
|
||||
import downloadutils
|
||||
from playqueue import Playqueue
|
||||
|
||||
import PlexAPI
|
||||
import PlexCompanion
|
||||
from PlexCompanion import PlexCompanion
|
||||
from monitor_kodi_play import Monitor_Kodi_Play
|
||||
from playback_starter import Playback_Starter
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -61,11 +63,19 @@ class Service():
|
|||
server_online = True
|
||||
warn_auth = True
|
||||
|
||||
userclient_running = False
|
||||
websocket_running = False
|
||||
user = None
|
||||
ws = None
|
||||
library = None
|
||||
plexCompanion = None
|
||||
playqueue = None
|
||||
|
||||
user_running = False
|
||||
ws_running = False
|
||||
library_running = False
|
||||
kodimonitor_running = False
|
||||
plexCompanion_running = False
|
||||
playqueue_running = False
|
||||
kodimonitor_running = False
|
||||
playback_starter_running = False
|
||||
|
||||
def __init__(self):
|
||||
|
||||
|
@ -96,13 +106,14 @@ class Service():
|
|||
"plex_online", "plex_serverStatus", "plex_onWake",
|
||||
"plex_dbCheck", "plex_kodiScan",
|
||||
"plex_shouldStop", "currUserId", "plex_dbScan",
|
||||
"plex_initialScan", "plex_customplaylist", "plex_playbackProps",
|
||||
"plex_initialScan", "plex_customplayqueue", "plex_playbackProps",
|
||||
"plex_runLibScan", "plex_username", "pms_token", "plex_token",
|
||||
"pms_server", "plex_machineIdentifier", "plex_servername",
|
||||
"plex_authenticated", "PlexUserImage", "useDirectPaths",
|
||||
"suspend_LibraryThread", "plex_terminateNow",
|
||||
"kodiplextimeoffset", "countError", "countUnauthorized",
|
||||
"plex_restricteduser", "plex_allows_mediaDeletion"
|
||||
"plex_restricteduser", "plex_allows_mediaDeletion",
|
||||
"plex_play_new_item", "plex_result"
|
||||
]
|
||||
for prop in properties:
|
||||
window(prop, clear=True)
|
||||
|
@ -111,7 +122,7 @@ class Service():
|
|||
videonodes.VideoNodes().clearProperties()
|
||||
|
||||
# Set the minimum database version
|
||||
window('plex_minDBVersion', value="1.1.5")
|
||||
window('plex_minDBVersion', value="1.5.2")
|
||||
|
||||
def getLogLevel(self):
|
||||
try:
|
||||
|
@ -126,16 +137,21 @@ class Service():
|
|||
monitor = self.monitor
|
||||
kodiProfile = xbmc.translatePath("special://profile")
|
||||
|
||||
# Detect playback start early on
|
||||
self.monitor_kodi_play = Monitor_Kodi_Play(self)
|
||||
self.monitor_kodi_play.start()
|
||||
|
||||
# Server auto-detect
|
||||
initialsetup.InitialSetup().setup()
|
||||
|
||||
# Queue for background sync
|
||||
queue = Queue.Queue()
|
||||
# Initialize important threads, handing over self for callback purposes
|
||||
self.user = UserClient(self)
|
||||
self.ws = WebSocket(self)
|
||||
self.library = LibrarySync(self)
|
||||
self.plexCompanion = PlexCompanion(self)
|
||||
self.playqueue = Playqueue(self)
|
||||
self.playback_starter = Playback_Starter(self)
|
||||
|
||||
# Initialize important threads
|
||||
user = userclient.UserClient()
|
||||
ws = wsc.WebSocket(queue)
|
||||
library = librarysync.LibrarySync(queue)
|
||||
plx = PlexAPI.PlexAPI()
|
||||
|
||||
welcome_msg = True
|
||||
|
@ -157,7 +173,7 @@ class Service():
|
|||
if window('plex_online') == "true":
|
||||
# Plex server is online
|
||||
# Verify if user is set and has access to the server
|
||||
if (user.currUser is not None) and user.HasAccess:
|
||||
if (self.user.currUser is not None) and self.user.HasAccess:
|
||||
if not self.kodimonitor_running:
|
||||
# Start up events
|
||||
self.warn_auth = True
|
||||
|
@ -166,38 +182,46 @@ class Service():
|
|||
welcome_msg = False
|
||||
xbmcgui.Dialog().notification(
|
||||
heading=addonName,
|
||||
message="%s %s" % (lang(33000), user.currUser),
|
||||
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png",
|
||||
message="%s %s" % (lang(33000),
|
||||
self.user.currUser),
|
||||
icon="special://home/addons/plugin."
|
||||
"video.plexkodiconnect/icon.png",
|
||||
time=2000,
|
||||
sound=False)
|
||||
# Start monitoring kodi events
|
||||
self.kodimonitor_running = kodimonitor.KodiMonitor()
|
||||
|
||||
self.kodimonitor_running = KodiMonitor(self)
|
||||
# Start playqueue client
|
||||
if not self.playqueue_running:
|
||||
self.playqueue_running = True
|
||||
self.playqueue.start()
|
||||
# Start the Websocket Client
|
||||
if not self.websocket_running:
|
||||
self.websocket_running = True
|
||||
ws.start()
|
||||
if not self.ws_running:
|
||||
self.ws_running = True
|
||||
self.ws.start()
|
||||
# Start the syncing thread
|
||||
if not self.library_running:
|
||||
self.library_running = True
|
||||
library.start()
|
||||
self.library.start()
|
||||
# Start the Plex Companion thread
|
||||
if not self.plexCompanion_running:
|
||||
self.plexCompanion_running = True
|
||||
plexCompanion = PlexCompanion.PlexCompanion()
|
||||
plexCompanion.start()
|
||||
self.plexCompanion.start()
|
||||
if not self.playback_starter_running:
|
||||
self.playback_starter_running = True
|
||||
self.playback_starter.start()
|
||||
else:
|
||||
if (user.currUser is None) and self.warn_auth:
|
||||
# Alert user is not authenticated and suppress future warning
|
||||
if (self.user.currUser is None) and self.warn_auth:
|
||||
# Alert user is not authenticated and suppress future
|
||||
# warning
|
||||
self.warn_auth = False
|
||||
log.warn("Not authenticated yet.")
|
||||
|
||||
# User access is restricted.
|
||||
# Keep verifying until access is granted
|
||||
# unless server goes offline or Kodi is shut down.
|
||||
while user.HasAccess == False:
|
||||
while self.user.HasAccess is False:
|
||||
# Verify access with an API call
|
||||
user.hasAccess()
|
||||
self.user.hasAccess()
|
||||
|
||||
if window('plex_online') != "true":
|
||||
# Server went offline
|
||||
|
@ -211,7 +235,7 @@ class Service():
|
|||
# Wait until Plex server is online
|
||||
# or Kodi is shut down.
|
||||
while not monitor.abortRequested():
|
||||
server = user.getServer()
|
||||
server = self.user.getServer()
|
||||
if server is False:
|
||||
# No server info set in add-on settings
|
||||
pass
|
||||
|
@ -268,9 +292,9 @@ class Service():
|
|||
window('suspend_LibraryThread', clear=True)
|
||||
|
||||
# Start the userclient thread
|
||||
if not self.userclient_running:
|
||||
self.userclient_running = True
|
||||
user.start()
|
||||
if not self.user_running:
|
||||
self.user_running = True
|
||||
self.user.start()
|
||||
|
||||
break
|
||||
|
||||
|
@ -286,27 +310,22 @@ class Service():
|
|||
|
||||
# Tell all threads to terminate (e.g. several lib sync threads)
|
||||
window('plex_terminateNow', value='true')
|
||||
|
||||
try:
|
||||
plexCompanion.stopThread()
|
||||
self.plexCompanion.stopThread()
|
||||
except:
|
||||
log.warn('plexCompanion already shut down')
|
||||
|
||||
try:
|
||||
library.stopThread()
|
||||
self.library.stopThread()
|
||||
except:
|
||||
log.warn('Library sync already shut down')
|
||||
|
||||
try:
|
||||
ws.stopThread()
|
||||
self.ws.stopThread()
|
||||
except:
|
||||
log.warn('Websocket client already shut down')
|
||||
|
||||
try:
|
||||
user.stopThread()
|
||||
self.user.stopThread()
|
||||
except:
|
||||
log.warn('User client already shut down')
|
||||
|
||||
try:
|
||||
downloadutils.DownloadUtils().stopSession()
|
||||
except:
|
||||
|
|
Loading…
Reference in a new issue