PlexKodiConnect/resources/lib/entrypoint.py

976 lines
35 KiB
Python
Raw Normal View History

2015-12-25 07:07:00 +11:00
# -*- coding: utf-8 -*-
2018-06-15 21:40:25 +10:00
#
# Loads of different functions called in SEPARATE Python instances through
# e.g. plugin://... calls. Hence be careful to only rely on window variables.
#
2016-02-20 06:03:06 +11:00
###############################################################################
2017-12-10 00:35:08 +11:00
from logging import getLogger
from shutil import copyfile
from os import walk, makedirs
from os.path import basename, join
2017-01-25 05:59:38 +11:00
from sys import argv
from urllib import urlencode
2015-12-25 07:07:00 +11:00
import xbmcplugin
2017-03-26 22:23:37 +11:00
from xbmc import sleep, executebuiltin, translatePath
2017-01-25 05:59:38 +11:00
from xbmcgui import ListItem
2015-12-25 07:07:00 +11:00
2018-02-11 22:59:04 +11:00
from utils import window, settings, language as lang, dialog, try_encode, \
2018-02-11 23:24:00 +11:00
catch_exceptions, exists_dir, plex_command, try_decode
2015-12-25 07:07:00 +11:00
import downloadutils
2017-01-25 05:59:38 +11:00
from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \
GetMachineIdentifier
from PlexAPI import API
2017-12-09 05:43:06 +11:00
import json_rpc as js
import variables as v
2016-01-02 00:40:40 +11:00
2016-02-20 06:03:06 +11:00
###############################################################################
2018-06-15 21:40:25 +10:00
LOG = getLogger("PLEX." + __name__)
2016-08-30 23:51:11 +10:00
2017-01-25 06:04:53 +11:00
try:
HANDLE = int(argv[1])
ARGV_0 = argv[0]
except IndexError:
pass
2016-08-30 23:51:11 +10:00
###############################################################################
2016-01-30 06:07:21 +11:00
2018-06-15 21:44:46 +10:00
def choose_pms_server():
"""
2016-05-30 00:52:00 +10:00
Lets user choose from list of PMS
"""
2018-06-15 21:40:25 +10:00
LOG.info("Choosing PMS server requested, starting")
import initialsetup
2016-05-25 04:43:52 +10:00
setup = initialsetup.InitialSetup()
2018-02-11 03:59:20 +11:00
server = setup.pick_pms(showDialog=True)
2016-05-25 04:43:52 +10:00
if server is None:
2018-06-15 21:40:25 +10:00
LOG.error('We did not connect to a new PMS, aborting')
plex_command('SUSPEND_USER_CLIENT', 'False')
plex_command('SUSPEND_LIBRARY_THREAD', 'False')
2016-05-25 04:43:52 +10:00
return
2018-06-15 22:15:39 +10:00
LOG.info("User chose server %s", server['name'])
2018-02-11 03:59:20 +11:00
setup.write_pms_to_settings(server)
2018-06-15 22:15:39 +10:00
if not _log_out():
2016-05-30 00:52:00 +10:00
return
2018-03-01 04:48:39 +11:00
from utils import wipe_database
# Wipe Kodi and Plex database as well as playlists and video nodes
wipe_database()
2016-05-30 01:17:33 +10:00
2016-05-30 00:52:00 +10:00
# Log in again
2018-06-15 22:15:39 +10:00
_log_in()
2018-06-15 21:40:25 +10:00
LOG.info("Choosing new PMS complete")
2016-05-30 00:52:00 +10:00
# '<PMS> connected'
2017-01-25 05:59:38 +11:00
dialog('notification',
lang(29999),
'%s %s' % (server['name'], lang(39220)),
icon='{plex}',
time=3000,
sound=False)
2018-06-15 21:44:46 +10:00
def toggle_plex_tv_sign_in():
"""
Signs out of Plex.tv if there was a token saved and thus deletes the token.
Or signs in to plex.tv if the user was not logged in before.
"""
2016-08-30 23:51:11 +10:00
if settings('plexToken'):
2018-06-15 21:40:25 +10:00
LOG.info('Reseting plex.tv credentials in settings')
2016-08-30 23:51:11 +10:00
settings('plexLogin', value="")
2017-05-09 03:29:52 +10:00
settings('plexToken', value="")
2016-08-30 23:51:11 +10:00
settings('plexid', value="")
settings('plexHomeSize', value="1")
settings('plexAvatar', value="")
settings('plex_status', value=lang(39226))
2016-08-30 23:51:11 +10:00
window('plex_token', clear=True)
plex_command('PLEX_TOKEN', '')
2017-05-18 00:14:17 +10:00
plex_command('PLEX_USERNAME', '')
2016-05-30 00:52:00 +10:00
else:
2018-06-15 21:40:25 +10:00
LOG.info('Login to plex.tv')
2016-05-30 00:52:00 +10:00
import initialsetup
2018-02-11 03:59:20 +11:00
initialsetup.InitialSetup().plex_tv_sign_in()
2017-01-25 05:59:38 +11:00
dialog('notification',
lang(29999),
lang(39221),
icon='{plex}',
time=3000,
sound=False)
2016-03-04 23:34:30 +11:00
2018-06-15 21:44:46 +10:00
def reset_authorization():
"""
User tried login and failed too many times. Reset # of logins
"""
2017-01-25 05:59:38 +11:00
resp = dialog('yesno', heading="{plex}", line1=lang(39206))
2015-12-25 07:07:00 +11:00
if resp == 1:
2018-06-15 21:40:25 +10:00
LOG.info("Reset login attempts.")
plex_command('PMS_STATUS', 'Auth')
2015-12-25 07:07:00 +11:00
else:
2017-01-25 05:59:38 +11:00
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
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"})
xbmcplugin.addDirectoryItem(handle=HANDLE,
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.
"""
2018-06-15 22:15:39 +10:00
LOG.debug('Do main listing with content_type: %s', content_type)
2017-01-25 05:59:38 +11:00
xbmcplugin.setContent(HANDLE, 'files')
2015-12-25 07:07:00 +11:00
# Get emby nodes from the window props
2016-08-30 23:51:11 +10:00
plexprops = window('Plex.nodes.total')
2016-05-31 16:06:42 +10:00
if plexprops:
totalnodes = int(plexprops)
2015-12-25 07:07:00 +11:00
for i in range(totalnodes):
2016-08-30 23:51:11 +10:00
path = window('Plex.nodes.%s.index' % i)
2015-12-25 07:07:00 +11:00
if not path:
2016-08-30 23:51:11 +10:00
path = window('Plex.nodes.%s.content' % i)
if not path:
continue
2016-08-30 23:51:11 +10:00
label = window('Plex.nodes.%s.title' % i)
node_type = 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':
2018-06-15 21:47:22 +10:00
directory_item(label, path)
elif (node_type != 'photos' and
content_type not in ('image', 'audio')):
2018-06-15 21:47:22 +10:00
directory_item(label, path)
2016-04-17 21:36:41 +10:00
# Plex Watch later
if content_type not in ('image', 'audio'):
2018-06-15 21:47:22 +10:00
directory_item(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-15 21:47:22 +10:00
directory_item(lang(30173),
2018-06-15 21:49:18 +10:00
"plugin://%s?mode=channels" % v.ADDON_ID)
2016-03-11 02:04:01 +11:00
# Plex user switch
2018-06-15 21:47:22 +10:00
directory_item('%s%s' % (lang(39200), 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-15 21:47:22 +10:00
directory_item(lang(39201),
2018-06-15 21:49:18 +10:00
"plugin://%s?mode=settings" % v.ADDON_ID)
2018-06-15 21:47:22 +10:00
directory_item(lang(39203),
2018-06-15 21:49:18 +10:00
"plugin://%s?mode=refreshplaylist" % v.ADDON_ID)
2018-06-15 21:47:22 +10:00
directory_item(lang(39204),
2018-06-15 21:49:18 +10:00
"plugin://%s?mode=manualsync" % v.ADDON_ID)
2017-01-25 05:59:38 +11:00
xbmcplugin.endOfDirectory(HANDLE)
2015-12-25 07:07:00 +11:00
2016-02-29 16:20:59 +11:00
2018-06-15 21:50:04 +10:00
def switch_plex_user():
2016-01-25 20:36:24 +11:00
"""
Signs out currently logged in user (if applicable). Triggers sign-in of a
new user
"""
# Guess these user avatars are a future feature. Skipping for now
# Delete any userimages. Since there's always only 1 user: position = 0
# position = 0
2016-08-30 23:51:11 +10:00
# window('EmbyAdditionalUserImage.%s' % position, clear=True)
2018-06-15 21:40:25 +10:00
LOG.info("Plex home user switch requested")
2018-06-15 22:15:39 +10:00
if not _log_out():
2016-05-30 00:52:00 +10:00
return
# First remove playlists of old user
2018-02-11 22:59:04 +11:00
from utils import delete_playlists, delete_nodes
delete_playlists()
# Remove video nodes
2018-02-11 22:59:04 +11:00
delete_nodes()
2018-06-15 22:15:39 +10:00
_log_in()
2016-01-25 20:36:24 +11:00
2018-06-15 21:55:32 +10:00
def create_listitem(item, append_show_title=False, append_sxxexx=False):
"""
Feed with a Kodi json item response to get a xbmcgui.ListItem() with
everything set and ready.
"""
2015-12-25 07:07:00 +11:00
title = item['title']
2018-06-15 21:55:32 +10:00
listitem = ListItem(title)
listitem.setProperty('IsPlayable', 'true')
metadata = {
2018-06-15 21:55:32 +10:00
'duration': str(item['runtime'] / 60),
2015-12-25 07:07:00 +11:00
'Plot': item['plot'],
'Playcount': item['playcount']
}
if 'episode' in item:
2015-12-25 07:07:00 +11:00
episode = item['episode']
metadata['Episode'] = episode
if 'season' in item:
2015-12-25 07:07:00 +11:00
season = item['season']
metadata['Season'] = season
if season and episode:
2018-06-15 21:55:32 +10:00
listitem.setProperty('episodeno', 's%.2de%.2d' % (season, episode))
2018-02-12 00:42:49 +11:00
if append_sxxexx is True:
title = 'S%.2dE%.2d - %s' % (season, episode, title)
if 'firstaired' in item:
2015-12-25 07:07:00 +11:00
metadata['Premiered'] = item['firstaired']
if 'showtitle' in item:
2015-12-25 07:07:00 +11:00
metadata['TVshowTitle'] = item['showtitle']
2018-02-12 00:42:49 +11:00
if append_show_title is True:
title = item['showtitle'] + ' - ' + title
if 'rating' in item:
metadata['Rating'] = str(round(float(item['rating']), 1))
if 'director' in item:
metadata['Director'] = item['director']
if 'writer' in item:
metadata['Writer'] = item['writer']
if 'cast' in item:
2015-12-25 07:07:00 +11:00
cast = []
castandrole = []
for person in item['cast']:
name = person['name']
cast.append(name)
castandrole.append((name, person['role']))
metadata['Cast'] = cast
metadata['CastAndRole'] = castandrole
metadata['Title'] = title
metadata['mediatype'] = 'episode'
metadata['dbid'] = str(item['episodeid'])
2018-06-15 21:55:32 +10:00
listitem.setLabel(title)
listitem.setInfo(type='Video', infoLabels=metadata)
listitem.setProperty('resumetime', str(item['resume']['position']))
listitem.setProperty('totaltime', str(item['resume']['total']))
listitem.setArt(item['art'])
listitem.setThumbnailImage(item['art'].get('thumb', ''))
listitem.setArt({'icon': 'DefaultTVShows.png'})
listitem.setProperty('fanart_image', item['art'].get('tvshow.fanart', ''))
2017-08-03 02:54:05 +10:00
try:
2018-06-15 21:55:32 +10:00
listitem.addContextMenuItems([(lang(30032), 'XBMC.Action(Info)',)])
2017-08-03 02:54:05 +10:00
except TypeError:
# Kodi fuck-up
pass
2015-12-25 07:07:00 +11:00
for key, value in item['streamdetails'].iteritems():
for stream in value:
2018-06-15 21:55:32 +10:00
listitem.addStreamInfo(key, stream)
return listitem
2015-12-25 07:07:00 +11:00
2018-06-15 21:57:33 +10:00
def next_up_episodes(tagname, limit):
"""
List the next up episodes for tagname.
"""
2015-12-25 07:07:00 +11:00
count = 0
# if the addon is called with nextup parameter,
# we return the nextepisodes list of the given tagname
2017-01-25 05:59:38 +11:00
xbmcplugin.setContent(HANDLE, 'episodes')
2015-12-25 07:07:00 +11:00
# First we get a list of all the TV shows - filtered by tag
2017-01-25 05:59:38 +11:00
params = {
'sort': {'order': "descending", 'method': "lastplayed"},
'filter': {
'and': [
{'operator': "true", 'field': "inprogress", 'value': ""},
{'operator': "is", 'field': "tag", 'value': "%s" % tagname}
]},
'properties': ['title', 'studio', 'mpaa', 'file', 'art']
2015-12-25 07:07:00 +11:00
}
2017-12-09 05:43:06 +11:00
for item in js.get_tv_shows(params):
if settings('ignoreSpecialsNextEpisodes') == "true":
params = {
'tvshowid': item['tvshowid'],
'sort': {'method': "episode"},
'filter': {
'and': [
{'operator': "lessthan",
'field': "playcount",
'value': "1"},
{'operator': "greaterthan",
'field': "season",
'value': "0"}]},
'properties': [
"title", "playcount", "season", "episode", "showtitle",
"plot", "file", "rating", "resume", "tvshowid", "art",
"streamdetails", "firstaired", "runtime", "writer",
"dateadded", "lastplayed"
],
'limits': {"end": 1}
}
else:
params = {
'tvshowid': item['tvshowid'],
'sort': {'method': "episode"},
'filter': {
'operator': "lessthan",
'field': "playcount",
'value': "1"},
'properties': [
"title", "playcount", "season", "episode", "showtitle",
"plot", "file", "rating", "resume", "tvshowid", "art",
"streamdetails", "firstaired", "runtime", "writer",
"dateadded", "lastplayed"
],
'limits': {"end": 1}
}
for episode in js.get_episodes(params):
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=episode['file'],
2018-06-15 21:55:32 +10:00
listitem=create_listitem(episode))
2017-12-09 05:43:06 +11:00
count += 1
if count == limit:
break
2017-01-25 05:59:38 +11:00
xbmcplugin.endOfDirectory(handle=HANDLE)
2015-12-25 07:07:00 +11:00
2018-06-15 21:58:39 +10:00
def in_progress_episodes(tagname, limit):
"""
List the episodes that are in progress for tagname
"""
2015-12-25 07:07:00 +11:00
count = 0
# if the addon is called with inprogressepisodes parameter,
# we return the inprogressepisodes list of the given tagname
2017-01-25 05:59:38 +11:00
xbmcplugin.setContent(HANDLE, 'episodes')
2015-12-25 07:07:00 +11:00
# First we get a list of all the in-progress TV shows - filtered by tag
2017-01-25 05:59:38 +11:00
params = {
'sort': {'order': "descending", 'method': "lastplayed"},
'filter': {
'and': [
{'operator': "true", 'field': "inprogress", 'value': ""},
{'operator': "is", 'field': "tag", 'value': "%s" % tagname}
]},
'properties': ['title', 'studio', 'mpaa', 'file', 'art']
2015-12-25 07:07:00 +11:00
}
2017-12-09 05:43:06 +11:00
for item in js.get_tv_shows(params):
params = {
'tvshowid': item['tvshowid'],
'sort': {'method': "episode"},
'filter': {
'operator': "true",
'field': "inprogress",
'value': ""},
'properties': ["title", "playcount", "season", "episode",
2018-06-15 22:00:12 +10:00
"showtitle", "plot", "file", "rating", "resume",
"tvshowid", "art", "cast", "streamdetails",
"firstaired", "runtime", "writer", "dateadded",
"lastplayed"]
2017-12-09 05:43:06 +11:00
}
for episode in js.get_episodes(params):
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=episode['file'],
2018-06-15 21:55:32 +10:00
listitem=create_listitem(episode))
2017-12-09 05:43:06 +11:00
count += 1
if count == limit:
break
2017-01-25 05:59:38 +11:00
xbmcplugin.endOfDirectory(handle=HANDLE)
2015-12-25 07:07:00 +11:00
2018-06-15 22:00:12 +10:00
2018-06-15 22:01:09 +10:00
def recent_episodes(mediatype, tagname, limit):
2018-06-15 22:00:12 +10:00
"""
List the recently added episodes for tagname
"""
2015-12-25 07:07:00 +11:00
count = 0
# if the addon is called with recentepisodes parameter,
# we return the recentepisodes list of the given tagname
2017-01-25 05:59:38 +11:00
xbmcplugin.setContent(HANDLE, 'episodes')
2018-02-12 00:42:49 +11:00
append_show_title = settings('RecentTvAppendShow') == 'true'
append_sxxexx = settings('RecentTvAppendSeason') == 'true'
2015-12-25 07:07:00 +11:00
# First we get a list of all the TV shows - filtered by tag
2018-06-15 22:05:48 +10:00
show_ids = set()
2017-01-25 05:59:38 +11:00
params = {
'sort': {'order': "descending", 'method': "dateadded"},
'filter': {'operator': "is", 'field': "tag", 'value': "%s" % tagname},
2015-12-25 07:07:00 +11:00
}
2017-12-09 05:43:06 +11:00
for tv_show in js.get_tv_shows(params):
2018-06-15 22:05:48 +10:00
show_ids.add(tv_show['tvshowid'])
2017-01-25 05:59:38 +11:00
params = {
'sort': {'order': "descending", 'method': "dateadded"},
'properties': ["title", "playcount", "season", "episode", "showtitle",
"plot", "file", "rating", "resume", "tvshowid", "art",
"streamdetails", "firstaired", "runtime", "cast", "writer",
"dateadded", "lastplayed"],
"limits": {"end": limit}
2016-03-16 00:19:56 +11:00
}
2016-08-30 23:51:11 +10:00
if settings('TVShowWatched') == 'false':
2017-01-25 05:59:38 +11:00
params['filter'] = {
'operator': "lessthan",
'field': "playcount",
'value': "1"
}
2017-12-09 05:43:06 +11:00
for episode in js.get_episodes(params):
2018-06-15 22:05:48 +10:00
if episode['tvshowid'] in show_ids:
2018-06-15 21:55:32 +10:00
listitem = create_listitem(episode,
append_show_title=append_show_title,
append_sxxexx=append_sxxexx)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=episode['file'],
listitem=listitem)
2017-12-09 05:43:06 +11:00
count += 1
if count == limit:
break
2017-01-25 05:59:38 +11:00
xbmcplugin.endOfDirectory(handle=HANDLE)
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')
2017-01-25 05:59:38 +11:00
return xbmcplugin.endOfDirectory(HANDLE)
2018-06-15 22:05:48 +10:00
item = GetPlexMetadata(plex_id)
try:
path = 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)
2017-01-25 05:59:38 +11:00
return xbmcplugin.endOfDirectory(HANDLE)
# 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(basename(path), '')
if exists_dir(path):
for root, dirs, files in walk(path):
for directory in dirs:
2018-02-11 22:59:04 +11:00
item_path = try_encode(join(root, directory))
2018-06-15 22:05:48 +10:00
listitem = ListItem(item_path, path=item_path)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=item_path,
2018-06-15 22:05:48 +10:00
listitem=listitem,
isFolder=True)
for file in files:
2018-02-11 22:59:04 +11:00
item_path = try_encode(join(root, file))
2018-06-15 22:05:48 +10:00
listitem = ListItem(item_path, path=item_path)
xbmcplugin.addDirectoryItem(handle=HANDLE,
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)
2017-01-25 05:59:38 +11:00
xbmcplugin.endOfDirectory(HANDLE)
2018-02-11 23:24:00 +11:00
@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')
2017-01-25 05:59:38 +11:00
return xbmcplugin.endOfDirectory(HANDLE)
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
2018-06-15 22:07:08 +10:00
fanart_dir = try_decode(translatePath(
2018-06-15 22:05:48 +10:00
"special://thumbnails/plex/%s/" % plex_id))
2018-06-15 22:07:08 +10:00
if not exists_dir(fanart_dir):
2016-07-23 00:55:57 +10:00
# Download the images to the cache directory
2018-06-15 22:07:08 +10:00
makedirs(fanart_dir)
2018-06-15 22:05:48 +10:00
xml = 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)
2017-01-25 05:59:38 +11:00
return xbmcplugin.endOfDirectory(HANDLE)
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
2018-06-15 22:07:53 +10:00
art_file = try_encode(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(
2017-01-25 05:59:38 +11:00
handle=HANDLE,
2018-06-15 22:07:53 +10:00
url=art_file,
2018-06-15 22:08:43 +10:00
listitem=listitem)
2018-06-15 22:07:53 +10:00
copyfile(backdrop, 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
2018-06-15 22:09:40 +10:00
for root, _, files in walk(fanart_dir):
for file in files:
2018-06-15 22:07:53 +10:00
art_file = try_encode(join(root, file))
2018-06-15 22:08:43 +10:00
listitem = ListItem(file, path=art_file)
xbmcplugin.addDirectoryItem(handle=HANDLE,
2018-06-15 22:07:53 +10:00
url=art_file,
2018-06-15 22:08:43 +10:00
listitem=listitem)
2017-01-25 05:59:38 +11:00
xbmcplugin.endOfDirectory(HANDLE)
2018-06-15 22:11:17 +10:00
def on_deck_episodes(viewid, tagname, limit):
2016-03-15 23:09:51 +11:00
"""
Retrieves Plex On Deck items, currently only for TV shows
Input:
viewid: Plex id of the library section, e.g. '1'
tagname: Name of the Plex library, e.g. "My Movies"
2016-03-16 00:19:56 +11:00
limit: Max. number of items to retrieve, e.g. 50
2016-03-15 23:09:51 +11:00
"""
2017-01-25 05:59:38 +11:00
xbmcplugin.setContent(HANDLE, 'episodes')
2018-02-12 00:42:49 +11:00
append_show_title = settings('OnDeckTvAppendShow') == 'true'
append_sxxexx = settings('OnDeckTvAppendSeason') == 'true'
2016-08-30 23:51:11 +10:00
if settings('OnDeckTVextended') == 'false':
# Chances are that this view is used on Kodi startup
# Wait till we've connected to a PMS. At most 30s
counter = 0
2016-08-30 23:51:11 +10:00
while window('plex_authenticated') != 'true':
counter += 1
if counter == 300:
2018-06-15 21:40:25 +10:00
LOG.error('Aborting On Deck view, we were not authenticated '
2016-08-30 23:51:11 +10:00
'for the PMS')
xbmcplugin.endOfDirectory(HANDLE, False)
return
2017-01-25 05:59:38 +11:00
sleep(100)
xml = downloadutils.DownloadUtils().downloadUrl(
'{server}/library/sections/%s/onDeck' % viewid)
if xml in (None, 401):
2018-06-15 22:15:39 +10:00
LOG.error('Could not download PMS xml for view %s', viewid)
xbmcplugin.endOfDirectory(HANDLE, False)
return
direct_paths = settings('useDirectPaths') == '1'
counter = 0
for item in xml:
2017-01-25 05:59:38 +11:00
api = API(item)
2018-02-12 00:42:49 +11:00
listitem = api.create_listitem(
append_show_title=append_show_title,
append_sxxexx=append_sxxexx)
if api.resume_point():
listitem.setProperty('resumetime', str(api.resume_point()))
path = api.path(force_first_media=False, direct_paths=direct_paths)
xbmcplugin.addDirectoryItem(
2017-01-25 05:59:38 +11:00
handle=HANDLE,
url=path,
listitem=listitem)
counter += 1
if counter == limit:
break
xbmcplugin.endOfDirectory(
2017-01-25 05:59:38 +11:00
handle=HANDLE,
2016-08-30 23:51:11 +10:00
cacheToDisc=settings('enableTextureCache') == 'true')
return
2016-03-15 23:09:51 +11:00
# if the addon is called with nextup parameter,
# we return the nextepisodes list of the given tagname
# First we get a list of all the TV shows - filtered by tag
2017-01-25 05:59:38 +11:00
params = {
'sort': {'order': "descending", 'method': "lastplayed"},
'filter': {
'and': [
{'operator': "true", 'field': "inprogress", 'value': ""},
{'operator': "is", 'field': "tag", 'value': "%s" % tagname}
]}
2016-03-15 23:09:51 +11:00
}
items = js.get_tv_shows(params)
if not items:
# Now items retrieved - empty directory
xbmcplugin.endOfDirectory(handle=HANDLE)
return
2017-01-25 05:59:38 +11:00
params = {
'sort': {'method': "episode"},
'limits': {"end": 1},
'properties': [
"title", "playcount", "season", "episode", "showtitle",
"plot", "file", "rating", "resume", "tvshowid", "art",
"streamdetails", "firstaired", "runtime", "cast", "writer",
"dateadded", "lastplayed"
],
2016-03-15 23:09:51 +11:00
}
2016-08-30 23:51:11 +10:00
if settings('ignoreSpecialsNextEpisodes') == "true":
2017-01-25 05:59:38 +11:00
params['filter'] = {
2016-03-15 23:09:51 +11:00
'and': [
{'operator': "lessthan", 'field': "playcount", 'value': "1"},
{'operator': "greaterthan", 'field': "season", 'value': "0"}
]
}
else:
2017-01-25 05:59:38 +11:00
params['filter'] = {
2016-03-15 23:09:51 +11:00
'or': [
{'operator': "lessthan", 'field': "playcount", 'value': "1"},
{'operator': "true", 'field': "inprogress", 'value': ""}
]
}
2016-03-15 23:09:51 +11:00
# Are there any episodes still in progress/not yet finished watching?!?
# Then we should show this episode, NOT the "next up"
2017-01-25 05:59:38 +11:00
inprog_params = {
'sort': {'method': "episode"},
'filter': {'operator': "true", 'field': "inprogress", 'value': ""},
'properties': params['properties']
2016-03-15 23:09:51 +11:00
}
count = 0
for item in items:
2017-01-25 05:59:38 +11:00
inprog_params['tvshowid'] = item['tvshowid']
2017-12-09 05:43:06 +11:00
episodes = js.get_episodes(inprog_params)
if not episodes:
2016-03-15 23:09:51 +11:00
# No, there are no episodes not yet finished. Get "next up"
2017-01-25 05:59:38 +11:00
params['tvshowid'] = item['tvshowid']
2017-12-09 05:43:06 +11:00
episodes = js.get_episodes(params)
if not episodes:
# Also no episodes currently coming up
continue
2016-03-15 23:09:51 +11:00
for episode in episodes:
# There will always be only 1 episode ('limit=1')
2018-06-15 21:55:32 +10:00
listitem = create_listitem(episode,
append_show_title=append_show_title,
append_sxxexx=append_sxxexx)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=episode['file'],
listitem=listitem,
isFolder=False)
2016-03-15 23:09:51 +11:00
count += 1
if count >= limit:
break
2017-01-25 05:59:38 +11:00
xbmcplugin.endOfDirectory(handle=HANDLE)
2016-04-17 21:36:41 +10:00
def watchlater():
"""
Listing for plex.tv Watch Later section (if signed in to plex.tv)
"""
2016-08-30 23:51:11 +10:00
if window('plex_token') == '':
2018-06-15 21:40:25 +10:00
LOG.error('No watch later - not signed in to plex.tv')
2017-01-25 05:59:38 +11:00
return xbmcplugin.endOfDirectory(HANDLE, False)
if window('plex_restricteduser') == 'true':
2018-06-15 21:40:25 +10:00
LOG.error('No watch later - restricted user')
2017-01-25 05:59:38 +11:00
return xbmcplugin.endOfDirectory(HANDLE, False)
2016-04-17 21:36:41 +10:00
xml = downloadutils.DownloadUtils().downloadUrl(
'https://plex.tv/pms/playlists/queue/all',
authenticate=False,
2016-08-30 23:51:11 +10:00
headerOptions={'X-Plex-Token': 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')
2017-01-25 05:59:38 +11:00
return xbmcplugin.endOfDirectory(HANDLE, False)
2016-04-17 21:36:41 +10:00
2018-06-15 21:40:25 +10:00
LOG.info('Displaying watch later plex.tv items')
2017-01-25 05:59:38 +11:00
xbmcplugin.setContent(HANDLE, 'movies')
direct_paths = settings('useDirectPaths') == '1'
2016-04-17 21:36:41 +10:00
for item in xml:
__build_item(item, direct_paths)
2016-04-17 21:36:41 +10:00
xbmcplugin.endOfDirectory(
2017-01-25 05:59:38 +11:00
handle=HANDLE,
2016-08-30 23:51:11 +10:00
cacheToDisc=settings('enableTextureCache') == 'true')
2016-05-30 00:52:00 +10:00
2017-03-08 02:37:36 +11:00
def channels():
"""
Listing for Plex Channels
"""
xml = downloadutils.DownloadUtils().downloadUrl('{server}/channels/all')
try:
xml[0].attrib
2017-05-01 01:07:56 +10:00
except (ValueError, AttributeError, IndexError, TypeError):
2018-06-15 21:40:25 +10:00
LOG.error('Could not download Plex Channels')
2017-03-08 02:37:36 +11:00
return xbmcplugin.endOfDirectory(HANDLE, False)
2018-06-15 21:40:25 +10:00
LOG.info('Displaying Plex Channels')
2017-03-08 02:37:36 +11:00
xbmcplugin.setContent(HANDLE, 'files')
2017-03-09 02:21:00 +11:00
for method in v.SORT_METHODS_DIRECTORY:
xbmcplugin.addSortMethod(HANDLE, getattr(xbmcplugin, method))
2017-03-08 02:37:36 +11:00
for item in xml:
__build_folder(item)
xbmcplugin.endOfDirectory(
handle=HANDLE,
cacheToDisc=settings('enableTextureCache') == 'true')
2017-03-09 02:21:00 +11:00
def browse_plex(key=None, plex_section_id=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
be used directly for PMS url {server}<key>) or the plex_section_id
2017-03-08 02:37:36 +11:00
"""
2017-03-09 02:21:00 +11:00
if key:
xml = downloadutils.DownloadUtils().downloadUrl('{server}%s' % key)
else:
2017-04-02 02:28:02 +10:00
xml = GetPlexSectionResults(plex_section_id)
2017-03-08 02:37:36 +11:00
try:
xml[0].attrib
except (ValueError, AttributeError, IndexError, TypeError):
2018-06-15 22:15:39 +10:00
LOG.error('Could not browse to %s', key)
2017-03-08 02:37:36 +11:00
return xbmcplugin.endOfDirectory(HANDLE, False)
2017-03-09 02:21:00 +11:00
photos = False
movies = False
clips = False
tvshows = False
episodes = False
songs = False
artists = False
albums = False
musicvideos = False
direct_paths = settings('useDirectPaths') == '1'
2017-03-08 02:37:36 +11:00
for item in xml:
if item.tag == 'Directory':
2017-03-09 03:53:43 +11:00
__build_folder(item, plex_section_id=plex_section_id)
2017-03-08 02:37:36 +11:00
else:
2017-03-19 22:14:16 +11:00
typus = item.attrib.get('type')
__build_item(item, direct_paths)
2017-03-09 02:21:00 +11:00
if typus == v.PLEX_TYPE_PHOTO:
photos = True
elif typus == v.PLEX_TYPE_MOVIE:
movies = True
elif typus == v.PLEX_TYPE_CLIP:
clips = True
elif typus in (v.PLEX_TYPE_SHOW, v.PLEX_TYPE_SEASON):
tvshows = True
elif typus == v.PLEX_TYPE_EPISODE:
episodes = True
elif typus == v.PLEX_TYPE_SONG:
songs = True
elif typus == v.PLEX_TYPE_ARTIST:
artists = True
elif typus == v.PLEX_TYPE_ALBUM:
albums = True
elif typus == v.PLEX_TYPE_MUSICVIDEO:
musicvideos = True
# Set the correct content type
2017-03-09 03:02:26 +11:00
if movies is True:
xbmcplugin.setContent(HANDLE, 'movies')
sort_methods = v.SORT_METHODS_MOVIES
2017-03-09 02:21:00 +11:00
elif clips is True:
xbmcplugin.setContent(HANDLE, 'movies')
sort_methods = v.SORT_METHODS_CLIPS
2017-03-09 03:02:26 +11:00
elif photos is True:
2017-03-16 05:26:55 +11:00
xbmcplugin.setContent(HANDLE, 'images')
2017-03-09 03:02:26 +11:00
sort_methods = v.SORT_METHODS_PHOTOS
2017-03-09 02:21:00 +11:00
elif tvshows is True:
xbmcplugin.setContent(HANDLE, 'tvshows')
sort_methods = v.SORT_METHOD_TVSHOWS
elif episodes is True:
xbmcplugin.setContent(HANDLE, 'episodes')
sort_methods = v.SORT_METHODS_EPISODES
elif songs is True:
xbmcplugin.setContent(HANDLE, 'songs')
sort_methods = v.SORT_METHODS_SONGS
elif artists is True:
xbmcplugin.setContent(HANDLE, 'artists')
sort_methods = v.SORT_METHODS_ARTISTS
elif albums is True:
xbmcplugin.setContent(HANDLE, 'albums')
sort_methods = v.SORT_METHODS_ALBUMS
elif musicvideos is True:
xbmcplugin.setContent(HANDLE, 'musicvideos')
sort_methods = v.SORT_METHODS_MOVIES
else:
xbmcplugin.setContent(HANDLE, 'files')
sort_methods = v.SORT_METHODS_DIRECTORY
for method in sort_methods:
xbmcplugin.addSortMethod(HANDLE, getattr(xbmcplugin, method))
# Set the Kodi title for this view
title = xml.attrib.get('librarySectionTitle', xml.attrib.get('title1'))
xbmcplugin.setPluginCategory(HANDLE, title)
2017-03-08 02:37:36 +11:00
xbmcplugin.endOfDirectory(
handle=HANDLE,
cacheToDisc=settings('enableTextureCache') == 'true')
2017-03-09 03:53:43 +11:00
def __build_folder(xml_element, plex_section_id=None):
2017-03-08 20:51:21 +11:00
url = "plugin://%s/" % v.ADDON_ID
2017-03-09 03:53:43 +11:00
key = xml_element.attrib.get('fastKey', xml_element.attrib.get('key'))
if not key.startswith('/'):
key = '/library/sections/%s/%s' % (plex_section_id, key)
2017-03-08 02:37:36 +11:00
params = {
2017-03-09 02:21:00 +11:00
'mode': "browseplex",
2017-03-09 03:53:43 +11:00
'key': key,
'id': plex_section_id
2017-03-08 02:37:36 +11:00
}
listitem = ListItem(xml_element.attrib.get('title'))
listitem.setArt({'thumb': xml_element.attrib.get('thumb'),
'poster': xml_element.attrib.get('art')})
2017-03-08 20:51:21 +11:00
xbmcplugin.addDirectoryItem(handle=HANDLE,
url="%s?%s" % (url, urlencode(params)),
isFolder=True,
listitem=listitem)
2017-03-08 02:37:36 +11:00
def __build_item(xml_element, direct_paths):
2017-03-08 02:37:36 +11:00
api = API(xml_element)
2018-02-12 00:42:49 +11:00
listitem = api.create_listitem()
resume = api.resume_point()
2018-01-29 03:28:02 +11:00
if resume:
listitem.setProperty('resumetime', str(resume))
2018-02-12 00:42:49 +11:00
if (api.path_and_plex_id().startswith('/system/services') or
api.path_and_plex_id().startswith('http')):
2017-03-09 04:24:50 +11:00
params = {
'mode': 'plex_node',
'key': xml_element.attrib.get('key'),
2018-01-29 03:21:28 +11:00
'offset': xml_element.attrib.get('viewOffset', '0'),
2017-03-09 04:24:50 +11:00
}
2017-03-19 22:14:16 +11:00
url = "plugin://%s?%s" % (v.ADDON_ID, urlencode(params))
2018-02-12 00:42:49 +11:00
elif api.plex_type() == v.PLEX_TYPE_PHOTO:
2017-03-19 22:14:16 +11:00
url = api.get_picture_path()
2017-03-09 04:24:50 +11:00
else:
url = api.path(direct_paths=direct_paths)
if api.resume_point():
listitem.setProperty('resumetime', str(api.resume_point()))
2017-03-08 20:51:21 +11:00
xbmcplugin.addDirectoryItem(handle=HANDLE,
2017-03-09 04:24:50 +11:00
url=url,
2017-03-08 20:51:21 +11:00
listitem=listitem)
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
"""
xbmcplugin.setContent(HANDLE, 'movies')
xml = GetPlexMetadata(plex_id)
try:
xml[0].attrib
except (TypeError, IndexError, KeyError):
xbmcplugin.endOfDirectory(HANDLE)
return
for item in API(xml[0]).extras():
api = API(item)
listitem = api.create_listitem()
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=api.path(),
listitem=listitem)
xbmcplugin.endOfDirectory(HANDLE)
2018-06-15 22:13:46 +10:00
def create_new_pms():
2016-05-30 00:52:00 +10:00
"""
Opens dialogs for the user the plug in the PMS details
"""
# "Enter your Plex Media Server's IP or URL. Examples are:"
2017-01-25 05:59:38 +11:00
dialog('ok', lang(29999), lang(39215), '192.168.1.2', 'plex.myServer.org')
2018-06-15 22:13:46 +10:00
address = dialog('input', "Enter PMS IP or URL")
if address == '':
2016-05-30 00:52:00 +10:00
return
2017-01-25 05:59:38 +11:00
port = dialog('input', "Enter PMS port", '32400', type='{numeric}')
2016-05-30 00:52:00 +10:00
if port == '':
return
2018-06-15 22:13:46 +10:00
url = '%s:%s' % (address, port)
2016-05-30 00:52:00 +10:00
# "Does your Plex Media Server support SSL connections?
# (https instead of http)"
2017-01-25 05:59:38 +11:00
https = dialog('yesno', lang(29999), lang(39217))
2016-05-30 00:52:00 +10:00
if https:
url = 'https://%s' % url
else:
url = 'http://%s' % url
https = 'true' if https else 'false'
2018-06-15 22:13:46 +10:00
machine_identifier = GetMachineIdentifier(url)
if machine_identifier is None:
2016-05-30 00:52:00 +10:00
# "Error contacting url
# Abort (Yes) or save address anyway (No)"
2017-01-25 05:59:38 +11:00
if dialog('yesno',
lang(29999),
'%s %s. %s' % (lang(39218), url, lang(39219))):
2016-05-30 00:52:00 +10:00
return
else:
2016-08-30 23:51:11 +10:00
settings('plex_machineIdentifier', '')
2016-05-30 00:52:00 +10:00
else:
2018-06-15 22:13:46 +10:00
settings('plex_machineIdentifier', machine_identifier)
LOG.info('Set new PMS to https %s, address %s, port %s, machineId %s',
https, address, port, machine_identifier)
2016-08-30 23:51:11 +10:00
settings('https', value=https)
2018-06-15 22:13:46 +10:00
settings('ipaddress', value=address)
2016-08-30 23:51:11 +10:00
settings('port', value=port)
# Chances are this is a local PMS, so disable SSL certificate check
2016-08-30 23:51:11 +10:00
settings('sslverify', value='false')
2016-05-30 00:52:00 +10:00
# Sign out to trigger new login
2018-06-15 22:15:39 +10:00
if _log_out():
2016-05-30 00:52:00 +10:00
# Only login again if logout was successful
2018-06-15 22:15:39 +10:00
_log_in()
2016-05-30 00:52:00 +10:00
2018-06-15 22:15:39 +10:00
def _log_in():
2016-05-30 00:52:00 +10:00
"""
Resets (clears) window properties to enable (re-)login
2016-05-30 00:52:00 +10:00
SUSPEND_LIBRARY_THREAD is set to False in service.py if user was signed
out!
2016-05-30 00:52:00 +10:00
"""
2017-08-22 02:53:38 +10:00
plex_command('RUN_LIB_SCAN', 'full')
2016-05-30 00:52:00 +10:00
# Restart user client
plex_command('SUSPEND_USER_CLIENT', 'False')
2016-05-30 00:52:00 +10:00
2018-06-15 22:15:39 +10:00
def _log_out():
2016-05-30 00:52:00 +10:00
"""
Finishes lib scans, logs out user.
2016-05-30 00:52:00 +10:00
Returns True if successfully signed out, False otherwise
"""
# Resetting, please wait
2017-01-25 05:59:38 +11:00
dialog('notification',
lang(29999),
lang(39207),
icon='{plex}',
time=3000,
sound=False)
2016-05-30 00:52:00 +10:00
# Pause library sync thread
plex_command('SUSPEND_LIBRARY_THREAD', 'True')
2016-05-30 00:52:00 +10:00
# Wait max for 10 seconds for all lib scans to shutdown
counter = 0
2016-08-30 23:51:11 +10:00
while window('plex_dbScan') == 'true':
2016-05-30 00:52:00 +10:00
if counter > 200:
# Failed to reset PMS and plex.tv connects. Try to restart Kodi.
2017-01-25 05:59:38 +11:00
dialog('ok', lang(29999), lang(39208))
2016-05-30 00:52:00 +10:00
# Resuming threads, just in case
plex_command('SUSPEND_LIBRARY_THREAD', 'False')
2018-06-15 21:40:25 +10:00
LOG.error("Could not stop library sync, aborting")
2016-05-30 00:52:00 +10:00
return False
counter += 1
2017-01-25 05:59:38 +11:00
sleep(50)
2018-06-15 21:40:25 +10:00
LOG.debug("Successfully stopped library sync")
2016-05-30 00:52:00 +10:00
counter = 0
# Log out currently signed in user:
window('plex_serverStatus', value='401')
plex_command('PMS_STATUS', '401')
# Above method needs to have run its course! Hence wait
2016-08-30 23:51:11 +10:00
while window('plex_serverStatus') == "401":
if counter > 100:
# 'Failed to reset PKC. Try to restart Kodi.'
2017-01-25 05:59:38 +11:00
dialog('ok', lang(29999), lang(39208))
2018-06-15 21:40:25 +10:00
LOG.error("Could not sign out user, aborting")
return False
counter += 1
2017-01-25 05:59:38 +11:00
sleep(50)
# Suspend the user client during procedure
plex_command('SUSPEND_USER_CLIENT', 'True')
2016-05-30 00:52:00 +10:00
return True