PlexKodiConnect/resources/lib/entrypoint.py

448 lines
18 KiB
Python
Raw Normal View History

#!/usr/bin/env python
2015-12-25 07:07:00 +11:00
# -*- coding: utf-8 -*-
"""
Loads of different functions called in SEPARATE Python instances through
e.g. plugin://... calls. Hence be careful to only rely on window variables.
"""
from __future__ import absolute_import, division, unicode_literals
2017-12-10 00:35:08 +11:00
from logging import getLogger
2019-01-09 04:00:54 +11:00
import sys
import xbmc
2015-12-25 07:07:00 +11:00
import xbmcplugin
2017-01-25 05:59:38 +11:00
from xbmcgui import ListItem
2015-12-25 07:07:00 +11:00
2018-06-22 03:24:37 +10:00
from . import utils
from . import path_ops
2018-06-22 03:24:37 +10:00
from .downloadutils import DownloadUtils as DU
from .plex_api import API
from . import plex_functions as PF
from . import variables as v
2018-11-19 00:59:17 +11:00
# Be careful - your using app in another Python instance!
2019-01-09 04:00:54 +11:00
from . import app, widgets
2016-01-02 00:40:40 +11:00
2018-06-22 03:24:37 +10:00
LOG = getLogger('PLEX.entrypoint')
2016-08-30 23:51:11 +10:00
2019-01-09 04:00:54 +11:00
def guess_content_type():
"""
Returns either 'video', 'audio' or 'image', based how the user navigated to
the current view.
Returns None if this failed, e.g. when the user picks widgets
"""
content_type = None
if xbmc.getCondVisibility('Window.IsActive(Videos)'):
content_type = 'video'
elif xbmc.getCondVisibility('Window.IsActive(Music)'):
content_type = 'audio'
elif xbmc.getCondVisibility('Window.IsActive(Pictures)'):
content_type = 'image'
elif xbmc.getCondVisibility('Container.Content(movies)'):
content_type = 'video'
elif xbmc.getCondVisibility('Container.Content(episodes)'):
content_type = 'video'
elif xbmc.getCondVisibility('Container.Content(seasons)'):
content_type = 'video'
elif xbmc.getCondVisibility('Container.Content(tvshows)'):
content_type = 'video'
elif xbmc.getCondVisibility('Container.Content(albums)'):
content_type = 'audio'
elif xbmc.getCondVisibility('Container.Content(artists)'):
content_type = 'audio'
elif xbmc.getCondVisibility('Container.Content(songs)'):
content_type = 'audio'
elif xbmc.getCondVisibility('Container.Content(pictures)'):
content_type = 'image'
LOG.debug('Guessed content type: %s', content_type)
return content_type
2016-08-30 23:51:11 +10:00
2018-06-15 21:47:22 +10:00
def directory_item(label, path, folder=True):
"""
Adds a xbmcplugin.addDirectoryItem() directory itemlistitem
"""
listitem = ListItem(label, path=path)
listitem.setThumbnailImage(
"special://home/addons/plugin.video.plexkodiconnect/icon.png")
listitem.setArt(
{"fanart": "special://home/addons/plugin.video.plexkodiconnect/fanart.jpg"})
listitem.setArt(
{"landscape":"special://home/addons/plugin.video.plexkodiconnect/fanart.jpg"})
2019-01-09 04:00:54 +11:00
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
2018-06-15 21:47:22 +10:00
url=path,
listitem=listitem,
isFolder=folder)
2015-12-25 07:07:00 +11:00
2016-08-30 23:51:11 +10:00
2018-06-15 21:49:18 +10:00
def show_main_menu(content_type=None):
"""
Shows the main PKC menu listing with all libraries, Channel, settings, etc.
"""
2019-01-09 04:00:54 +11:00
content_type = content_type or guess_content_type()
LOG.debug('Do main listing for content_type: %s', content_type)
xbmcplugin.setContent(int(sys.argv[1]), 'files')
# Get nodes from the window props
totalnodes = int(utils.window('Plex.nodes.total') or 0)
for i in range(totalnodes):
path = utils.window('Plex.nodes.%s.content' % i)
if not path:
continue
label = utils.window('Plex.nodes.%s.title' % i)
node_type = utils.window('Plex.nodes.%s.type' % i)
# because we do not use seperate entrypoints for each content type,
# we need to figure out which items to show in each listing. for
# now we just only show picture nodes in the picture library video
# nodes in the video library and all nodes in any other window
if node_type == 'photos' and content_type == 'image':
directory_item(label, path)
elif node_type in ('artists',
'albums',
'songs') and content_type == 'audio':
directory_item(label, path)
elif node_type in ('movies',
'tvshows',
'homevideos',
'musicvideos') and content_type == 'video':
directory_item(label, path)
elif content_type is None:
# To let the user pick this node as a WIDGET (content_type is None)
# Should only be called if the user selects widgets
LOG.info('Detected user selecting widgets')
directory_item(label, path)
if not path.startswith('library://'):
# Already using add-on paths (e.g. section not synched)
continue
# Add ANOTHER menu item that uses add-on paths instead of direct
# paths in order to let the user navigate into all submenus
addon_path = utils.window('Plex.nodes.%s.addon_path' % i)
# Append "(More...)" to the label
directory_item('%s (%s)' % (label, utils.lang(22082)), addon_path)
2018-07-30 21:20:40 +10:00
# Playlists
if content_type != 'image':
2019-01-09 04:00:54 +11:00
path = 'plugin://%s?mode=playlists' % v.ADDON_ID
if content_type:
path += '&content_type=%s' % content_type
directory_item(utils.lang(136), path)
# Plex Hub
2019-01-09 04:00:54 +11:00
path = 'plugin://%s?mode=hub' % v.ADDON_ID
if content_type:
path += '&content_type=%s' % content_type
directory_item('Plex Hub', path)
2016-04-17 21:36:41 +10:00
# Plex Watch later
if content_type not in ('image', 'audio'):
2018-06-22 03:24:37 +10:00
directory_item(utils.lang(39211),
2018-06-15 21:49:18 +10:00
"plugin://%s?mode=watchlater" % v.ADDON_ID)
2017-03-08 02:37:36 +11:00
# Plex Channels
2018-06-22 03:24:37 +10:00
directory_item(utils.lang(30173), "plugin://%s?mode=channels" % v.ADDON_ID)
2016-03-11 02:04:01 +11:00
# Plex user switch
2018-06-22 03:24:37 +10:00
directory_item('%s%s' % (utils.lang(39200), utils.settings('username')),
2018-06-15 21:49:18 +10:00
"plugin://%s?mode=switchuser" % v.ADDON_ID)
# some extra entries for settings and stuff
2018-06-22 03:24:37 +10:00
directory_item(utils.lang(39201), "plugin://%s?mode=settings" % v.ADDON_ID)
directory_item(utils.lang(39204),
2018-06-15 21:49:18 +10:00
"plugin://%s?mode=manualsync" % v.ADDON_ID)
2019-01-09 04:00:54 +11:00
xbmcplugin.endOfDirectory(int(sys.argv[1]))
2016-01-25 20:36:24 +11:00
2019-01-09 04:00:54 +11:00
def show_listing(xml, plex_type=None, section_id=None, synched=True, key=None,
content_type=None):
2018-06-15 21:55:32 +10:00
"""
2019-01-09 04:00:54 +11:00
Pass synched=False if the items have not been synched to the Kodi DB
2018-06-15 21:55:32 +10:00
"""
2019-01-09 04:00:54 +11:00
content_type = content_type or guess_content_type()
LOG.debug('show_listing: content_type %s, section_id %s, synched %s, '
'key %s, plex_type %s', content_type, section_id, synched, key,
plex_type)
2017-08-03 02:54:05 +10:00
try:
2019-01-09 04:00:54 +11:00
xml[0]
except IndexError:
LOG.info('xml received from the PMS is empty: %s', xml.attrib)
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
return
if content_type == 'video':
xbmcplugin.setContent(int(sys.argv[1]), 'videos')
elif content_type == 'audio':
xbmcplugin.setContent(int(sys.argv[1]), 'artists')
elif plex_type in (v.PLEX_TYPE_PLAYLIST, v.PLEX_TYPE_CHANNEL):
xbmcplugin.setContent(int(sys.argv[1]), 'videos')
elif plex_type:
xbmcplugin.setContent(int(sys.argv[1]),
v.MEDIATYPE_FROM_PLEX_TYPE[plex_type])
else:
xbmcplugin.setContent(int(sys.argv[1]), 'files')
# Initialization
widgets.PLEX_TYPE = plex_type
widgets.SYNCHED = synched
if content_type and xml[0].tag == 'Playlist':
# Certain views mix playlist types audio and video
for entry in reversed(xml):
if entry.get('playlistType') != content_type:
xml.remove(entry)
if xml.get('librarySectionID'):
widgets.SECTION_ID = utils.cast(int, xml.get('librarySectionID'))
elif section_id:
widgets.SECTION_ID = utils.cast(int, section_id)
if xml.get('viewGroup') == 'secondary':
# Need to chain keys for navigation
widgets.KEY = key
# Process all items to show
widgets.attach_kodi_ids(xml)
all_items = widgets.process_method_on_list(widgets.generate_item, xml)
all_items = widgets.process_method_on_list(widgets.prepare_listitem,
all_items)
# fill that listing...
all_items = widgets.process_method_on_list(widgets.create_listitem,
all_items)
xbmcplugin.addDirectoryItems(int(sys.argv[1]), all_items, len(all_items))
# end directory listing
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_UNSORTED)
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
2015-12-25 07:07:00 +11:00
2018-06-15 22:05:48 +10:00
def get_video_files(plex_id, params):
"""
GET VIDEO EXTRAS FOR LISTITEM
returns the video files for the item as plugin listing, can be used for
browsing the actual files or videoextras etc.
"""
2018-06-15 22:05:48 +10:00
if plex_id is None:
filename = params.get('filename')
if filename is not None:
filename = filename[0]
import re
regex = re.compile(r'''library/metadata/(\d+)''')
filename = regex.findall(filename)
try:
2018-06-15 22:05:48 +10:00
plex_id = filename[0]
except IndexError:
pass
2018-06-15 22:05:48 +10:00
if plex_id is None:
2018-06-15 21:40:25 +10:00
LOG.info('No Plex ID found, abort getting Extras')
2019-01-09 04:00:54 +11:00
return xbmcplugin.endOfDirectory(int(sys.argv[1]))
app.init(entrypoint=True)
2018-06-22 03:24:37 +10:00
item = PF.GetPlexMetadata(plex_id)
try:
path = utils.try_decode(item[0][0][0].attrib['file'])
2018-06-15 22:05:48 +10:00
except (TypeError, IndexError, AttributeError, KeyError):
2018-06-15 22:15:39 +10:00
LOG.error('Could not get file path for item %s', plex_id)
2019-01-09 04:00:54 +11:00
return xbmcplugin.endOfDirectory(int(sys.argv[1]))
# Assign network protocol
if path.startswith('\\\\'):
path = path.replace('\\\\', 'smb://')
path = path.replace('\\', '/')
# Plex returns Windows paths as e.g. 'c:\slfkjelf\slfje\file.mkv'
elif '\\' in path:
path = path.replace('\\', '\\\\')
# Directory only, get rid of filename
path = path.replace(path_ops.path.basename(path), '')
if path_ops.exists(path):
for root, dirs, files in path_ops.walk(path):
for directory in dirs:
item_path = utils.try_encode(path_ops.path.join(root,
directory))
2018-06-15 22:05:48 +10:00
listitem = ListItem(item_path, path=item_path)
2019-01-09 04:00:54 +11:00
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
url=item_path,
2018-06-15 22:05:48 +10:00
listitem=listitem,
isFolder=True)
for file in files:
item_path = utils.try_encode(path_ops.path.join(root, file))
2018-06-15 22:05:48 +10:00
listitem = ListItem(item_path, path=item_path)
2019-01-09 04:00:54 +11:00
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
url=file,
2018-06-15 22:05:48 +10:00
listitem=listitem)
break
else:
2018-06-15 22:15:39 +10:00
LOG.error('Kodi cannot access folder %s', path)
2019-01-09 04:00:54 +11:00
xbmcplugin.endOfDirectory(int(sys.argv[1]))
2018-06-22 03:24:37 +10:00
@utils.catch_exceptions(warnuser=False)
2018-06-15 22:05:48 +10:00
def extra_fanart(plex_id, plex_path):
"""
Get extrafanart for listitem
will be called by skinhelper script to get the extrafanart
2018-06-15 22:05:48 +10:00
for tvshows we get the plex_id just from the path
"""
2018-06-15 22:05:48 +10:00
LOG.debug('Called with plex_id: %s, plex_path: %s', plex_id, plex_path)
if not plex_id:
if "plugin.video.plexkodiconnect" in plex_path:
plex_id = plex_path.split("/")[-2]
if not plex_id:
LOG.error('Could not get a plex_id, aborting')
2019-01-09 04:00:54 +11:00
return xbmcplugin.endOfDirectory(int(sys.argv[1]))
2016-07-23 00:55:57 +10:00
# We need to store the images locally for this to work
# because of the caching system in xbmc
fanart_dir = path_ops.translate_path("special://thumbnails/plex/%s/"
% plex_id)
if not path_ops.exists(fanart_dir):
2016-07-23 00:55:57 +10:00
# Download the images to the cache directory
path_ops.makedirs(fanart_dir)
app.init(entrypoint=True)
2018-06-22 03:24:37 +10:00
xml = PF.GetPlexMetadata(plex_id)
2016-07-23 00:55:57 +10:00
if xml is None:
2018-06-15 22:05:48 +10:00
LOG.error('Could not download metadata for %s', plex_id)
2019-01-09 04:00:54 +11:00
return xbmcplugin.endOfDirectory(int(sys.argv[1]))
2017-01-25 05:59:38 +11:00
api = API(xml[0])
2018-02-12 00:42:49 +11:00
backdrops = api.artwork()['Backdrop']
2016-07-23 00:55:57 +10:00
for count, backdrop in enumerate(backdrops):
# Same ordering as in artwork
art_file = utils.try_encode(path_ops.path.join(
fanart_dir, "fanart%.3d.jpg" % count))
2018-06-15 22:08:43 +10:00
listitem = ListItem("%.3d" % count, path=art_file)
2016-07-23 00:55:57 +10:00
xbmcplugin.addDirectoryItem(
2019-01-09 04:00:54 +11:00
handle=int(sys.argv[1]),
2018-06-15 22:07:53 +10:00
url=art_file,
2018-06-15 22:08:43 +10:00
listitem=listitem)
path_ops.copyfile(backdrop, utils.try_decode(art_file))
2016-07-23 00:55:57 +10:00
else:
2018-06-15 21:40:25 +10:00
LOG.info("Found cached backdrop.")
2016-07-23 00:55:57 +10:00
# Use existing cached images
fanart_dir = utils.try_decode(fanart_dir)
for root, _, files in path_ops.walk(fanart_dir):
root = utils.decode_path(root)
for file in files:
file = utils.decode_path(file)
art_file = utils.try_encode(path_ops.path.join(root, file))
2018-06-15 22:08:43 +10:00
listitem = ListItem(file, path=art_file)
2019-01-09 04:00:54 +11:00
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
2018-06-15 22:07:53 +10:00
url=art_file,
2018-06-15 22:08:43 +10:00
listitem=listitem)
2019-01-09 04:00:54 +11:00
xbmcplugin.endOfDirectory(int(sys.argv[1]))
2016-04-17 21:36:41 +10:00
2018-07-30 21:20:40 +10:00
def playlists(content_type):
"""
2018-07-19 22:53:16 +10:00
Lists all Plex playlists of the media type plex_playlist_type
2018-07-30 21:20:40 +10:00
content_type: 'audio', 'video'
"""
2019-01-09 04:00:54 +11:00
content_type = content_type or guess_content_type()
2018-07-30 21:20:40 +10:00
LOG.debug('Listing Plex %s playlists', content_type)
app.init(entrypoint=True)
from .playlists.pms import all_playlists
xml = all_playlists()
if xml is None:
return
2019-01-09 04:00:54 +11:00
if content_type is not None:
# This will be skipped if user selects a widget
# Buggy xml.remove(child) requires reversed()
for entry in reversed(xml):
api = API(entry)
if not api.playlist_type() == content_type:
xml.remove(entry)
show_listing(xml, content_type=content_type)
def hub(content_type):
"""
Plus hub endpoint pms:port/hubs. Need to separate Kodi types with
content_type:
audio, video, image
"""
2019-01-09 04:00:54 +11:00
content_type = content_type or guess_content_type()
LOG.debug('Showing Plex Hub entries for %s', content_type)
app.init(entrypoint=True)
xml = PF.get_plex_hub()
try:
xml.attrib
except AttributeError:
LOG.error('Could not get Plex hub listing')
2019-01-09 04:00:54 +11:00
return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
# We need to make sure that only entries that WORK are displayed
# WARNING: using xml.remove(child) in for-loop requires traversing from
# the end!
for entry in reversed(xml):
api = API(entry)
2019-01-09 04:00:54 +11:00
append = False
if content_type == 'video' and api.plex_type() in v.PLEX_VIDEOTYPES:
2019-01-09 04:00:54 +11:00
append = True
elif content_type == 'audio' and api.plex_type() in v.PLEX_AUDIOTYPES:
2019-01-09 04:00:54 +11:00
append = True
elif content_type == 'image' and api.plex_type() == v.PLEX_TYPE_PHOTO:
2019-01-09 04:00:54 +11:00
append = True
elif content_type != 'image' and api.plex_type() == v.PLEX_TYPE_PLAYLIST:
append = True
elif content_type is None:
# Needed for widgets, where no content_type is provided
2019-01-09 04:00:54 +11:00
append = True
if not append:
xml.remove(entry)
show_listing(xml, content_type=content_type)
2016-04-17 21:36:41 +10:00
def watchlater():
"""
Listing for plex.tv Watch Later section (if signed in to plex.tv)
"""
2018-06-22 03:24:37 +10:00
if utils.window('plex_token') == '':
2018-06-15 21:40:25 +10:00
LOG.error('No watch later - not signed in to plex.tv')
2019-01-09 04:00:54 +11:00
return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
2018-06-22 03:24:37 +10:00
if utils.window('plex_restricteduser') == 'true':
2018-06-15 21:40:25 +10:00
LOG.error('No watch later - restricted user')
2019-01-09 04:00:54 +11:00
return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
2016-04-17 21:36:41 +10:00
app.init(entrypoint=True)
2018-06-22 03:24:37 +10:00
xml = DU().downloadUrl('https://plex.tv/pms/playlists/queue/all',
authenticate=False,
headerOptions={'X-Plex-Token': utils.window('plex_token')})
2016-04-17 21:36:41 +10:00
if xml in (None, 401):
2018-06-15 21:40:25 +10:00
LOG.error('Could not download watch later list from plex.tv')
2019-01-09 04:00:54 +11:00
return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
show_listing(xml)
2016-04-17 21:36:41 +10:00
2016-05-30 00:52:00 +10:00
2019-01-09 04:00:54 +11:00
def browse_plex(key=None, plex_type=None, section_id=None, synched=True,
prompt=None):
2017-03-08 02:37:36 +11:00
"""
2017-03-09 02:21:00 +11:00
Lists the content of a Plex folder, e.g. channels. Either pass in key (to
2019-01-09 04:00:54 +11:00
be used directly for PMS url {server}<key>) or the section_id
Pass synched=False if the items have NOT been synched to the Kodi DB
2017-03-08 02:37:36 +11:00
"""
2019-03-10 01:42:33 +11:00
LOG.debug('Browsing to key %s, section %s, plex_type: %s, synched: %s, '
'prompt "%s"', key, section_id, plex_type, synched, prompt)
app.init(entrypoint=True)
2019-03-10 01:42:33 +11:00
if prompt:
prompt = utils.dialog('input', prompt)
if prompt is None:
# User cancelled
return
prompt = prompt.strip().decode('utf-8')
if '?' not in key:
key = '%s?query=%s' % (key, prompt)
else:
key = '%s&query=%s' % (key, prompt)
2019-01-09 04:00:54 +11:00
xml = DU().downloadUrl('{server}%s' % key)
2017-03-08 02:37:36 +11:00
try:
xml.attrib
except AttributeError:
2018-07-19 23:07:08 +10:00
LOG.error('Could not browse to key %s, section %s',
2019-01-09 04:00:54 +11:00
key, section_id)
show_listing(xml, plex_type, section_id, synched, key)
2017-03-08 02:37:36 +11:00
2018-05-05 03:03:27 +10:00
def extras(plex_id):
"""
Lists all extras for plex_id
"""
app.init(entrypoint=True)
2018-06-22 03:24:37 +10:00
xml = PF.GetPlexMetadata(plex_id)
2018-05-05 03:03:27 +10:00
try:
xml[0].attrib
except (TypeError, IndexError, KeyError):
2019-01-09 04:00:54 +11:00
xbmcplugin.endOfDirectory(int(sys.argv[1]))
2018-05-05 03:03:27 +10:00
return
2019-01-09 04:00:54 +11:00
extras = API(xml[0]).extras()
if not extras:
return
for child in xml:
xml.remove(child)
for i, child in enumerate(extras):
xml.insert(i, child)
show_listing(xml, synched=False, plex_type=v.PLEX_TYPE_MOVIE)