Merge branch 'beta-version'
This commit is contained in:
commit
352cbece62
12 changed files with 306 additions and 120 deletions
|
@ -1,5 +1,5 @@
|
||||||
[![stable version](https://img.shields.io/badge/stable_version-2.1.6-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
|
[![stable version](https://img.shields.io/badge/stable_version-2.1.6-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
|
||||||
[![beta version](https://img.shields.io/badge/beta_version-2.2.15-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
|
[![beta version](https://img.shields.io/badge/beta_version-2.2.16-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
|
||||||
|
|
||||||
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
|
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
|
||||||
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)
|
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)
|
||||||
|
|
10
addon.xml
10
addon.xml
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.2.15" provider-name="croneter">
|
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.2.16" provider-name="croneter">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.1.0"/>
|
<import addon="xbmc.python" version="2.1.0"/>
|
||||||
<import addon="script.module.requests" version="2.9.1" />
|
<import addon="script.module.requests" version="2.9.1" />
|
||||||
|
@ -73,7 +73,13 @@
|
||||||
<summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary>
|
<summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary>
|
||||||
<description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description>
|
<description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description>
|
||||||
<disclaimer lang="uk_UA">Використовуйте на свій ризик</disclaimer>
|
<disclaimer lang="uk_UA">Використовуйте на свій ризик</disclaimer>
|
||||||
<news>version 2.2.15 (beta only):
|
<news>version 2.2.16 (beta only):
|
||||||
|
- Enable Kodi libraries for Plex Music libraries
|
||||||
|
- New Playlists menu item for video libraries
|
||||||
|
- Only show Plex libraries in the applicable Kodi media category
|
||||||
|
- Optimize code
|
||||||
|
|
||||||
|
version 2.2.15 (beta only):
|
||||||
- Fix ImportError on first PKC run
|
- Fix ImportError on first PKC run
|
||||||
|
|
||||||
version 2.2.14 (beta only):
|
version 2.2.14 (beta only):
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
version 2.2.16 (beta only):
|
||||||
|
- Enable Kodi libraries for Plex Music libraries
|
||||||
|
- New Playlists menu item for video libraries
|
||||||
|
- Only show Plex libraries in the applicable Kodi media category
|
||||||
|
- Optimize code
|
||||||
|
|
||||||
version 2.2.15 (beta only):
|
version 2.2.15 (beta only):
|
||||||
- Fix ImportError on first PKC run
|
- Fix ImportError on first PKC run
|
||||||
|
|
||||||
|
|
|
@ -136,6 +136,9 @@ class Main():
|
||||||
plexId = itemid or None
|
plexId = itemid or None
|
||||||
entrypoint.get_video_files(plexId, params)
|
entrypoint.get_video_files(plexId, params)
|
||||||
|
|
||||||
|
elif mode == 'playlists':
|
||||||
|
entrypoint.playlists(params.get('type'))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
entrypoint.show_main_menu(content_type=params.get('content_type'))
|
entrypoint.show_main_menu(content_type=params.get('content_type'))
|
||||||
|
|
||||||
|
|
|
@ -145,8 +145,12 @@ def show_main_menu(content_type=None):
|
||||||
# nodes in the video library and all nodes in any other window
|
# nodes in the video library and all nodes in any other window
|
||||||
if node_type == 'photos' and content_type == 'image':
|
if node_type == 'photos' and content_type == 'image':
|
||||||
directory_item(label, path)
|
directory_item(label, path)
|
||||||
elif (node_type != 'photos' and
|
elif node_type == 'albums' and content_type == 'audio':
|
||||||
content_type not in ('image', 'audio')):
|
directory_item(label, path)
|
||||||
|
elif node_type in ('movies',
|
||||||
|
'tvshows',
|
||||||
|
'homevideos',
|
||||||
|
'musicvideos') and content_type == 'video':
|
||||||
directory_item(label, path)
|
directory_item(label, path)
|
||||||
|
|
||||||
# Plex Watch later
|
# Plex Watch later
|
||||||
|
@ -524,6 +528,24 @@ def extra_fanart(plex_id, plex_path):
|
||||||
xbmcplugin.endOfDirectory(int(argv[1]))
|
xbmcplugin.endOfDirectory(int(argv[1]))
|
||||||
|
|
||||||
|
|
||||||
|
def _wait_for_auth():
|
||||||
|
"""
|
||||||
|
Call to be sure that PKC is authenticated, e.g. for widgets on Kodi
|
||||||
|
startup. Will wait for at most 30s, then fail if not authenticated.
|
||||||
|
|
||||||
|
Will set xbmcplugin.endOfDirectory(int(argv[1]), False) if failed
|
||||||
|
"""
|
||||||
|
counter = 0
|
||||||
|
while utils.window('plex_authenticated') != 'true':
|
||||||
|
counter += 1
|
||||||
|
if counter == 300:
|
||||||
|
LOG.error('Aborting view, we were not authenticated for PMS')
|
||||||
|
xbmcplugin.endOfDirectory(int(argv[1]), False)
|
||||||
|
return False
|
||||||
|
sleep(100)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def on_deck_episodes(viewid, tagname, limit):
|
def on_deck_episodes(viewid, tagname, limit):
|
||||||
"""
|
"""
|
||||||
Retrieves Plex On Deck items, currently only for TV shows
|
Retrieves Plex On Deck items, currently only for TV shows
|
||||||
|
@ -539,15 +561,8 @@ def on_deck_episodes(viewid, tagname, limit):
|
||||||
if utils.settings('OnDeckTVextended') == 'false':
|
if utils.settings('OnDeckTVextended') == 'false':
|
||||||
# Chances are that this view is used on Kodi startup
|
# Chances are that this view is used on Kodi startup
|
||||||
# Wait till we've connected to a PMS. At most 30s
|
# Wait till we've connected to a PMS. At most 30s
|
||||||
counter = 0
|
if not _wait_for_auth():
|
||||||
while utils.window('plex_authenticated') != 'true':
|
return
|
||||||
counter += 1
|
|
||||||
if counter == 300:
|
|
||||||
LOG.error('Aborting On Deck view, we were not authenticated '
|
|
||||||
'for the PMS')
|
|
||||||
xbmcplugin.endOfDirectory(int(argv[1]), False)
|
|
||||||
return
|
|
||||||
sleep(100)
|
|
||||||
xml = DU().downloadUrl('{server}/library/sections/%s/onDeck' % viewid)
|
xml = DU().downloadUrl('{server}/library/sections/%s/onDeck' % viewid)
|
||||||
if xml in (None, 401):
|
if xml in (None, 401):
|
||||||
LOG.error('Could not download PMS xml for view %s', viewid)
|
LOG.error('Could not download PMS xml for view %s', viewid)
|
||||||
|
@ -659,6 +674,39 @@ def on_deck_episodes(viewid, tagname, limit):
|
||||||
xbmcplugin.endOfDirectory(handle=int(argv[1]))
|
xbmcplugin.endOfDirectory(handle=int(argv[1]))
|
||||||
|
|
||||||
|
|
||||||
|
def playlists(plex_playlist_type):
|
||||||
|
"""
|
||||||
|
Lists all Plex playlists of the media type plex_playlist_type
|
||||||
|
"""
|
||||||
|
LOG.debug('Listing Plex %s playlists', plex_playlist_type)
|
||||||
|
if not _wait_for_auth():
|
||||||
|
return
|
||||||
|
xbmcplugin.setContent(int(argv[1]), 'files')
|
||||||
|
from .playlists.pms import all_playlists
|
||||||
|
xml = all_playlists()
|
||||||
|
if xml is None:
|
||||||
|
return
|
||||||
|
for item in xml:
|
||||||
|
api = API(item)
|
||||||
|
if not api.playlist_type() == plex_playlist_type:
|
||||||
|
continue
|
||||||
|
listitem = ListItem(api.title())
|
||||||
|
listitem.setArt({'thumb': api.one_artwork('composite')})
|
||||||
|
url = "plugin://%s/" % v.ADDON_ID
|
||||||
|
key = api.path_and_plex_id()
|
||||||
|
params = {
|
||||||
|
'mode': "browseplex",
|
||||||
|
'key': key,
|
||||||
|
}
|
||||||
|
xbmcplugin.addDirectoryItem(handle=int(argv[1]),
|
||||||
|
url="%s?%s" % (url, urlencode(params)),
|
||||||
|
isFolder=True,
|
||||||
|
listitem=listitem)
|
||||||
|
xbmcplugin.endOfDirectory(
|
||||||
|
handle=int(argv[1]),
|
||||||
|
cacheToDisc=utils.settings('enableTextureCache') == 'true')
|
||||||
|
|
||||||
|
|
||||||
def watchlater():
|
def watchlater():
|
||||||
"""
|
"""
|
||||||
Listing for plex.tv Watch Later section (if signed in to plex.tv)
|
Listing for plex.tv Watch Later section (if signed in to plex.tv)
|
||||||
|
@ -714,6 +762,7 @@ def browse_plex(key=None, plex_section_id=None):
|
||||||
Lists the content of a Plex folder, e.g. channels. Either pass in key (to
|
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
|
be used directly for PMS url {server}<key>) or the plex_section_id
|
||||||
"""
|
"""
|
||||||
|
LOG.debug('Browsing to key %s, section %s', key, plex_section_id)
|
||||||
if key:
|
if key:
|
||||||
xml = DU().downloadUrl('{server}%s' % key)
|
xml = DU().downloadUrl('{server}%s' % key)
|
||||||
else:
|
else:
|
||||||
|
@ -721,7 +770,8 @@ def browse_plex(key=None, plex_section_id=None):
|
||||||
try:
|
try:
|
||||||
xml[0].attrib
|
xml[0].attrib
|
||||||
except (ValueError, AttributeError, IndexError, TypeError):
|
except (ValueError, AttributeError, IndexError, TypeError):
|
||||||
LOG.error('Could not browse to %s', key)
|
LOG.error('Could not browse to key %s, section %s',
|
||||||
|
key, plex_section_id)
|
||||||
return xbmcplugin.endOfDirectory(int(argv[1]), False)
|
return xbmcplugin.endOfDirectory(int(argv[1]), False)
|
||||||
|
|
||||||
photos = False
|
photos = False
|
||||||
|
@ -761,37 +811,48 @@ def browse_plex(key=None, plex_section_id=None):
|
||||||
|
|
||||||
# Set the correct content type
|
# Set the correct content type
|
||||||
if movies is True:
|
if movies is True:
|
||||||
|
LOG.debug('Setting view to movies')
|
||||||
xbmcplugin.setContent(int(argv[1]), 'movies')
|
xbmcplugin.setContent(int(argv[1]), 'movies')
|
||||||
sort_methods = v.SORT_METHODS_MOVIES
|
sort_methods = v.SORT_METHODS_MOVIES
|
||||||
elif clips is True:
|
elif clips is True:
|
||||||
|
LOG.debug('Clips -> Setting view to movies')
|
||||||
xbmcplugin.setContent(int(argv[1]), 'movies')
|
xbmcplugin.setContent(int(argv[1]), 'movies')
|
||||||
sort_methods = v.SORT_METHODS_CLIPS
|
sort_methods = v.SORT_METHODS_CLIPS
|
||||||
elif photos is True:
|
elif photos is True:
|
||||||
|
LOG.debug('Setting view to images')
|
||||||
xbmcplugin.setContent(int(argv[1]), 'images')
|
xbmcplugin.setContent(int(argv[1]), 'images')
|
||||||
sort_methods = v.SORT_METHODS_PHOTOS
|
sort_methods = v.SORT_METHODS_PHOTOS
|
||||||
elif tvshows is True:
|
elif tvshows is True:
|
||||||
|
LOG.debug('Setting view to tvshows')
|
||||||
xbmcplugin.setContent(int(argv[1]), 'tvshows')
|
xbmcplugin.setContent(int(argv[1]), 'tvshows')
|
||||||
sort_methods = v.SORT_METHOD_TVSHOWS
|
sort_methods = v.SORT_METHOD_TVSHOWS
|
||||||
elif episodes is True:
|
elif episodes is True:
|
||||||
|
LOG.debug('Setting view to episodes')
|
||||||
xbmcplugin.setContent(int(argv[1]), 'episodes')
|
xbmcplugin.setContent(int(argv[1]), 'episodes')
|
||||||
sort_methods = v.SORT_METHODS_EPISODES
|
sort_methods = v.SORT_METHODS_EPISODES
|
||||||
elif songs is True:
|
elif songs is True:
|
||||||
|
LOG.debug('Setting view to songs')
|
||||||
xbmcplugin.setContent(int(argv[1]), 'songs')
|
xbmcplugin.setContent(int(argv[1]), 'songs')
|
||||||
sort_methods = v.SORT_METHODS_SONGS
|
sort_methods = v.SORT_METHODS_SONGS
|
||||||
elif artists is True:
|
elif artists is True:
|
||||||
|
LOG.debug('Setting view to artists')
|
||||||
xbmcplugin.setContent(int(argv[1]), 'artists')
|
xbmcplugin.setContent(int(argv[1]), 'artists')
|
||||||
sort_methods = v.SORT_METHODS_ARTISTS
|
sort_methods = v.SORT_METHODS_ARTISTS
|
||||||
elif albums is True:
|
elif albums is True:
|
||||||
|
LOG.debug('Setting view to albums')
|
||||||
xbmcplugin.setContent(int(argv[1]), 'albums')
|
xbmcplugin.setContent(int(argv[1]), 'albums')
|
||||||
sort_methods = v.SORT_METHODS_ALBUMS
|
sort_methods = v.SORT_METHODS_ALBUMS
|
||||||
elif musicvideos is True:
|
elif musicvideos is True:
|
||||||
|
LOG.debug('Setting view to musicvideos')
|
||||||
xbmcplugin.setContent(int(argv[1]), 'musicvideos')
|
xbmcplugin.setContent(int(argv[1]), 'musicvideos')
|
||||||
sort_methods = v.SORT_METHODS_MOVIES
|
sort_methods = v.SORT_METHODS_MOVIES
|
||||||
else:
|
else:
|
||||||
|
LOG.debug('Setting view to files')
|
||||||
xbmcplugin.setContent(int(argv[1]), 'files')
|
xbmcplugin.setContent(int(argv[1]), 'files')
|
||||||
sort_methods = v.SORT_METHODS_DIRECTORY
|
sort_methods = v.SORT_METHODS_DIRECTORY
|
||||||
|
|
||||||
for method in sort_methods:
|
for method in sort_methods:
|
||||||
|
LOG.debug('Adding Kodi sort method %s', method)
|
||||||
xbmcplugin.addSortMethod(int(argv[1]), getattr(xbmcplugin, method))
|
xbmcplugin.addSortMethod(int(argv[1]), getattr(xbmcplugin, method))
|
||||||
|
|
||||||
# Set the Kodi title for this view
|
# Set the Kodi title for this view
|
||||||
|
@ -840,8 +901,6 @@ def __build_item(xml_element, direct_paths):
|
||||||
url = api.get_picture_path()
|
url = api.get_picture_path()
|
||||||
else:
|
else:
|
||||||
url = api.path(direct_paths=direct_paths)
|
url = api.path(direct_paths=direct_paths)
|
||||||
if api.resume_point():
|
|
||||||
listitem.setProperty('resumetime', str(api.resume_point()))
|
|
||||||
xbmcplugin.addDirectoryItem(handle=int(argv[1]),
|
xbmcplugin.addDirectoryItem(handle=int(argv[1]),
|
||||||
url=url,
|
url=url,
|
||||||
listitem=listitem)
|
listitem=listitem)
|
||||||
|
|
|
@ -472,8 +472,8 @@ class SpecialMonitor(Thread):
|
||||||
def run(self):
|
def run(self):
|
||||||
LOG.info("----====# Starting Special Monitor #====----")
|
LOG.info("----====# Starting Special Monitor #====----")
|
||||||
# "Start from beginning", "Play from beginning"
|
# "Start from beginning", "Play from beginning"
|
||||||
strings = (utils.try_encode(xbmc.getLocalizedString(12021)),
|
strings = (utils.try_encode(utils.lang(12021)),
|
||||||
utils.try_encode(xbmc.getLocalizedString(12023)))
|
utils.try_encode(utils.lang(12023)))
|
||||||
while not self.stopped():
|
while not self.stopped():
|
||||||
if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'):
|
if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'):
|
||||||
if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings:
|
if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings:
|
||||||
|
|
|
@ -329,8 +329,7 @@ class LibrarySync(Thread):
|
||||||
utils.playlist_xsp(mediatype, foldername, folderid, viewtype)
|
utils.playlist_xsp(mediatype, foldername, folderid, viewtype)
|
||||||
lists.append(foldername)
|
lists.append(foldername)
|
||||||
# Create the video node
|
# Create the video node
|
||||||
if (foldername not in nodes and
|
if foldername not in nodes:
|
||||||
mediatype != v.PLEX_TYPE_ARTIST):
|
|
||||||
vnodes.viewNode(sorted_views.index(foldername),
|
vnodes.viewNode(sorted_views.index(foldername),
|
||||||
foldername,
|
foldername,
|
||||||
mediatype,
|
mediatype,
|
||||||
|
@ -362,42 +361,41 @@ class LibrarySync(Thread):
|
||||||
# Update view with new info
|
# Update view with new info
|
||||||
plex_db.updateView(foldername, tagid, folderid)
|
plex_db.updateView(foldername, tagid, folderid)
|
||||||
|
|
||||||
if mediatype != "artist":
|
if plex_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
|
||||||
# The tag could be a combined view. Ensure there's
|
# no other tags with the same name before deleting
|
||||||
# no other tags with the same name before deleting
|
# playlist.
|
||||||
# playlist.
|
utils.playlist_xsp(mediatype,
|
||||||
utils.playlist_xsp(mediatype,
|
current_viewname,
|
||||||
current_viewname,
|
folderid,
|
||||||
folderid,
|
current_viewtype,
|
||||||
current_viewtype,
|
True)
|
||||||
True)
|
# Delete video node
|
||||||
# Delete video node
|
if mediatype != "musicvideos":
|
||||||
if mediatype != "musicvideos":
|
vnodes.viewNode(
|
||||||
vnodes.viewNode(
|
indexnumber=sorted_views.index(foldername),
|
||||||
indexnumber=sorted_views.index(foldername),
|
tagname=current_viewname,
|
||||||
tagname=current_viewname,
|
mediatype=mediatype,
|
||||||
mediatype=mediatype,
|
viewtype=current_viewtype,
|
||||||
viewtype=current_viewtype,
|
viewid=folderid,
|
||||||
viewid=folderid,
|
delete=True)
|
||||||
delete=True)
|
# Added new playlist
|
||||||
# Added new playlist
|
if (foldername not in lists and mediatype in
|
||||||
if (foldername not in lists and mediatype in
|
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
|
||||||
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
|
utils.playlist_xsp(mediatype,
|
||||||
utils.playlist_xsp(mediatype,
|
foldername,
|
||||||
foldername,
|
folderid,
|
||||||
folderid,
|
viewtype)
|
||||||
viewtype)
|
lists.append(foldername)
|
||||||
lists.append(foldername)
|
# Add new video node
|
||||||
# Add new video node
|
if foldername not in nodes and mediatype != "musicvideos":
|
||||||
if foldername not in nodes and mediatype != "musicvideos":
|
vnodes.viewNode(sorted_views.index(foldername),
|
||||||
vnodes.viewNode(sorted_views.index(foldername),
|
foldername,
|
||||||
foldername,
|
mediatype,
|
||||||
mediatype,
|
viewtype,
|
||||||
viewtype,
|
folderid)
|
||||||
folderid)
|
nodes.append(foldername)
|
||||||
nodes.append(foldername)
|
totalnodes += 1
|
||||||
totalnodes += 1
|
|
||||||
|
|
||||||
# Update items with new tag
|
# Update items with new tag
|
||||||
items = plex_db.getItem_byView(folderid)
|
items = plex_db.getItem_byView(folderid)
|
||||||
|
@ -407,23 +405,22 @@ class LibrarySync(Thread):
|
||||||
current_tagid, tagid, item[0], current_viewtype[:-1])
|
current_tagid, tagid, item[0], current_viewtype[:-1])
|
||||||
else:
|
else:
|
||||||
# Validate the playlist exists or recreate it
|
# Validate the playlist exists or recreate it
|
||||||
if mediatype != v.PLEX_TYPE_ARTIST:
|
if (foldername not in lists and mediatype in
|
||||||
if (foldername not in lists and mediatype in
|
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
|
||||||
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
|
utils.playlist_xsp(mediatype,
|
||||||
utils.playlist_xsp(mediatype,
|
foldername,
|
||||||
foldername,
|
folderid,
|
||||||
folderid,
|
viewtype)
|
||||||
viewtype)
|
lists.append(foldername)
|
||||||
lists.append(foldername)
|
# Create the video node if not already exists
|
||||||
# Create the video node if not already exists
|
if foldername not in nodes and mediatype != "musicvideos":
|
||||||
if foldername not in nodes and mediatype != "musicvideos":
|
vnodes.viewNode(sorted_views.index(foldername),
|
||||||
vnodes.viewNode(sorted_views.index(foldername),
|
foldername,
|
||||||
foldername,
|
mediatype,
|
||||||
mediatype,
|
viewtype,
|
||||||
viewtype,
|
folderid)
|
||||||
folderid)
|
nodes.append(foldername)
|
||||||
nodes.append(foldername)
|
totalnodes += 1
|
||||||
totalnodes += 1
|
|
||||||
return totalnodes
|
return totalnodes
|
||||||
|
|
||||||
def maintain_views(self):
|
def maintain_views(self):
|
||||||
|
@ -454,7 +451,8 @@ class LibrarySync(Thread):
|
||||||
|
|
||||||
for view in sections:
|
for view in sections:
|
||||||
if (view.attrib['type'] in
|
if (view.attrib['type'] in
|
||||||
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW, v.PLEX_TYPE_PHOTO)):
|
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW, v.PLEX_TYPE_PHOTO,
|
||||||
|
v.PLEX_TYPE_ARTIST)):
|
||||||
self.sorted_views.append(view.attrib['title'])
|
self.sorted_views.append(view.attrib['title'])
|
||||||
LOG.debug('Sorted views: %s', self.sorted_views)
|
LOG.debug('Sorted views: %s', self.sorted_views)
|
||||||
|
|
||||||
|
|
|
@ -69,14 +69,19 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
|
||||||
try:
|
try:
|
||||||
pos = js.get_position(playqueue.playlistid)
|
pos = js.get_position(playqueue.playlistid)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
LOG.error('Still no position - abort')
|
LOG.info('Assuming video instead of audio playlist playback')
|
||||||
# "Play error"
|
playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_VIDEO)
|
||||||
utils.dialog('notification',
|
try:
|
||||||
utils.lang(29999),
|
pos = js.get_position(playqueue.playlistid)
|
||||||
utils.lang(30128),
|
except KeyError:
|
||||||
icon='{error}')
|
LOG.error('Still no position - abort')
|
||||||
_ensure_resolve(abort=True)
|
# "Play error"
|
||||||
return
|
utils.dialog('notification',
|
||||||
|
utils.lang(29999),
|
||||||
|
utils.lang(30128),
|
||||||
|
icon='{error}')
|
||||||
|
_ensure_resolve(abort=True)
|
||||||
|
return
|
||||||
# HACK to detect playback of playlists for add-on paths
|
# HACK to detect playback of playlists for add-on paths
|
||||||
items = js.playlist_get_items(playqueue.playlistid)
|
items = js.playlist_get_items(playqueue.playlistid)
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -313,7 +313,8 @@ def verify_kodi_item(plex_id, kodi_item):
|
||||||
LOG.debug('Detected song. Research results: %s', kodi_item)
|
LOG.debug('Detected song. Research results: %s', kodi_item)
|
||||||
return kodi_item
|
return kodi_item
|
||||||
# Need more info since we don't have kodi_id nor type. Use file path.
|
# Need more info since we don't have kodi_id nor type. Use file path.
|
||||||
if (kodi_item['file'].startswith('plugin') or
|
if ((kodi_item['file'].startswith('plugin') and
|
||||||
|
not kodi_item['file'].startswith('plugin://%s' % v.ADDON_ID)) or
|
||||||
kodi_item['file'].startswith('http')):
|
kodi_item['file'].startswith('http')):
|
||||||
LOG.info('kodi_item %s cannot be used for Plex playback', kodi_item)
|
LOG.info('kodi_item %s cannot be used for Plex playback', kodi_item)
|
||||||
raise PlaylistError
|
raise PlaylistError
|
||||||
|
|
|
@ -816,20 +816,25 @@ class API(object):
|
||||||
'subtitle': subtitlelanguages
|
'subtitle': subtitlelanguages
|
||||||
}
|
}
|
||||||
|
|
||||||
def _one_artwork(self, art_kind):
|
def one_artwork(self, art_kind):
|
||||||
artwork = self.item.get(art_kind)
|
artwork = self.item.get(art_kind)
|
||||||
if artwork and not artwork.startswith('http'):
|
if artwork and not artwork.startswith('http'):
|
||||||
if '/composite/' in artwork:
|
if '/composite/' in artwork:
|
||||||
# e.g. Plex collections where artwork already contains width
|
try:
|
||||||
# and height. Need to upscale for better resolution
|
# e.g. Plex collections where artwork already contains
|
||||||
artwork, args = artwork.split('?')
|
# width and height. Need to upscale for better resolution
|
||||||
args = dict(parse_qsl(args))
|
artwork, args = artwork.split('?')
|
||||||
width = int(args.get('width', 400))
|
args = dict(parse_qsl(args))
|
||||||
height = int(args.get('height', 400))
|
width = int(args.get('width', 400))
|
||||||
# Adjust to 4k resolution 3,840x2,160
|
height = int(args.get('height', 400))
|
||||||
scaling = 3840.0 / float(max(width, height))
|
# Adjust to 4k resolution 3,840x2,160
|
||||||
width = int(scaling * width)
|
scaling = 3840.0 / float(max(width, height))
|
||||||
height = int(scaling * height)
|
width = int(scaling * width)
|
||||||
|
height = int(scaling * height)
|
||||||
|
except ValueError:
|
||||||
|
# e.g. playlists
|
||||||
|
width = 3840
|
||||||
|
height = 3840
|
||||||
artwork = '%s?width=%s&height=%s' % (artwork, width, height)
|
artwork = '%s?width=%s&height=%s' % (artwork, width, height)
|
||||||
artwork = ('%s/photo/:/transcode?width=3840&height=3840&'
|
artwork = ('%s/photo/:/transcode?width=3840&height=3840&'
|
||||||
'minSize=1&upscale=0&url=%s'
|
'minSize=1&upscale=0&url=%s'
|
||||||
|
@ -864,13 +869,13 @@ class API(object):
|
||||||
# the other artwork will be saved under season and show
|
# the other artwork will be saved under season and show
|
||||||
# EXCEPT if you're constructing a listitem
|
# EXCEPT if you're constructing a listitem
|
||||||
if not full_artwork:
|
if not full_artwork:
|
||||||
art = self._one_artwork('thumb')
|
art = self.one_artwork('thumb')
|
||||||
if art:
|
if art:
|
||||||
artworks['thumb'] = art
|
artworks['thumb'] = art
|
||||||
return artworks
|
return artworks
|
||||||
for kodi_artwork, plex_artwork in \
|
for kodi_artwork, plex_artwork in \
|
||||||
v.KODI_TO_PLEX_ARTWORK_EPISODE.iteritems():
|
v.KODI_TO_PLEX_ARTWORK_EPISODE.iteritems():
|
||||||
art = self._one_artwork(plex_artwork)
|
art = self.one_artwork(plex_artwork)
|
||||||
if art:
|
if art:
|
||||||
artworks[kodi_artwork] = art
|
artworks[kodi_artwork] = art
|
||||||
if not full_artwork:
|
if not full_artwork:
|
||||||
|
@ -912,24 +917,24 @@ class API(object):
|
||||||
# if self.plex_type() == v.PLEX_TYPE_EPISODE:
|
# if self.plex_type() == v.PLEX_TYPE_EPISODE:
|
||||||
|
|
||||||
for kodi_artwork, plex_artwork in v.KODI_TO_PLEX_ARTWORK.iteritems():
|
for kodi_artwork, plex_artwork in v.KODI_TO_PLEX_ARTWORK.iteritems():
|
||||||
art = self._one_artwork(plex_artwork)
|
art = self.one_artwork(plex_artwork)
|
||||||
if art:
|
if art:
|
||||||
artworks[kodi_artwork] = art
|
artworks[kodi_artwork] = art
|
||||||
if self.plex_type() in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_ALBUM):
|
if self.plex_type() in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_ALBUM):
|
||||||
# Get parent item artwork if the main item is missing artwork
|
# Get parent item artwork if the main item is missing artwork
|
||||||
if 'fanart' not in artworks:
|
if 'fanart' not in artworks:
|
||||||
art = self._one_artwork('parentArt')
|
art = self.one_artwork('parentArt')
|
||||||
if art:
|
if art:
|
||||||
artworks['fanart1'] = art
|
artworks['fanart1'] = art
|
||||||
if 'poster' not in artworks:
|
if 'poster' not in artworks:
|
||||||
art = self._one_artwork('parentThumb')
|
art = self.one_artwork('parentThumb')
|
||||||
if art:
|
if art:
|
||||||
artworks['poster'] = art
|
artworks['poster'] = art
|
||||||
if self.plex_type() in (v.PLEX_TYPE_SONG,
|
if self.plex_type() in (v.PLEX_TYPE_SONG,
|
||||||
v.PLEX_TYPE_ALBUM,
|
v.PLEX_TYPE_ALBUM,
|
||||||
v.PLEX_TYPE_ARTIST):
|
v.PLEX_TYPE_ARTIST):
|
||||||
# need to set poster also as thumb
|
# need to set poster also as thumb
|
||||||
art = self._one_artwork('thumb')
|
art = self.one_artwork('thumb')
|
||||||
if art:
|
if art:
|
||||||
artworks['thumb'] = art
|
artworks['thumb'] = art
|
||||||
return artworks
|
return artworks
|
||||||
|
@ -1468,7 +1473,10 @@ class API(object):
|
||||||
listitem = self._create_photo_listitem(listitem)
|
listitem = self._create_photo_listitem(listitem)
|
||||||
# Only set the bare minimum of artwork
|
# Only set the bare minimum of artwork
|
||||||
listitem.setArt({'icon': 'DefaultPicture.png',
|
listitem.setArt({'icon': 'DefaultPicture.png',
|
||||||
'fanart': self._one_artwork('thumb')})
|
'fanart': self.one_artwork('thumb')})
|
||||||
|
elif self.plex_type() == v.PLEX_TYPE_SONG:
|
||||||
|
listitem = self._create_audio_listitem(listitem)
|
||||||
|
listitem.setArt(self.artwork())
|
||||||
else:
|
else:
|
||||||
listitem = self._create_video_listitem(listitem,
|
listitem = self._create_video_listitem(listitem,
|
||||||
append_show_title,
|
append_show_title,
|
||||||
|
@ -1591,6 +1599,74 @@ class API(object):
|
||||||
pass
|
pass
|
||||||
return listitem
|
return listitem
|
||||||
|
|
||||||
|
def track_number(self):
|
||||||
|
"""
|
||||||
|
Returns the song's track number as an int or None if not found
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return int(self.item.get('index'))
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def disc_number(self):
|
||||||
|
"""
|
||||||
|
Returns the song's disc number as an int or None if not found
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return int(self.item.get('parentIndex'))
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _create_audio_listitem(self, listitem=None):
|
||||||
|
"""
|
||||||
|
Use for songs only
|
||||||
|
Call on a child level of PMS xml response (e.g. in a for loop)
|
||||||
|
|
||||||
|
listitem : existing xbmcgui.ListItem to work with
|
||||||
|
otherwise, a new one is created
|
||||||
|
|
||||||
|
Returns XBMC listitem for this PMS library item
|
||||||
|
"""
|
||||||
|
if listitem is None:
|
||||||
|
listitem = ListItem(self.title())
|
||||||
|
else:
|
||||||
|
listitem.setLabel(self.title())
|
||||||
|
listitem.setProperty('IsPlayable', 'true')
|
||||||
|
userdata = self.userdata()
|
||||||
|
metadata = {
|
||||||
|
'mediatype': 'song',
|
||||||
|
'tracknumber': self.track_number(),
|
||||||
|
'discnumber': self.track_number(),
|
||||||
|
'duration': userdata['Runtime'],
|
||||||
|
'year': self.year(),
|
||||||
|
# Kodi does not support list of str
|
||||||
|
'genre': ','.join(self.genre_list()) or None,
|
||||||
|
'album': self.item.get('parentTitle'),
|
||||||
|
'artist': self.item.get('originalTitle') or self.grandparent_title(),
|
||||||
|
'title': self.title(),
|
||||||
|
'rating': self.audience_rating(),
|
||||||
|
'playcount': userdata['PlayCount'],
|
||||||
|
'lastplayed': userdata['LastPlayedDate'],
|
||||||
|
# lyrics string (On a dark desert highway...)
|
||||||
|
# userrating integer - range is 1..10
|
||||||
|
# comment string (This is a great song)
|
||||||
|
# listeners integer (25614)
|
||||||
|
# musicbrainztrackid string (cd1de9af-0b71-4503-9f96-9f5efe27923c)
|
||||||
|
# musicbrainzartistid string (d87e52c5-bb8d-4da8-b941-9f4928627dc8)
|
||||||
|
# musicbrainzalbumid string (24944755-2f68-3778-974e-f572a9e30108)
|
||||||
|
# musicbrainzalbumartistid string (d87e52c5-bb8d-4da8-b941-9f4928627dc8)
|
||||||
|
}
|
||||||
|
plex_id = self.plex_id()
|
||||||
|
listitem.setProperty('plexid', plex_id)
|
||||||
|
if v.KODIVERSION >= 18:
|
||||||
|
with plexdb.Get_Plex_DB() as plex_db:
|
||||||
|
kodi_id = plex_db.getItem_byId(plex_id)
|
||||||
|
if kodi_id:
|
||||||
|
kodi_id = kodi_id[0]
|
||||||
|
metadata['dbid'] = kodi_id
|
||||||
|
listitem.setInfo('music', infoLabels=metadata)
|
||||||
|
return listitem
|
||||||
|
|
||||||
def add_video_streams(self, listitem):
|
def add_video_streams(self, listitem):
|
||||||
"""
|
"""
|
||||||
Add media stream information to xbmcgui.ListItem
|
Add media stream information to xbmcgui.ListItem
|
||||||
|
|
|
@ -115,9 +115,12 @@ def settings(setting, value=None):
|
||||||
|
|
||||||
def lang(stringid):
|
def lang(stringid):
|
||||||
"""
|
"""
|
||||||
Central string retrieval from strings.po
|
Central string retrieval from strings.po. If not found within PKC,
|
||||||
|
standard XBMC/Kodi strings are retrieved.
|
||||||
|
Will return unicode
|
||||||
"""
|
"""
|
||||||
return ADDON.getLocalizedString(stringid)
|
return (ADDON.getLocalizedString(stringid) or
|
||||||
|
xbmc.getLocalizedString(stringid))
|
||||||
|
|
||||||
|
|
||||||
def dialog(typus, *args, **kwargs):
|
def dialog(typus, *args, **kwargs):
|
||||||
|
|
|
@ -54,7 +54,8 @@ class VideoNodes(object):
|
||||||
'show': 'tvshows',
|
'show': 'tvshows',
|
||||||
'photo': 'photos',
|
'photo': 'photos',
|
||||||
'homevideo': 'homevideos',
|
'homevideo': 'homevideos',
|
||||||
'musicvideos': 'musicvideos'
|
'musicvideos': 'musicvideos',
|
||||||
|
'artist': 'albums'
|
||||||
}
|
}
|
||||||
mediatype = mediatypes[mediatype]
|
mediatype = mediatypes[mediatype]
|
||||||
|
|
||||||
|
@ -135,7 +136,8 @@ class VideoNodes(object):
|
||||||
'10': "random",
|
'10': "random",
|
||||||
'11': "recommended",
|
'11': "recommended",
|
||||||
'12': "ondeck",
|
'12': "ondeck",
|
||||||
'13': 'browsefiles'
|
'13': 'browsefiles',
|
||||||
|
'14': 'playlists'
|
||||||
}
|
}
|
||||||
mediatypes = {
|
mediatypes = {
|
||||||
# label according to nodetype per mediatype
|
# label according to nodetype per mediatype
|
||||||
|
@ -150,7 +152,8 @@ class VideoNodes(object):
|
||||||
'10': 30227,
|
'10': 30227,
|
||||||
'11': 30230,
|
'11': 30230,
|
||||||
'12': 39500,
|
'12': 39500,
|
||||||
'13': 39702
|
'13': 39702,
|
||||||
|
'14': 136
|
||||||
},
|
},
|
||||||
|
|
||||||
'tvshows':
|
'tvshows':
|
||||||
|
@ -165,7 +168,8 @@ class VideoNodes(object):
|
||||||
'10': 30227,
|
'10': 30227,
|
||||||
# '11': 30230,
|
# '11': 30230,
|
||||||
'12': 39500,
|
'12': 39500,
|
||||||
'13': 39702
|
'13': 39702,
|
||||||
|
'14': 136
|
||||||
},
|
},
|
||||||
|
|
||||||
'homevideos':
|
'homevideos':
|
||||||
|
@ -173,7 +177,8 @@ class VideoNodes(object):
|
||||||
'1': tagname,
|
'1': tagname,
|
||||||
'2': 30251,
|
'2': 30251,
|
||||||
'11': 30253,
|
'11': 30253,
|
||||||
'13': 39702
|
'13': 39702,
|
||||||
|
'14': 136
|
||||||
},
|
},
|
||||||
|
|
||||||
'photos':
|
'photos':
|
||||||
|
@ -192,6 +197,15 @@ class VideoNodes(object):
|
||||||
'4': 30257,
|
'4': 30257,
|
||||||
'6': 30258,
|
'6': 30258,
|
||||||
'13': 39702
|
'13': 39702
|
||||||
|
},
|
||||||
|
|
||||||
|
'albums':
|
||||||
|
{
|
||||||
|
'1': tagname,
|
||||||
|
'2': 517, # Recently played albums
|
||||||
|
'2': 359, # Recently added albums
|
||||||
|
'13': 39702, # browse by folder
|
||||||
|
'14': 136 # Playlists
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +223,8 @@ class VideoNodes(object):
|
||||||
'10': '8', # "random",
|
'10': '8', # "random",
|
||||||
'11': '5', # "recommended",
|
'11': '5', # "recommended",
|
||||||
'12': '1', # "ondeck"
|
'12': '1', # "ondeck"
|
||||||
'13': '9' # browse by folder
|
'13': '9', # browse by folder
|
||||||
|
'14': '10' # Playlists
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes = mediatypes[mediatype]
|
nodes = mediatypes[mediatype]
|
||||||
|
@ -221,8 +236,6 @@ class VideoNodes(object):
|
||||||
stringid = nodes[node]
|
stringid = nodes[node]
|
||||||
if node != "1":
|
if node != "1":
|
||||||
label = utils.lang(stringid)
|
label = utils.lang(stringid)
|
||||||
if not label:
|
|
||||||
label = xbmc.getLocalizedString(stringid)
|
|
||||||
else:
|
else:
|
||||||
label = stringid
|
label = stringid
|
||||||
|
|
||||||
|
@ -257,6 +270,12 @@ class VideoNodes(object):
|
||||||
nodetype = 'inprogress'
|
nodetype = 'inprogress'
|
||||||
elif nodetype == 'browsefiles':
|
elif nodetype == 'browsefiles':
|
||||||
path = 'plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s/folder' % viewid
|
path = 'plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s/folder' % viewid
|
||||||
|
elif nodetype == 'playlists':
|
||||||
|
path = 'plugin://plugin.video.plexkodiconnect?mode=playlists'
|
||||||
|
if mediatype in ('movies', 'tvshows', 'homevideos'):
|
||||||
|
path += '&type=%s' % v.PLEX_TYPE_VIDEO_PLAYLIST
|
||||||
|
else:
|
||||||
|
path += '&type=%s' % v.PLEX_TYPE_AUDIO_PLAYLIST
|
||||||
else:
|
else:
|
||||||
path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype)
|
path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype)
|
||||||
|
|
||||||
|
@ -298,23 +317,33 @@ class VideoNodes(object):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Create the root
|
# Create the root
|
||||||
if (nodetype in ("nextepisodes", "ondeck", 'recentepisodes', 'browsefiles') or mediatype == "homevideos"):
|
if (nodetype in ("nextepisodes",
|
||||||
|
"ondeck",
|
||||||
|
'recentepisodes',
|
||||||
|
'browsefiles',
|
||||||
|
'playlists') or mediatype == "homevideos"):
|
||||||
# Folder type with plugin path
|
# Folder type with plugin path
|
||||||
root = self.commonRoot(order=sortorder[node],
|
root = self.commonRoot(order=sortorder[node],
|
||||||
label=label,
|
label=label,
|
||||||
tagname=tagname,
|
tagname=tagname,
|
||||||
roottype=2)
|
roottype=2)
|
||||||
etree.SubElement(root, 'path').text = path
|
|
||||||
etree.SubElement(root, 'content').text = "episodes"
|
|
||||||
else:
|
else:
|
||||||
root = self.commonRoot(order=sortorder[node],
|
root = self.commonRoot(order=sortorder[node],
|
||||||
label=label,
|
label=label,
|
||||||
tagname=tagname)
|
tagname=tagname)
|
||||||
if nodetype in ('recentepisodes', 'inprogressepisodes'):
|
# Set the content type
|
||||||
etree.SubElement(root, 'content').text = "episodes"
|
if mediatype == 'tvshows':
|
||||||
else:
|
etree.SubElement(root, 'content').text = 'episodes'
|
||||||
etree.SubElement(root, 'content').text = mediatype
|
else:
|
||||||
|
etree.SubElement(root, 'content').text = mediatype
|
||||||
|
# Now fill the view
|
||||||
|
if (nodetype in ("nextepisodes",
|
||||||
|
"ondeck",
|
||||||
|
'recentepisodes',
|
||||||
|
'browsefiles',
|
||||||
|
'playlists') or mediatype == "homevideos"):
|
||||||
|
etree.SubElement(root, 'path').text = path
|
||||||
|
else:
|
||||||
# Elements per nodetype
|
# Elements per nodetype
|
||||||
if nodetype == "all":
|
if nodetype == "all":
|
||||||
etree.SubElement(root,
|
etree.SubElement(root,
|
||||||
|
|
Loading…
Reference in a new issue