commit
1ed8de2e0f
18 changed files with 2089 additions and 171 deletions
|
@ -1,5 +1,5 @@
|
||||||
[![stable version](https://img.shields.io/badge/stable_version-2.8.7-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.9.0-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.8.7-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.9.0-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)
|
||||||
|
@ -78,6 +78,7 @@ Some people argue that PKC is 'hacky' because of the way it directly accesses th
|
||||||
+ Russian, thanks @UncleStark
|
+ Russian, thanks @UncleStark
|
||||||
+ Hungarian, thanks @savage93
|
+ Hungarian, thanks @savage93
|
||||||
+ Ukrainian, thanks @uniss
|
+ Ukrainian, thanks @uniss
|
||||||
|
+ Lithuanian, thanks @egidusm
|
||||||
|
|
||||||
### Additional Artwork
|
### Additional Artwork
|
||||||
PKC uses additional artwork for free from [TheMovieDB](https://www.themoviedb.org). Many thanks for lettings us use the API, guys!
|
PKC uses additional artwork for free from [TheMovieDB](https://www.themoviedb.org). Many thanks for lettings us use the API, guys!
|
||||||
|
|
34
addon.xml
34
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.8.7" provider-name="croneter">
|
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.9.0" 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" />
|
||||||
|
@ -77,7 +77,37 @@
|
||||||
<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.8.7:
|
<summary lang="sv_SE">Inbyggd integrering av Plex i Kodi</summary>
|
||||||
|
<description lang="sv_SE">Anslut Kodi till din Plex Media Server. Detta tillägg antar att du hanterar alla dina filmer med Plex (och ingen med Kodi). Du kan förlora data redan sparad i Kodis video och musik databaser (eftersom detta tillägg direkt ändrar dem). Använd på egen risk!</description>
|
||||||
|
<disclaimer lang="sv_SE">Använd på egen risk</disclaimer>
|
||||||
|
<summary lang="lt_LT">Natūralioji „Plex“ integracija į „Kodi“</summary>
|
||||||
|
<description lang="lt_LT">Prijunkite „Kodi“ prie „Plex Medija Serverio“. Šiame papildinyje daroma prielaida, kad valdote visus savo vaizdo įrašus naudodami „Plex“ (ir nė vieno su „Kodi“). Galite prarasti jau saugomus „Kodi“ vaizdo įrašų ir muzikos duomenų bazių duomenis (kadangi šis papildinys juos tiesiogiai pakeičia). Naudokite savo pačių rizika!</description>
|
||||||
|
<disclaimer lang="lt_LT">Naudokite savo pačių rizika</disclaimer>
|
||||||
|
<news>version 2.9.0:
|
||||||
|
WARNING: You might have to manually select your PKC widgets again
|
||||||
|
- versions 2.8.8 - 2.8.11 for everyone
|
||||||
|
- Fix AttributeError: 'NoneType' object has no attribute 'attrib' on playback startup
|
||||||
|
- Add new Lithuanian translations (thanks @egidusm)
|
||||||
|
|
||||||
|
version 2.8.11 (beta only):
|
||||||
|
- Support for the Up Next Kodi add-on
|
||||||
|
- Fix casting to PlexKodiConnect always starting the first episode
|
||||||
|
- Rename video nodes for ondeck
|
||||||
|
|
||||||
|
version 2.8.10 (beta only):
|
||||||
|
- Fix broken PKC update
|
||||||
|
|
||||||
|
version 2.8.9 (beta only):
|
||||||
|
- Fix sections that are not synced not displaying menu but entire library
|
||||||
|
- Provide more metadata for unsynced directory-like items like a tv show
|
||||||
|
- Fix 'Plex.nodes."id".path' not linking directly to entire library
|
||||||
|
|
||||||
|
version 2.8.8 (beta only):
|
||||||
|
WARNING: You might have to manually select your PKC widgets again
|
||||||
|
- Ensure correct Kodi Container.Type is set for PKC widgets
|
||||||
|
- Fix missing cast artwork if an actor also acted as director or writer for another movie. You will have to manually reset the Kodi DB.
|
||||||
|
|
||||||
|
version 2.8.7:
|
||||||
- Fix PKC potentially marking a video as watched on startup; don't sync time by toggling a video watch status but use PMS epoch time
|
- Fix PKC potentially marking a video as watched on startup; don't sync time by toggling a video watch status but use PMS epoch time
|
||||||
|
|
||||||
version 2.8.6:
|
version 2.8.6:
|
||||||
|
|
|
@ -1,3 +1,27 @@
|
||||||
|
version 2.9.0:
|
||||||
|
WARNING: You might have to manually select your PKC widgets again
|
||||||
|
- versions 2.8.8 - 2.8.11 for everyone
|
||||||
|
- Fix AttributeError: 'NoneType' object has no attribute 'attrib' on playback startup
|
||||||
|
- Add new Lithuanian translations (thanks @egidusm)
|
||||||
|
|
||||||
|
version 2.8.11 (beta only):
|
||||||
|
- Support for the Up Next Kodi add-on
|
||||||
|
- Fix casting to PlexKodiConnect always starting the first episode
|
||||||
|
- Rename video nodes for ondeck
|
||||||
|
|
||||||
|
version 2.8.10 (beta only):
|
||||||
|
- Fix broken PKC update
|
||||||
|
|
||||||
|
version 2.8.9 (beta only):
|
||||||
|
- Fix sections that are not synced not displaying menu but entire library
|
||||||
|
- Provide more metadata for unsynced directory-like items like a tv show
|
||||||
|
- Fix 'Plex.nodes.<id>.path' not linking directly to entire library
|
||||||
|
|
||||||
|
version 2.8.8 (beta only):
|
||||||
|
WARNING: You might have to manually select your PKC widgets again
|
||||||
|
- Ensure correct Kodi Container.Type is set for PKC widgets
|
||||||
|
- Fix missing cast artwork if an actor also acted as director or writer for another movie. You will have to manually reset the Kodi DB.
|
||||||
|
|
||||||
version 2.8.7:
|
version 2.8.7:
|
||||||
- Fix PKC potentially marking a video as watched on startup; don't sync time by toggling a video watch status but use PMS epoch time
|
- Fix PKC potentially marking a video as watched on startup; don't sync time by toggling a video watch status but use PMS epoch time
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,9 @@ class Main():
|
||||||
synched=params.get('synched') != 'false',
|
synched=params.get('synched') != 'false',
|
||||||
prompt=params.get('prompt'))
|
prompt=params.get('prompt'))
|
||||||
|
|
||||||
|
elif mode == 'show_section':
|
||||||
|
entrypoint.show_section(params.get('section_index'))
|
||||||
|
|
||||||
elif mode == 'watchlater':
|
elif mode == 'watchlater':
|
||||||
entrypoint.watchlater()
|
entrypoint.watchlater()
|
||||||
|
|
||||||
|
|
1618
resources/language/resource.language.lt_LT/strings.po
Normal file
1618
resources/language/resource.language.lt_LT/strings.po
Normal file
File diff suppressed because it is too large
Load diff
|
@ -20,11 +20,13 @@ from . import plex_functions as PF
|
||||||
from . import variables as v
|
from . import variables as v
|
||||||
# Be careful - your using app in another Python instance!
|
# Be careful - your using app in another Python instance!
|
||||||
from . import app, widgets
|
from . import app, widgets
|
||||||
|
from .library_sync.nodes import NODE_TYPES
|
||||||
|
|
||||||
|
|
||||||
LOG = getLogger('PLEX.entrypoint')
|
LOG = getLogger('PLEX.entrypoint')
|
||||||
|
|
||||||
|
|
||||||
def guess_content_type():
|
def guess_video_or_audio():
|
||||||
"""
|
"""
|
||||||
Returns either 'video', 'audio' or 'image', based how the user navigated to
|
Returns either 'video', 'audio' or 'image', based how the user navigated to
|
||||||
the current view.
|
the current view.
|
||||||
|
@ -102,9 +104,9 @@ def show_main_menu(content_type=None):
|
||||||
"""
|
"""
|
||||||
Shows the main PKC menu listing with all libraries, Channel, settings, etc.
|
Shows the main PKC menu listing with all libraries, Channel, settings, etc.
|
||||||
"""
|
"""
|
||||||
content_type = content_type or guess_content_type()
|
content_type = content_type or guess_video_or_audio()
|
||||||
LOG.debug('Do main listing for content_type: %s', content_type)
|
LOG.debug('Do main listing for %s', content_type)
|
||||||
xbmcplugin.setContent(int(sys.argv[1]), 'files')
|
xbmcplugin.setContent(int(sys.argv[1]), v.CONTENT_TYPE_FILE)
|
||||||
# Get nodes from the window props
|
# Get nodes from the window props
|
||||||
totalnodes = int(utils.window('Plex.nodes.total') or 0)
|
totalnodes = int(utils.window('Plex.nodes.total') or 0)
|
||||||
for i in range(totalnodes):
|
for i in range(totalnodes):
|
||||||
|
@ -133,14 +135,6 @@ def show_main_menu(content_type=None):
|
||||||
# Should only be called if the user selects widgets
|
# Should only be called if the user selects widgets
|
||||||
LOG.info('Detected user selecting widgets')
|
LOG.info('Detected user selecting widgets')
|
||||||
directory_item(label, path)
|
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_index = utils.window('Plex.nodes.%s.addon_index' % i)
|
|
||||||
# Append "(More...)" to the label
|
|
||||||
directory_item('%s (%s)' % (label, utils.lang(22082)), addon_index)
|
|
||||||
# Playlists
|
# Playlists
|
||||||
if content_type != 'image':
|
if content_type != 'image':
|
||||||
path = 'plugin://%s?mode=playlists' % v.ADDON_ID
|
path = 'plugin://%s?mode=playlists' % v.ADDON_ID
|
||||||
|
@ -169,32 +163,64 @@ def show_main_menu(content_type=None):
|
||||||
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
||||||
|
|
||||||
|
|
||||||
def show_listing(xml, plex_type=None, section_id=None, synched=True, key=None,
|
def show_section(section_index):
|
||||||
content_type=None):
|
"""
|
||||||
|
Displays menu for an entire Plex section. We're using add-on paths instead
|
||||||
|
of Kodi video library xmls to be able to use type="filter" library xmls
|
||||||
|
and thus set the "content"
|
||||||
|
|
||||||
|
Only used for synched Plex sections - otherwise, PMS xml for the section
|
||||||
|
is used directly
|
||||||
|
"""
|
||||||
|
LOG.debug('Do section listing for section index %s', section_index)
|
||||||
|
xbmcplugin.setContent(int(sys.argv[1]), v.CONTENT_TYPE_FILE)
|
||||||
|
# Get nodes from the window props
|
||||||
|
node = 'Plex.nodes.%s' % section_index
|
||||||
|
content = utils.window('%s.type' % node)
|
||||||
|
plex_type = v.PLEX_TYPE_MOVIE if content == v.CONTENT_TYPE_MOVIE \
|
||||||
|
else v.PLEX_TYPE_SHOW
|
||||||
|
for node_type, _, _, _, _ in NODE_TYPES[plex_type]:
|
||||||
|
label = utils.window('%s.%s.title' % (node, node_type))
|
||||||
|
path = utils.window('%s.%s.index' % (node, node_type))
|
||||||
|
directory_item(label, path)
|
||||||
|
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
||||||
|
|
||||||
|
|
||||||
|
def show_listing(xml, plex_type=None, section_id=None, synched=True, key=None):
|
||||||
"""
|
"""
|
||||||
Pass synched=False if the items have not been synched to the Kodi DB
|
Pass synched=False if the items have not been synched to the Kodi DB
|
||||||
|
|
||||||
|
Kodi content type will be set using the very first item returned by the PMS
|
||||||
"""
|
"""
|
||||||
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)
|
|
||||||
try:
|
try:
|
||||||
xml[0]
|
xml[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
LOG.info('xml received from the PMS is empty: %s', xml.attrib)
|
LOG.info('xml received from the PMS is empty: %s, %s',
|
||||||
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
|
xml.tag, xml.attrib)
|
||||||
|
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
||||||
return
|
return
|
||||||
if content_type == 'video':
|
api = API(xml[0])
|
||||||
xbmcplugin.setContent(int(sys.argv[1]), 'videos')
|
# Determine content type for Kodi's Container.content
|
||||||
elif content_type == 'audio':
|
if key == '/hubs/home/continueWatching':
|
||||||
xbmcplugin.setContent(int(sys.argv[1]), 'artists')
|
# Mix of movies and episodes
|
||||||
elif plex_type in (v.PLEX_TYPE_PLAYLIST, v.PLEX_TYPE_CHANNEL):
|
plex_type = v.PLEX_TYPE_VIDEO
|
||||||
xbmcplugin.setContent(int(sys.argv[1]), 'videos')
|
elif key == '/hubs/home/recentlyAdded?type=2':
|
||||||
elif plex_type:
|
# "Recently Added TV", potentially a mix of Seasons and Episodes
|
||||||
xbmcplugin.setContent(int(sys.argv[1]),
|
plex_type = v.PLEX_TYPE_VIDEO
|
||||||
v.MEDIATYPE_FROM_PLEX_TYPE[plex_type])
|
elif api.plex_type is None and api.fast_key and '?collection=' in api.fast_key:
|
||||||
|
# Collections/Kodi sets
|
||||||
|
plex_type = v.PLEX_TYPE_SET
|
||||||
|
elif api.plex_type is None and plex_type:
|
||||||
|
# e.g. browse by folder - folders will be listed first
|
||||||
|
# Retain plex_type
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
xbmcplugin.setContent(int(sys.argv[1]), 'files')
|
plex_type = api.plex_type
|
||||||
|
content_type = v.CONTENT_FROM_PLEX_TYPE[plex_type]
|
||||||
|
LOG.debug('show_listing: section_id %s, synched %s, key %s, plex_type %s, '
|
||||||
|
'content type %s',
|
||||||
|
section_id, synched, key, plex_type, content_type)
|
||||||
|
xbmcplugin.setContent(int(sys.argv[1]), content_type)
|
||||||
# Initialization
|
# Initialization
|
||||||
widgets.PLEX_TYPE = plex_type
|
widgets.PLEX_TYPE = plex_type
|
||||||
widgets.SYNCHED = synched
|
widgets.SYNCHED = synched
|
||||||
|
@ -204,11 +230,15 @@ def show_listing(xml, plex_type=None, section_id=None, synched=True, key=None,
|
||||||
if plex_type == v.PLEX_TYPE_SHOW and key and 'recentlyAdded' in key:
|
if plex_type == v.PLEX_TYPE_SHOW and key and 'recentlyAdded' in key:
|
||||||
widgets.APPEND_SHOW_TITLE = utils.settings('RecentTvAppendShow') == 'true'
|
widgets.APPEND_SHOW_TITLE = utils.settings('RecentTvAppendShow') == 'true'
|
||||||
widgets.APPEND_SXXEXX = utils.settings('RecentTvAppendSeason') == 'true'
|
widgets.APPEND_SXXEXX = utils.settings('RecentTvAppendSeason') == 'true'
|
||||||
if content_type and xml[0].tag == 'Playlist':
|
if api.tag == 'Playlist':
|
||||||
# Certain views mix playlist types audio and video
|
# Only show video playlists if navigation started for videos
|
||||||
for entry in reversed(xml):
|
# and vice-versa for audio playlists
|
||||||
if entry.get('playlistType') != content_type:
|
content = guess_video_or_audio()
|
||||||
xml.remove(entry)
|
if content:
|
||||||
|
for entry in reversed(xml):
|
||||||
|
tmp_api = API(entry)
|
||||||
|
if tmp_api.playlist_type() != content:
|
||||||
|
xml.remove(entry)
|
||||||
if xml.get('librarySectionID'):
|
if xml.get('librarySectionID'):
|
||||||
widgets.SECTION_ID = utils.cast(int, xml.get('librarySectionID'))
|
widgets.SECTION_ID = utils.cast(int, xml.get('librarySectionID'))
|
||||||
elif section_id:
|
elif section_id:
|
||||||
|
@ -355,15 +385,14 @@ def playlists(content_type):
|
||||||
Lists all Plex playlists of the media type plex_playlist_type
|
Lists all Plex playlists of the media type plex_playlist_type
|
||||||
content_type: 'audio', 'video'
|
content_type: 'audio', 'video'
|
||||||
"""
|
"""
|
||||||
content_type = content_type or guess_content_type()
|
LOG.debug('Listing Plex playlists for content type %s', content_type)
|
||||||
LOG.debug('Listing Plex %s playlists', content_type)
|
|
||||||
if not _wait_for_auth():
|
if not _wait_for_auth():
|
||||||
return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
|
return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
|
||||||
app.init(entrypoint=True)
|
app.init(entrypoint=True)
|
||||||
from .playlists.pms import all_playlists
|
from .playlists.pms import all_playlists
|
||||||
xml = all_playlists()
|
xml = all_playlists()
|
||||||
if xml is None:
|
if xml is None:
|
||||||
return
|
return xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
|
||||||
if content_type is not None:
|
if content_type is not None:
|
||||||
# This will be skipped if user selects a widget
|
# This will be skipped if user selects a widget
|
||||||
# Buggy xml.remove(child) requires reversed()
|
# Buggy xml.remove(child) requires reversed()
|
||||||
|
@ -371,7 +400,7 @@ def playlists(content_type):
|
||||||
api = API(entry)
|
api = API(entry)
|
||||||
if not api.playlist_type() == content_type:
|
if not api.playlist_type() == content_type:
|
||||||
xml.remove(entry)
|
xml.remove(entry)
|
||||||
show_listing(xml, content_type=content_type)
|
show_listing(xml)
|
||||||
|
|
||||||
|
|
||||||
def hub(content_type):
|
def hub(content_type):
|
||||||
|
@ -380,7 +409,7 @@ def hub(content_type):
|
||||||
content_type:
|
content_type:
|
||||||
audio, video, image
|
audio, video, image
|
||||||
"""
|
"""
|
||||||
content_type = content_type or guess_content_type()
|
content_type = content_type or guess_video_or_audio()
|
||||||
LOG.debug('Showing Plex Hub entries for %s', content_type)
|
LOG.debug('Showing Plex Hub entries for %s', content_type)
|
||||||
if not _wait_for_auth():
|
if not _wait_for_auth():
|
||||||
return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
|
return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
|
||||||
|
@ -410,7 +439,7 @@ def hub(content_type):
|
||||||
append = True
|
append = True
|
||||||
if not append:
|
if not append:
|
||||||
xml.remove(entry)
|
xml.remove(entry)
|
||||||
show_listing(xml, content_type=content_type)
|
show_listing(xml)
|
||||||
|
|
||||||
|
|
||||||
def watchlater():
|
def watchlater():
|
||||||
|
@ -447,7 +476,8 @@ def browse_plex(key=None, plex_type=None, section_id=None, synched=True,
|
||||||
LOG.debug('Browsing to key %s, section %s, plex_type: %s, synched: %s, '
|
LOG.debug('Browsing to key %s, section %s, plex_type: %s, synched: %s, '
|
||||||
'prompt "%s"', key, section_id, plex_type, synched, prompt)
|
'prompt "%s"', key, section_id, plex_type, synched, prompt)
|
||||||
if not _wait_for_auth():
|
if not _wait_for_auth():
|
||||||
return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
|
xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
|
||||||
|
return
|
||||||
app.init(entrypoint=True)
|
app.init(entrypoint=True)
|
||||||
if prompt:
|
if prompt:
|
||||||
prompt = utils.dialog('input', prompt)
|
prompt = utils.dialog('input', prompt)
|
||||||
|
|
|
@ -349,7 +349,13 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
if kind == 'actor':
|
if kind == 'actor':
|
||||||
for person in people_list:
|
for person in people_list:
|
||||||
# Make sure the person entry in table actor exists
|
# Make sure the person entry in table actor exists
|
||||||
actor_id = self._get_actor_id(person[0], art_url=person[1])
|
actor_id, new = self._get_actor_id(person[0],
|
||||||
|
art_url=person[1])
|
||||||
|
if not new and person[1]:
|
||||||
|
# Person might have shown up as a director or writer first
|
||||||
|
# WITHOUT an art url from the Plex side!
|
||||||
|
# Check here if we need to set the actor's art url
|
||||||
|
self._check_actor_art(actor_id, person[1])
|
||||||
# Link the person with the media element
|
# Link the person with the media element
|
||||||
try:
|
try:
|
||||||
self.cursor.execute('INSERT INTO actor_link VALUES (?, ?, ?, ?, ?)',
|
self.cursor.execute('INSERT INTO actor_link VALUES (?, ?, ?, ?, ?)',
|
||||||
|
@ -361,7 +367,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
else:
|
else:
|
||||||
for person in people_list:
|
for person in people_list:
|
||||||
# Make sure the person entry in table actor exists:
|
# Make sure the person entry in table actor exists:
|
||||||
actor_id = self._get_actor_id(person[0])
|
actor_id, _ = self._get_actor_id(person[0])
|
||||||
# Link the person with the media element
|
# Link the person with the media element
|
||||||
try:
|
try:
|
||||||
self.cursor.execute('INSERT INTO %s_link VALUES (?, ?, ?)' % kind,
|
self.cursor.execute('INSERT INTO %s_link VALUES (?, ?, ?)' % kind,
|
||||||
|
@ -450,8 +456,11 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
|
|
||||||
def _get_actor_id(self, name, art_url=None):
|
def _get_actor_id(self, name, art_url=None):
|
||||||
"""
|
"""
|
||||||
Returns the actor_id [int] for name [unicode] in table actor (without
|
Returns the tuple
|
||||||
ensuring that the name matches).
|
(actor_id [int], new_entry [bool])
|
||||||
|
for name [unicode] in table actor (without ensuring that the name
|
||||||
|
matches)."new_entry" will be True if a new DB entry has just been
|
||||||
|
created.
|
||||||
If not, will create a new record with actor_id, name, art_url
|
If not, will create a new record with actor_id, name, art_url
|
||||||
|
|
||||||
Uses Plex ids and thus assumes that Plex person id is unique!
|
Uses Plex ids and thus assumes that Plex person id is unique!
|
||||||
|
@ -459,9 +468,21 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
self.cursor.execute('SELECT actor_id FROM actor WHERE name=? LIMIT 1',
|
self.cursor.execute('SELECT actor_id FROM actor WHERE name=? LIMIT 1',
|
||||||
(name,))
|
(name,))
|
||||||
try:
|
try:
|
||||||
return self.cursor.fetchone()[0]
|
return (self.cursor.fetchone()[0], False)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return self._new_actor_id(name, art_url)
|
return (self._new_actor_id(name, art_url), True)
|
||||||
|
|
||||||
|
def _check_actor_art(self, actor_id, url):
|
||||||
|
"""
|
||||||
|
Sets the actor's art url [unicode] for actor_id [int]
|
||||||
|
"""
|
||||||
|
self.cursor.execute('''
|
||||||
|
SELECT EXISTS(SELECT 1 FROM art
|
||||||
|
WHERE media_id = ? AND media_type = 'actor'
|
||||||
|
LIMIT 1)''', (actor_id, ))
|
||||||
|
if not self.cursor.fetchone()[0]:
|
||||||
|
# We got a new artwork url for this actor!
|
||||||
|
self.add_art(url, actor_id, 'actor', 'thumb')
|
||||||
|
|
||||||
def get_art(self, kodi_id, kodi_type):
|
def get_art(self, kodi_id, kodi_type):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -7,10 +7,13 @@ from __future__ import absolute_import, division, unicode_literals
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from json import loads
|
from json import loads
|
||||||
import copy
|
import copy
|
||||||
|
import json
|
||||||
|
import binascii
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
|
|
||||||
|
from .plex_api import API
|
||||||
from .plex_db import PlexDB
|
from .plex_db import PlexDB
|
||||||
from . import kodi_db
|
from . import kodi_db
|
||||||
from .downloadutils import DownloadUtils as DU
|
from .downloadutils import DownloadUtils as DU
|
||||||
|
@ -143,6 +146,8 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
elif method == "System.OnQuit":
|
elif method == "System.OnQuit":
|
||||||
LOG.info('Kodi OnQuit detected - shutting down')
|
LOG.info('Kodi OnQuit detected - shutting down')
|
||||||
app.APP.stop_pkc = True
|
app.APP.stop_pkc = True
|
||||||
|
elif method == 'Other.plugin.video.plexkodiconnect_play_action':
|
||||||
|
self._start_next_episode(data)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _hack_addon_paths_replay_video():
|
def _hack_addon_paths_replay_video():
|
||||||
|
@ -283,6 +288,18 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
json_item.get('type'),
|
json_item.get('type'),
|
||||||
json_item.get('file'))
|
json_item.get('file'))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _start_next_episode(data):
|
||||||
|
"""
|
||||||
|
Used for the add-on Upnext to start playback of the next episode
|
||||||
|
"""
|
||||||
|
LOG.info('Upnext: Start playback of the next episode')
|
||||||
|
play_info = binascii.unhexlify(data[0])
|
||||||
|
play_info = json.loads(play_info)
|
||||||
|
app.APP.player.stop()
|
||||||
|
handle = 'RunPlugin(%s)' % play_info.get('handle')
|
||||||
|
xbmc.executebuiltin(handle.encode('utf-8'))
|
||||||
|
|
||||||
def PlayBackStart(self, data):
|
def PlayBackStart(self, data):
|
||||||
"""
|
"""
|
||||||
Called whenever playback is started. Example data:
|
Called whenever playback is started. Example data:
|
||||||
|
@ -415,6 +432,8 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
status['playmethod'] = item.playmethod
|
status['playmethod'] = item.playmethod
|
||||||
status['playcount'] = item.playcount
|
status['playcount'] = item.playcount
|
||||||
LOG.debug('Set the player state: %s', status)
|
LOG.debug('Set the player state: %s', status)
|
||||||
|
if not app.SYNC.direct_paths:
|
||||||
|
_notify_upnext(item)
|
||||||
|
|
||||||
|
|
||||||
def _playback_cleanup(ended=False):
|
def _playback_cleanup(ended=False):
|
||||||
|
@ -537,6 +556,85 @@ def _clean_file_table():
|
||||||
LOG.debug('Done cleaning up Kodi file table')
|
LOG.debug('Done cleaning up Kodi file table')
|
||||||
|
|
||||||
|
|
||||||
|
def _next_episode(current_api):
|
||||||
|
"""
|
||||||
|
Returns the xml for the next episode after the current one
|
||||||
|
Returns None if something went wrong or there is no next episode
|
||||||
|
"""
|
||||||
|
xml = PF.show_episodes(current_api.grandparent_id())
|
||||||
|
if xml is None:
|
||||||
|
return
|
||||||
|
for counter, episode in enumerate(xml):
|
||||||
|
api = API(episode)
|
||||||
|
if api.plex_id == current_api.plex_id:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
LOG.error('Did not find the episode with Plex id %s for show %s: %s',
|
||||||
|
current_api.plex_id, current_api.grandparent_id(),
|
||||||
|
current_api.grandparent_title())
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
next_api = API(xml[counter + 1])
|
||||||
|
except IndexError:
|
||||||
|
# Was the last episode
|
||||||
|
return
|
||||||
|
return next_api
|
||||||
|
|
||||||
|
|
||||||
|
def _complete_artwork_keys(info):
|
||||||
|
"""
|
||||||
|
Make sure that the minimum set of keys is present in the info dict
|
||||||
|
"""
|
||||||
|
for key in ('tvshow.poster',
|
||||||
|
'tvshow.fanart',
|
||||||
|
'tvshow.landscape',
|
||||||
|
'tvshow.clearart',
|
||||||
|
'tvshow.clearlogo',
|
||||||
|
'thumb'):
|
||||||
|
if key not in info['art']:
|
||||||
|
info['art'][key] = ''
|
||||||
|
|
||||||
|
|
||||||
|
def _notify_upnext(item):
|
||||||
|
"""
|
||||||
|
Signals to the Kodi add-on Upnext that there is another episode after this
|
||||||
|
one.
|
||||||
|
Needed for add-on paths in order to prevent crashes when Upnext does this
|
||||||
|
by itself
|
||||||
|
"""
|
||||||
|
if not item.plex_type == v.PLEX_TYPE_EPISODE:
|
||||||
|
return
|
||||||
|
this_api = API(item.xml)
|
||||||
|
next_api = _next_episode(this_api)
|
||||||
|
if next_api is None:
|
||||||
|
return
|
||||||
|
info = {}
|
||||||
|
for key, api in (('current_episode', this_api),
|
||||||
|
('next_episode', next_api)):
|
||||||
|
info[key] = {
|
||||||
|
'episodeid': api.plex_id,
|
||||||
|
'tvshowid': api.grandparent_id(),
|
||||||
|
'title': api.title(),
|
||||||
|
'showtitle': api.grandparent_title(),
|
||||||
|
'plot': api.plot(),
|
||||||
|
'playcount': api.viewcount(),
|
||||||
|
'season': api.season_number(),
|
||||||
|
'episode': api.index(),
|
||||||
|
'firstaired': api.year(),
|
||||||
|
'rating': api.rating(),
|
||||||
|
'art': api.artwork(kodi_id=api.kodi_id,
|
||||||
|
kodi_type=api.kodi_type,
|
||||||
|
full_artwork=True)
|
||||||
|
}
|
||||||
|
_complete_artwork_keys(info[key])
|
||||||
|
info['play_info'] = {'handle': next_api.path(force_addon=True)}
|
||||||
|
sender = v.ADDON_ID.encode('utf-8')
|
||||||
|
method = 'upnext_data'.encode('utf-8')
|
||||||
|
data = binascii.hexlify(json.dumps(info))
|
||||||
|
data = '\\"[\\"{0}\\"]\\"'.format(data)
|
||||||
|
xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data))
|
||||||
|
|
||||||
|
|
||||||
class ContextMonitor(backgroundthread.KillableThread):
|
class ContextMonitor(backgroundthread.KillableThread):
|
||||||
"""
|
"""
|
||||||
Detect the resume dialog for widgets. Could also be used to detect
|
Detect the resume dialog for widgets. Could also be used to detect
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
import urllib
|
import urllib
|
||||||
|
import copy
|
||||||
|
|
||||||
from ..utils import etree
|
from ..utils import etree
|
||||||
from .. import variables as v, utils
|
from .. import variables as v, utils
|
||||||
|
@ -18,40 +19,37 @@ RECOMMENDED_SCORE_LOWER_BOUND = 7
|
||||||
# )
|
# )
|
||||||
NODE_TYPES = {
|
NODE_TYPES = {
|
||||||
v.PLEX_TYPE_MOVIE: (
|
v.PLEX_TYPE_MOVIE: (
|
||||||
('ondeck',
|
('plex_ondeck',
|
||||||
utils.lang(39500), # "On Deck"
|
utils.lang(39500), # "On Deck"
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/onDeck',
|
'key': '/library/sections/{self.section_id}/onDeck',
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'movies',
|
v.CONTENT_TYPE_MOVIE,
|
||||||
True),
|
True),
|
||||||
('pkc_ondeck',
|
('ondeck',
|
||||||
utils.lang(39502), # "PKC On Deck (faster)"
|
utils.lang(39502), # "PKC On Deck (faster)"
|
||||||
{},
|
{},
|
||||||
'movies',
|
v.CONTENT_TYPE_MOVIE,
|
||||||
False),
|
False),
|
||||||
('recent',
|
('recent',
|
||||||
utils.lang(30174), # "Recently Added"
|
utils.lang(30174), # "Recently Added"
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/recentlyAdded',
|
'key': '/library/sections/{self.section_id}/recentlyAdded',
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'movies',
|
v.CONTENT_TYPE_MOVIE,
|
||||||
False),
|
False),
|
||||||
('all',
|
('all',
|
||||||
'{self.name}', # We're using this section's name
|
'{self.name}', # We're using this section's name
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/all',
|
'key': '/library/sections/{self.section_id}/all',
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'movies',
|
v.CONTENT_TYPE_MOVIE,
|
||||||
False),
|
False),
|
||||||
('recommended',
|
('recommended',
|
||||||
utils.lang(30230), # "Recommended"
|
utils.lang(30230), # "Recommended"
|
||||||
|
@ -59,30 +57,27 @@ NODE_TYPES = {
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': ('/library/sections/{self.section_id}&%s'
|
'key': ('/library/sections/{self.section_id}&%s'
|
||||||
% urllib.urlencode({'sort': 'rating:desc'})),
|
% urllib.urlencode({'sort': 'rating:desc'})),
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'movies',
|
v.CONTENT_TYPE_MOVIE,
|
||||||
False),
|
False),
|
||||||
('genres',
|
('genres',
|
||||||
utils.lang(135), # "Genres"
|
utils.lang(135), # "Genres"
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/genre',
|
'key': '/library/sections/{self.section_id}/genre',
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'movies',
|
v.CONTENT_TYPE_MOVIE,
|
||||||
False),
|
False),
|
||||||
('sets',
|
('sets',
|
||||||
utils.lang(39501), # "Collections"
|
utils.lang(39501), # "Collections"
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/collection',
|
'key': '/library/sections/{self.section_id}/collection',
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'movies',
|
v.CONTENT_TYPE_MOVIE,
|
||||||
False),
|
False),
|
||||||
('random',
|
('random',
|
||||||
utils.lang(30227), # "Random"
|
utils.lang(30227), # "Random"
|
||||||
|
@ -90,20 +85,18 @@ NODE_TYPES = {
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': ('/library/sections/{self.section_id}&%s'
|
'key': ('/library/sections/{self.section_id}&%s'
|
||||||
% urllib.urlencode({'sort': 'random'})),
|
% urllib.urlencode({'sort': 'random'})),
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'movies',
|
v.CONTENT_TYPE_MOVIE,
|
||||||
False),
|
False),
|
||||||
('lastplayed',
|
('lastplayed',
|
||||||
utils.lang(568), # "Last played"
|
utils.lang(568), # "Last played"
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/recentlyViewed',
|
'key': '/library/sections/{self.section_id}/recentlyViewed',
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'movies',
|
v.CONTENT_TYPE_MOVIE,
|
||||||
False),
|
False),
|
||||||
('browse',
|
('browse',
|
||||||
utils.lang(39702), # "Browse by folder"
|
utils.lang(39702), # "Browse by folder"
|
||||||
|
@ -111,19 +104,20 @@ NODE_TYPES = {
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/folder',
|
'key': '/library/sections/{self.section_id}/folder',
|
||||||
'plex_type': '{self.section_type}',
|
'plex_type': '{self.section_type}',
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}',
|
||||||
|
'folder': True
|
||||||
},
|
},
|
||||||
'movies',
|
v.CONTENT_TYPE_MOVIE,
|
||||||
True),
|
True),
|
||||||
('more',
|
('more',
|
||||||
utils.lang(22082), # "More..."
|
utils.lang(22082), # "More..."
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}',
|
'key': '/library/sections/{self.section_id}',
|
||||||
'plex_type': '{self.section_type}',
|
'section_id': '{self.section_id}',
|
||||||
'section_id': '{self.section_id}'
|
'folder': True
|
||||||
},
|
},
|
||||||
'movies',
|
v.CONTENT_TYPE_FILE,
|
||||||
True),
|
True),
|
||||||
),
|
),
|
||||||
###########################################################
|
###########################################################
|
||||||
|
@ -133,30 +127,27 @@ NODE_TYPES = {
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/onDeck',
|
'key': '/library/sections/{self.section_id}/onDeck',
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'episodes',
|
v.CONTENT_TYPE_EPISODE,
|
||||||
True),
|
True),
|
||||||
('recent',
|
('recent',
|
||||||
utils.lang(30174), # "Recently Added"
|
utils.lang(30174), # "Recently Added"
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/recentlyAdded',
|
'key': '/library/sections/{self.section_id}/recentlyAdded',
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'episodes',
|
v.CONTENT_TYPE_EPISODE,
|
||||||
False),
|
False),
|
||||||
('all',
|
('all',
|
||||||
'{self.name}', # We're using this section's name
|
'{self.name}', # We're using this section's name
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/all',
|
'key': '/library/sections/{self.section_id}/all',
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'tvshows',
|
v.CONTENT_TYPE_SHOW,
|
||||||
False),
|
False),
|
||||||
('recommended',
|
('recommended',
|
||||||
utils.lang(30230), # "Recommended"
|
utils.lang(30230), # "Recommended"
|
||||||
|
@ -164,30 +155,27 @@ NODE_TYPES = {
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': ('/library/sections/{self.section_id}&%s'
|
'key': ('/library/sections/{self.section_id}&%s'
|
||||||
% urllib.urlencode({'sort': 'rating:desc'})),
|
% urllib.urlencode({'sort': 'rating:desc'})),
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'tvshows',
|
v.CONTENT_TYPE_SHOW,
|
||||||
False),
|
False),
|
||||||
('genres',
|
('genres',
|
||||||
utils.lang(135), # "Genres"
|
utils.lang(135), # "Genres"
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/genre',
|
'key': '/library/sections/{self.section_id}/genre',
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'tvshows',
|
v.CONTENT_TYPE_SHOW,
|
||||||
False),
|
False),
|
||||||
('sets',
|
('sets',
|
||||||
utils.lang(39501), # "Collections"
|
utils.lang(39501), # "Collections"
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/collection',
|
'key': '/library/sections/{self.section_id}/collection',
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'tvshows',
|
v.CONTENT_TYPE_SHOW,
|
||||||
True), # There are no sets/collections for shows with Kodi
|
True), # There are no sets/collections for shows with Kodi
|
||||||
('random',
|
('random',
|
||||||
utils.lang(30227), # "Random"
|
utils.lang(30227), # "Random"
|
||||||
|
@ -195,10 +183,9 @@ NODE_TYPES = {
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': ('/library/sections/{self.section_id}&%s'
|
'key': ('/library/sections/{self.section_id}&%s'
|
||||||
% urllib.urlencode({'sort': 'random'})),
|
% urllib.urlencode({'sort': 'random'})),
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'tvshows',
|
v.CONTENT_TYPE_SHOW,
|
||||||
False),
|
False),
|
||||||
('lastplayed',
|
('lastplayed',
|
||||||
utils.lang(568), # "Last played"
|
utils.lang(568), # "Last played"
|
||||||
|
@ -206,30 +193,29 @@ NODE_TYPES = {
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': ('/library/sections/{self.section_id}/recentlyViewed&%s'
|
'key': ('/library/sections/{self.section_id}/recentlyViewed&%s'
|
||||||
% urllib.urlencode({'type': v.PLEX_TYPE_NUMBER_FROM_PLEX_TYPE[v.PLEX_TYPE_EPISODE]})),
|
% urllib.urlencode({'type': v.PLEX_TYPE_NUMBER_FROM_PLEX_TYPE[v.PLEX_TYPE_EPISODE]})),
|
||||||
'plex_type': '{self.section_type}',
|
|
||||||
'section_id': '{self.section_id}'
|
'section_id': '{self.section_id}'
|
||||||
},
|
},
|
||||||
'episodes',
|
v.CONTENT_TYPE_EPISODE,
|
||||||
False),
|
False),
|
||||||
('browse',
|
('browse',
|
||||||
utils.lang(39702), # "Browse by folder"
|
utils.lang(39702), # "Browse by folder"
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}/folder',
|
'key': '/library/sections/{self.section_id}/folder',
|
||||||
'plex_type': '{self.section_type}',
|
'section_id': '{self.section_id}',
|
||||||
'section_id': '{self.section_id}'
|
'folder': True
|
||||||
},
|
},
|
||||||
'episodes',
|
v.CONTENT_TYPE_EPISODE,
|
||||||
True),
|
True),
|
||||||
('more',
|
('more',
|
||||||
utils.lang(22082), # "More..."
|
utils.lang(22082), # "More..."
|
||||||
{
|
{
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': '/library/sections/{self.section_id}',
|
'key': '/library/sections/{self.section_id}',
|
||||||
'plex_type': '{self.section_type}',
|
'section_id': '{self.section_id}',
|
||||||
'section_id': '{self.section_id}'
|
'folder': True
|
||||||
},
|
},
|
||||||
'episodes',
|
v.CONTENT_TYPE_FILE,
|
||||||
True),
|
True),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -239,9 +225,19 @@ def node_pms(section, node_name, args):
|
||||||
"""
|
"""
|
||||||
Nodes where the logic resides with the PMS - we're NOT building an
|
Nodes where the logic resides with the PMS - we're NOT building an
|
||||||
xml that filters and sorts, but point to PKC add-on path
|
xml that filters and sorts, but point to PKC add-on path
|
||||||
|
|
||||||
|
Be sure to set args['folder'] = True if the listing is a folder and does
|
||||||
|
not contain playable elements like movies, episodes or tracks
|
||||||
"""
|
"""
|
||||||
xml = etree.Element('node', attrib={'order': unicode(section.order),
|
if 'folder' in args:
|
||||||
'type': 'folder'})
|
args = copy.deepcopy(args)
|
||||||
|
args.pop('folder')
|
||||||
|
folder = True
|
||||||
|
else:
|
||||||
|
folder = False
|
||||||
|
xml = etree.Element('node',
|
||||||
|
attrib={'order': unicode(section.order),
|
||||||
|
'type': 'folder' if folder else 'filter'})
|
||||||
etree.SubElement(xml, 'label').text = node_name
|
etree.SubElement(xml, 'label').text = node_name
|
||||||
etree.SubElement(xml, 'icon').text = ICON_PATH
|
etree.SubElement(xml, 'icon').text = ICON_PATH
|
||||||
etree.SubElement(xml, 'content').text = section.content
|
etree.SubElement(xml, 'content').text = section.content
|
||||||
|
@ -249,7 +245,7 @@ def node_pms(section, node_name, args):
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
|
|
||||||
def node_pkc_ondeck(section, node_name):
|
def node_ondeck(section, node_name):
|
||||||
"""
|
"""
|
||||||
For movies only - returns in-progress movies sorted by last played
|
For movies only - returns in-progress movies sorted by last played
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -132,7 +132,7 @@ class Section(object):
|
||||||
@section_type.setter
|
@section_type.setter
|
||||||
def section_type(self, value):
|
def section_type(self, value):
|
||||||
self._section_type = value
|
self._section_type = value
|
||||||
self.content = v.MEDIATYPE_FROM_PLEX_TYPE[value]
|
self.content = v.CONTENT_FROM_PLEX_TYPE[value]
|
||||||
# Default values whether we sync or not based on the Plex type
|
# Default values whether we sync or not based on the Plex type
|
||||||
if value == v.PLEX_TYPE_PHOTO:
|
if value == v.PLEX_TYPE_PHOTO:
|
||||||
self.sync_to_kodi = False
|
self.sync_to_kodi = False
|
||||||
|
@ -239,29 +239,41 @@ class Section(object):
|
||||||
raise RuntimeError('Index not initialized')
|
raise RuntimeError('Index not initialized')
|
||||||
# Main list entry for this section - which will show the different
|
# Main list entry for this section - which will show the different
|
||||||
# nodes as "submenus" once the user navigates into this section
|
# nodes as "submenus" once the user navigates into this section
|
||||||
args = {
|
|
||||||
'mode': 'browseplex',
|
|
||||||
'key': '/library/sections/%s' % self.section_id,
|
|
||||||
'plex_type': self.section_type,
|
|
||||||
'section_id': unicode(self.section_id)
|
|
||||||
}
|
|
||||||
if not self.sync_to_kodi:
|
|
||||||
args['synched'] = 'false'
|
|
||||||
addon_index = self.addon_path(args)
|
|
||||||
if self.sync_to_kodi and self.section_type in v.PLEX_VIDEOTYPES:
|
if self.sync_to_kodi and self.section_type in v.PLEX_VIDEOTYPES:
|
||||||
|
# Node showing a menu for this section
|
||||||
|
args = {
|
||||||
|
'mode': 'show_section',
|
||||||
|
'section_index': self.index
|
||||||
|
}
|
||||||
|
index = utils.extend_url('plugin://%s' % v.ADDON_ID, args)
|
||||||
|
# Node directly displaying all content
|
||||||
path = 'library://video/Plex-{0}/{0}_all.xml'
|
path = 'library://video/Plex-{0}/{0}_all.xml'
|
||||||
path = path.format(self.section_id)
|
path = path.format(self.section_id)
|
||||||
index = 'library://video/Plex-%s' % self.section_id
|
|
||||||
else:
|
else:
|
||||||
# No xmls to link to - let's show the listings on the fly
|
# Node showing a menu for this section
|
||||||
index = addon_index
|
args = {
|
||||||
args['key'] = '/library/sections/%s/all' % self.section_id
|
'mode': 'browseplex',
|
||||||
|
'key': '/library/sections/%s' % self.section_id,
|
||||||
|
'section_id': unicode(self.section_id)
|
||||||
|
}
|
||||||
|
if not self.sync_to_kodi:
|
||||||
|
args['synched'] = 'false'
|
||||||
|
# No library xmls to speed things up
|
||||||
|
# Immediately show the PMS options for this section
|
||||||
|
index = self.addon_path(args)
|
||||||
|
# Node directly displaying all content
|
||||||
|
args = {
|
||||||
|
'mode': 'browseplex',
|
||||||
|
'key': '/library/sections/%s/all' % self.section_id,
|
||||||
|
'section_id': unicode(self.section_id)
|
||||||
|
}
|
||||||
|
if not self.sync_to_kodi:
|
||||||
|
args['synched'] = 'false'
|
||||||
path = self.addon_path(args)
|
path = self.addon_path(args)
|
||||||
# .index will list all possible nodes for this library
|
|
||||||
utils.window('%s.index' % self.node, value=index)
|
utils.window('%s.index' % self.node, value=index)
|
||||||
utils.window('%s.title' % self.node, value=self.name)
|
utils.window('%s.title' % self.node, value=self.name)
|
||||||
utils.window('%s.type' % self.node, value=self.content)
|
utils.window('%s.type' % self.node, value=self.content)
|
||||||
utils.window('%s.content' % self.node, value=path)
|
utils.window('%s.content' % self.node, value=index)
|
||||||
# .path leads to all elements of this library
|
# .path leads to all elements of this library
|
||||||
if self.section_type in v.PLEX_VIDEOTYPES:
|
if self.section_type in v.PLEX_VIDEOTYPES:
|
||||||
utils.window('%s.path' % self.node,
|
utils.window('%s.path' % self.node,
|
||||||
|
@ -274,8 +286,6 @@ class Section(object):
|
||||||
utils.window('%s.path' % self.node,
|
utils.window('%s.path' % self.node,
|
||||||
value='ActivateWindow(pictures,%s,return)' % path)
|
value='ActivateWindow(pictures,%s,return)' % path)
|
||||||
utils.window('%s.id' % self.node, value=str(self.section_id))
|
utils.window('%s.id' % self.node, value=str(self.section_id))
|
||||||
# To let the user navigate into this node when selecting widgets
|
|
||||||
utils.window('%s.addon_index' % self.node, value=addon_index)
|
|
||||||
if not self.sync_to_kodi:
|
if not self.sync_to_kodi:
|
||||||
self.remove_files_from_kodi()
|
self.remove_files_from_kodi()
|
||||||
return
|
return
|
||||||
|
@ -312,18 +322,22 @@ class Section(object):
|
||||||
def _build_node(self, node_type, node_name, args, content, pms_node):
|
def _build_node(self, node_type, node_name, args, content, pms_node):
|
||||||
self.content = content
|
self.content = content
|
||||||
node_name = node_name.format(self=self)
|
node_name = node_name.format(self=self)
|
||||||
xml_name = '%s_%s.xml' % (self.section_id, node_type)
|
if pms_node:
|
||||||
path = path_ops.path.join(self.path, xml_name)
|
# Do NOT write a Kodi video library xml - can't use type="filter"
|
||||||
if not path_ops.exists(path):
|
# to point back to plugin://plugin.video.plexkodiconnect
|
||||||
if pms_node:
|
xml = nodes.node_pms(self, node_name, args)
|
||||||
# Even the xml will point back to the PKC add-on
|
args.pop('folder', None)
|
||||||
xml = nodes.node_pms(self, node_name, args)
|
path = self.addon_path(args)
|
||||||
else:
|
else:
|
||||||
|
# Write a Kodi video library xml
|
||||||
|
xml_name = '%s_%s.xml' % (self.section_id, node_type)
|
||||||
|
path = path_ops.path.join(self.path, xml_name)
|
||||||
|
if not path_ops.exists(path):
|
||||||
# Let's use Kodi's logic to sort/filter the Kodi library
|
# Let's use Kodi's logic to sort/filter the Kodi library
|
||||||
xml = getattr(nodes, 'node_%s' % node_type)(self, node_name)
|
xml = getattr(nodes, 'node_%s' % node_type)(self, node_name)
|
||||||
self._write_xml(xml, xml_name)
|
self._write_xml(xml, xml_name)
|
||||||
|
path = 'library://video/Plex-%s/%s' % (self.section_id, xml_name)
|
||||||
self.order += 1
|
self.order += 1
|
||||||
path = 'library://video/Plex-%s/%s' % (self.section_id, xml_name)
|
|
||||||
self._window_node(path, node_name, node_type, pms_node)
|
self._window_node(path, node_name, node_type, pms_node)
|
||||||
|
|
||||||
def _write_xml(self, xml, xml_name):
|
def _write_xml(self, xml, xml_name):
|
||||||
|
@ -337,7 +351,7 @@ class Section(object):
|
||||||
LOG.debug('Creating smart playlist for section %s: %s',
|
LOG.debug('Creating smart playlist for section %s: %s',
|
||||||
self.name, self.playlist_path)
|
self.name, self.playlist_path)
|
||||||
xml = etree.Element('smartplaylist',
|
xml = etree.Element('smartplaylist',
|
||||||
attrib={'type': v.MEDIATYPE_FROM_PLEX_TYPE[self.section_type]})
|
attrib={'type': v.CONTENT_FROM_PLEX_TYPE[self.section_type]})
|
||||||
etree.SubElement(xml, 'name').text = self.name
|
etree.SubElement(xml, 'name').text = self.name
|
||||||
etree.SubElement(xml, 'match').text = 'all'
|
etree.SubElement(xml, 'match').text = 'all'
|
||||||
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
|
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
|
||||||
|
@ -647,7 +661,6 @@ def _clear_window_vars(index):
|
||||||
utils.window('%s.content' % node, clear=True)
|
utils.window('%s.content' % node, clear=True)
|
||||||
utils.window('%s.path' % node, clear=True)
|
utils.window('%s.path' % node, clear=True)
|
||||||
utils.window('%s.id' % node, clear=True)
|
utils.window('%s.id' % node, clear=True)
|
||||||
utils.window('%s.addon_index' % node, clear=True)
|
|
||||||
# Just clear everything here, ignore the plex_type
|
# Just clear everything here, ignore the plex_type
|
||||||
for typus in (x[0] for y in nodes.NODE_TYPES.values() for x in y):
|
for typus in (x[0] for y in nodes.NODE_TYPES.values() for x in y):
|
||||||
for kind in WINDOW_ARGS:
|
for kind in WINDOW_ARGS:
|
||||||
|
|
|
@ -51,4 +51,10 @@ def check_migration():
|
||||||
plexdb.cursor.execute('DROP INDEX IF EXISTS ix_playlists_3')
|
plexdb.cursor.execute('DROP INDEX IF EXISTS ix_playlists_3')
|
||||||
# Index will be automatically recreated on next PKC startup
|
# Index will be automatically recreated on next PKC startup
|
||||||
|
|
||||||
|
if not utils.compare_version(last_migration, '2.8.9'):
|
||||||
|
LOG.info('Migrating to version 2.8.8')
|
||||||
|
from .library_sync import sections
|
||||||
|
sections.clear_window_vars()
|
||||||
|
sections.delete_videonode_files()
|
||||||
|
|
||||||
utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
|
utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
|
||||||
|
|
|
@ -234,13 +234,14 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
||||||
playqueue.clear()
|
playqueue.clear()
|
||||||
if plex_type != v.PLEX_TYPE_CLIP:
|
if plex_type != v.PLEX_TYPE_CLIP:
|
||||||
# Post to the PMS to create a playqueue - in any case due to Companion
|
# Post to the PMS to create a playqueue - in any case due to Companion
|
||||||
|
section_uuid = xml.attrib.get('librarySectionUUID')
|
||||||
xml = PF.init_plex_playqueue(plex_id,
|
xml = PF.init_plex_playqueue(plex_id,
|
||||||
xml.attrib.get('librarySectionUUID'),
|
section_uuid,
|
||||||
mediatype=plex_type,
|
mediatype=plex_type,
|
||||||
trailers=trailers)
|
trailers=trailers)
|
||||||
if xml is None:
|
if xml is None:
|
||||||
LOG.error('Could not get a playqueue xml for plex id %s, UUID %s',
|
LOG.error('Could not get a playqueue xml for plex id %s, UUID %s',
|
||||||
plex_id, xml.attrib.get('librarySectionUUID'))
|
plex_id, section_uuid)
|
||||||
# "Play error"
|
# "Play error"
|
||||||
utils.dialog('notification',
|
utils.dialog('notification',
|
||||||
utils.lang(29999),
|
utils.lang(29999),
|
||||||
|
|
|
@ -71,6 +71,13 @@ class Base(object):
|
||||||
"""
|
"""
|
||||||
return cast(int, self.xml.get('ratingKey'))
|
return cast(int, self.xml.get('ratingKey'))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fast_key(self):
|
||||||
|
"""
|
||||||
|
Returns the 'fastKey' as unicode or None
|
||||||
|
"""
|
||||||
|
return self.xml.get('fastKey')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def plex_type(self):
|
def plex_type(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -91,9 +91,10 @@ class File(object):
|
||||||
key = '/library/sections/%s/%s' % (section_id, key)
|
key = '/library/sections/%s/%s' % (section_id, key)
|
||||||
params = {
|
params = {
|
||||||
'mode': 'browseplex',
|
'mode': 'browseplex',
|
||||||
'key': key,
|
'key': key
|
||||||
'plex_type': plex_type or self.plex_type
|
|
||||||
}
|
}
|
||||||
|
if plex_type or self.plex_type:
|
||||||
|
params['plex_type'] = plex_type or self.plex_type
|
||||||
if not synched:
|
if not synched:
|
||||||
# No item to be found in the Kodi DB
|
# No item to be found in the Kodi DB
|
||||||
params['synched'] = 'false'
|
params['synched'] = 'false'
|
||||||
|
|
|
@ -33,7 +33,8 @@ def update_playqueue_from_PMS(playqueue,
|
||||||
playqueue_id=None,
|
playqueue_id=None,
|
||||||
repeat=None,
|
repeat=None,
|
||||||
offset=None,
|
offset=None,
|
||||||
transient_token=None):
|
transient_token=None,
|
||||||
|
start_plex_id=None):
|
||||||
"""
|
"""
|
||||||
Completely updates the Kodi playqueue with the new Plex playqueue. Pass
|
Completely updates the Kodi playqueue with the new Plex playqueue. Pass
|
||||||
in playqueue_id if we need to fetch a new playqueue
|
in playqueue_id if we need to fetch a new playqueue
|
||||||
|
@ -42,7 +43,8 @@ def update_playqueue_from_PMS(playqueue,
|
||||||
offset = time offset in Plextime (milliseconds)
|
offset = time offset in Plextime (milliseconds)
|
||||||
"""
|
"""
|
||||||
LOG.info('New playqueue %s received from Plex companion with offset '
|
LOG.info('New playqueue %s received from Plex companion with offset '
|
||||||
'%s, repeat %s', playqueue_id, offset, repeat)
|
'%s, repeat %s, start_plex_id %s',
|
||||||
|
playqueue_id, offset, repeat, start_plex_id)
|
||||||
# Safe transient token from being deleted
|
# Safe transient token from being deleted
|
||||||
if transient_token is None:
|
if transient_token is None:
|
||||||
transient_token = playqueue.plex_transient_token
|
transient_token = playqueue.plex_transient_token
|
||||||
|
@ -61,7 +63,10 @@ def update_playqueue_from_PMS(playqueue,
|
||||||
return
|
return
|
||||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||||
playqueue.plex_transient_token = transient_token
|
playqueue.plex_transient_token = transient_token
|
||||||
playback.play_xml(playqueue, xml, offset)
|
playback.play_xml(playqueue,
|
||||||
|
xml,
|
||||||
|
offset=offset,
|
||||||
|
start_plex_id=start_plex_id)
|
||||||
|
|
||||||
|
|
||||||
class PlexCompanion(backgroundthread.KillableThread):
|
class PlexCompanion(backgroundthread.KillableThread):
|
||||||
|
@ -154,11 +159,15 @@ class PlexCompanion(backgroundthread.KillableThread):
|
||||||
api = API(xml[0])
|
api = API(xml[0])
|
||||||
playqueue = PQ.get_playqueue_from_type(
|
playqueue = PQ.get_playqueue_from_type(
|
||||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type])
|
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type])
|
||||||
|
key = data.get('key')
|
||||||
|
if key:
|
||||||
|
_, key, _ = PF.ParseContainerKey(key)
|
||||||
update_playqueue_from_PMS(playqueue,
|
update_playqueue_from_PMS(playqueue,
|
||||||
playqueue_id=container_key,
|
playqueue_id=container_key,
|
||||||
repeat=query.get('repeat'),
|
repeat=query.get('repeat'),
|
||||||
offset=data.get('offset'),
|
offset=data.get('offset'),
|
||||||
transient_token=data.get('token'))
|
transient_token=data.get('token'),
|
||||||
|
key=key)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _process_streams(data):
|
def _process_streams(data):
|
||||||
|
|
|
@ -1029,3 +1029,15 @@ def GetUserArtworkURL(username):
|
||||||
url = user.thumb
|
url = user.thumb
|
||||||
LOG.debug("Avatar url for user %s is: %s", username, url)
|
LOG.debug("Avatar url for user %s is: %s", username, url)
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def show_episodes(plex_id):
|
||||||
|
"""
|
||||||
|
Returns all episodes for the tv show with plex_id
|
||||||
|
"""
|
||||||
|
url = "{server}/library/metadata/%s/allLeaves" % plex_id
|
||||||
|
arguments = {
|
||||||
|
'checkFiles': 0,
|
||||||
|
'skipRefresh': 1,
|
||||||
|
}
|
||||||
|
return DownloadChunks(utils.extend_url(url, arguments))
|
||||||
|
|
|
@ -165,6 +165,7 @@ PLEX_TYPE_VIDEO = 'video'
|
||||||
PLEX_TYPE_MOVIE = 'movie'
|
PLEX_TYPE_MOVIE = 'movie'
|
||||||
PLEX_TYPE_CLIP = 'clip' # e.g. trailers
|
PLEX_TYPE_CLIP = 'clip' # e.g. trailers
|
||||||
PLEX_TYPE_SET = 'collection' # sets/collections
|
PLEX_TYPE_SET = 'collection' # sets/collections
|
||||||
|
PLEX_TYPE_GENRE = 'genre'
|
||||||
PLEX_TYPE_MIXED = 'mixed'
|
PLEX_TYPE_MIXED = 'mixed'
|
||||||
|
|
||||||
PLEX_TYPE_EPISODE = 'episode'
|
PLEX_TYPE_EPISODE = 'episode'
|
||||||
|
@ -201,7 +202,7 @@ KODI_PLAYLIST_TYPE_FROM_PLEX_PLAYLIST_TYPE = {
|
||||||
KODI_TYPE_VIDEO = 'video'
|
KODI_TYPE_VIDEO = 'video'
|
||||||
KODI_TYPE_MOVIE = 'movie'
|
KODI_TYPE_MOVIE = 'movie'
|
||||||
KODI_TYPE_SET = 'set' # for movie sets of several movies
|
KODI_TYPE_SET = 'set' # for movie sets of several movies
|
||||||
KODI_TYPE_CLIP = 'clip' # e.g. trailers
|
KODI_TYPE_CLIP = 'video' # e.g. trailers
|
||||||
|
|
||||||
KODI_TYPE_EPISODE = 'episode'
|
KODI_TYPE_EPISODE = 'episode'
|
||||||
KODI_TYPE_SEASON = 'season'
|
KODI_TYPE_SEASON = 'season'
|
||||||
|
@ -216,6 +217,24 @@ KODI_TYPE_MUSICVIDEO = 'musicvideo'
|
||||||
KODI_TYPE_PHOTO = 'photo'
|
KODI_TYPE_PHOTO = 'photo'
|
||||||
|
|
||||||
KODI_TYPE_PLAYLIST = 'playlist'
|
KODI_TYPE_PLAYLIST = 'playlist'
|
||||||
|
KODI_TYPE_GENRE = 'genre'
|
||||||
|
|
||||||
|
# Kodi content types, primarily used for xbmcplugin.setContent()
|
||||||
|
CONTENT_TYPE_MOVIE = 'movies'
|
||||||
|
CONTENT_TYPE_SHOW = 'tvshows'
|
||||||
|
CONTENT_TYPE_SEASON = 'seasons'
|
||||||
|
CONTENT_TYPE_EPISODE = 'episodes'
|
||||||
|
CONTENT_TYPE_ARTIST = 'artists'
|
||||||
|
CONTENT_TYPE_ALBUM = 'albums'
|
||||||
|
CONTENT_TYPE_SONG = 'songs'
|
||||||
|
CONTENT_TYPE_CLIP = 'movies'
|
||||||
|
CONTENT_TYPE_SET = 'sets'
|
||||||
|
CONTENT_TYPE_PHOTO = 'photos'
|
||||||
|
CONTENT_TYPE_GENRE = 'genres'
|
||||||
|
CONTENT_TYPE_VIDEO = 'videos'
|
||||||
|
CONTENT_TYPE_PLAYLIST = 'playlists'
|
||||||
|
CONTENT_TYPE_FILE = 'files'
|
||||||
|
|
||||||
|
|
||||||
KODI_VIDEOTYPES = (
|
KODI_VIDEOTYPES = (
|
||||||
KODI_TYPE_VIDEO,
|
KODI_TYPE_VIDEO,
|
||||||
|
@ -306,7 +325,6 @@ PLEX_TYPE_FROM_KODI_TYPE = {
|
||||||
KODI_TYPE_EPISODE: PLEX_TYPE_EPISODE,
|
KODI_TYPE_EPISODE: PLEX_TYPE_EPISODE,
|
||||||
KODI_TYPE_SEASON: PLEX_TYPE_SEASON,
|
KODI_TYPE_SEASON: PLEX_TYPE_SEASON,
|
||||||
KODI_TYPE_SHOW: PLEX_TYPE_SHOW,
|
KODI_TYPE_SHOW: PLEX_TYPE_SHOW,
|
||||||
KODI_TYPE_CLIP: PLEX_TYPE_CLIP,
|
|
||||||
KODI_TYPE_ARTIST: PLEX_TYPE_ARTIST,
|
KODI_TYPE_ARTIST: PLEX_TYPE_ARTIST,
|
||||||
KODI_TYPE_ALBUM: PLEX_TYPE_ALBUM,
|
KODI_TYPE_ALBUM: PLEX_TYPE_ALBUM,
|
||||||
KODI_TYPE_SONG: PLEX_TYPE_SONG,
|
KODI_TYPE_SONG: PLEX_TYPE_SONG,
|
||||||
|
@ -418,24 +436,29 @@ PLEX_TYPE_NUMBER_FROM_PLEX_TYPE = {
|
||||||
PLEX_TYPE_ALBUM: 9,
|
PLEX_TYPE_ALBUM: 9,
|
||||||
PLEX_TYPE_SONG: 10,
|
PLEX_TYPE_SONG: 10,
|
||||||
PLEX_TYPE_CLIP: 12,
|
PLEX_TYPE_CLIP: 12,
|
||||||
'playlist': 15,
|
PLEX_TYPE_PLAYLIST: 15,
|
||||||
PLEX_TYPE_SET: 18
|
PLEX_TYPE_SET: 18
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# To be used with e.g. Kodi Widgets
|
# To be used with e.g. Kodi Widgets
|
||||||
MEDIATYPE_FROM_PLEX_TYPE = {
|
CONTENT_FROM_PLEX_TYPE = {
|
||||||
PLEX_TYPE_MOVIE: 'movies',
|
PLEX_TYPE_MOVIE: CONTENT_TYPE_MOVIE,
|
||||||
PLEX_TYPE_SHOW: 'tvshows',
|
PLEX_TYPE_SHOW: CONTENT_TYPE_SHOW,
|
||||||
PLEX_TYPE_SEASON: 'tvshows',
|
PLEX_TYPE_SEASON: CONTENT_TYPE_SEASON,
|
||||||
PLEX_TYPE_EPISODE: 'episodes',
|
PLEX_TYPE_EPISODE: CONTENT_TYPE_EPISODE,
|
||||||
PLEX_TYPE_ARTIST: 'artists',
|
PLEX_TYPE_ARTIST: CONTENT_TYPE_ARTIST,
|
||||||
PLEX_TYPE_ALBUM: 'albumbs',
|
PLEX_TYPE_ALBUM: CONTENT_TYPE_ALBUM,
|
||||||
PLEX_TYPE_SONG: 'songs',
|
PLEX_TYPE_SONG: CONTENT_TYPE_SONG,
|
||||||
PLEX_TYPE_CLIP: 'videos',
|
PLEX_TYPE_CLIP: CONTENT_TYPE_CLIP,
|
||||||
PLEX_TYPE_SET: 'movies',
|
PLEX_TYPE_SET: CONTENT_TYPE_SET,
|
||||||
PLEX_TYPE_PHOTO: 'photos',
|
PLEX_TYPE_PHOTO: CONTENT_TYPE_PHOTO,
|
||||||
'mixed': 'tvshows',
|
PLEX_TYPE_GENRE: CONTENT_TYPE_GENRE,
|
||||||
|
PLEX_TYPE_VIDEO: CONTENT_TYPE_VIDEO,
|
||||||
|
PLEX_TYPE_PLAYLIST: CONTENT_TYPE_PLAYLIST,
|
||||||
|
PLEX_TYPE_CHANNEL: CONTENT_TYPE_FILE,
|
||||||
|
'mixed': CONTENT_TYPE_SHOW,
|
||||||
|
None: CONTENT_TYPE_FILE
|
||||||
}
|
}
|
||||||
|
|
||||||
KODI_TO_PLEX_ARTWORK = {
|
KODI_TO_PLEX_ARTWORK = {
|
||||||
|
|
|
@ -79,24 +79,49 @@ def generate_item(api):
|
||||||
|
|
||||||
def _generate_folder(api):
|
def _generate_folder(api):
|
||||||
'''Generates "folder"/"directory" items that user can further navigate'''
|
'''Generates "folder"/"directory" items that user can further navigate'''
|
||||||
art = api.artwork()
|
typus = ''
|
||||||
return {
|
if api.plex_type == v.PLEX_TYPE_GENRE:
|
||||||
'title': api.title(),
|
# Unfortunately, 'genre' is not yet supported by Kodi
|
||||||
'label': api.title(),
|
# typus = v.KODI_TYPE_GENRE
|
||||||
'file': api.directory_path(section_id=SECTION_ID,
|
pass
|
||||||
plex_type=PLEX_TYPE,
|
elif api.plex_type == v.PLEX_TYPE_SHOW:
|
||||||
old_key=KEY),
|
typus = v.KODI_TYPE_SHOW
|
||||||
'icon': 'DefaultFolder.png',
|
elif api.plex_type == v.PLEX_TYPE_SEASON:
|
||||||
'art': {
|
typus = v.KODI_TYPE_SEASON
|
||||||
'thumb': art['thumb'] if 'thumb' in art else
|
elif api.plex_type == v.PLEX_TYPE_ARTIST:
|
||||||
(art['poster'] if 'poster' in art else
|
typus = v.KODI_TYPE_ARTIST
|
||||||
'special://home/addons/%s/icon.png' % v.ADDON_ID),
|
elif api.plex_type == v.PLEX_TYPE_ALBUM:
|
||||||
'fanart': art['fanart'] if 'fanart' in art else
|
typus = v.KODI_TYPE_ALBUM
|
||||||
'special://home/addons/%s/fanart.jpg' % v.ADDON_ID},
|
elif api.fast_key and '?collection=' in api.fast_key:
|
||||||
'isFolder': True,
|
typus = v.KODI_TYPE_SET
|
||||||
'type': '',
|
if typus and typus != v.KODI_TYPE_SET:
|
||||||
'IsPlayable': 'false',
|
content = _generate_content(api)
|
||||||
}
|
content['type'] = typus
|
||||||
|
content['file'] = api.directory_path(section_id=SECTION_ID,
|
||||||
|
plex_type=PLEX_TYPE,
|
||||||
|
old_key=KEY)
|
||||||
|
content['isFolder'] = True
|
||||||
|
content['IsPlayable'] = 'false'
|
||||||
|
return content
|
||||||
|
else:
|
||||||
|
art = api.artwork()
|
||||||
|
return {
|
||||||
|
'title': api.title(),
|
||||||
|
'label': api.title(),
|
||||||
|
'file': api.directory_path(section_id=SECTION_ID,
|
||||||
|
plex_type=PLEX_TYPE,
|
||||||
|
old_key=KEY),
|
||||||
|
'icon': 'DefaultFolder.png',
|
||||||
|
'art': {
|
||||||
|
'thumb': art['thumb'] if 'thumb' in art else
|
||||||
|
(art['poster'] if 'poster' in art else
|
||||||
|
'special://home/addons/%s/icon.png' % v.ADDON_ID),
|
||||||
|
'fanart': art['fanart'] if 'fanart' in art else
|
||||||
|
'special://home/addons/%s/fanart.jpg' % v.ADDON_ID},
|
||||||
|
'isFolder': True,
|
||||||
|
'type': typus,
|
||||||
|
'IsPlayable': 'false',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _generate_content(api):
|
def _generate_content(api):
|
||||||
|
|
Loading…
Add table
Reference in a new issue