Refactor direct and add-on paths. Enables use of Plex music playlists synched to Kodi

This commit is contained in:
croneter 2020-02-26 18:10:15 +01:00
parent 6c9870f581
commit 25f972f30f
9 changed files with 136 additions and 140 deletions

View file

@ -142,7 +142,7 @@ class ContextMenu(object):
v.KODI_PLAYLIST_TYPE_FROM_KODI_TYPE[self.kodi_type])
playqueue.clear()
app.PLAYSTATE.context_menu_play = True
handle = self.api.path(force_addon=True)
handle = self.api.fullpath(force_addon=True)[0]
handle = 'RunPlugin(%s)' % handle
xbmc.executebuiltin(handle.encode('utf-8'))

View file

@ -5,7 +5,7 @@ from logging import getLogger
from .common import ItemBase
from ..plex_api import API
from .. import app, variables as v, plex_functions as PF, utils
from .. import app, variables as v, plex_functions as PF
LOG = getLogger('PLEX.movies')
@ -36,35 +36,12 @@ class Movie(ItemBase):
update_item = False
kodi_id = self.kodidb.new_movie_id()
# GET THE FILE AND PATH #####
do_indirect = not app.SYNC.direct_paths
if app.SYNC.direct_paths:
# Direct paths is set the Kodi way
playurl = api.file_path(force_first_media=True)
if playurl is None:
# Something went wrong, trying to use non-direct paths
do_indirect = True
else:
playurl = api.validate_playurl(playurl, api.plex_type)
if playurl is None:
return False
if '\\' in playurl:
# Local path
filename = playurl.rsplit("\\", 1)[1]
else:
# Network share
filename = playurl.rsplit("/", 1)[1]
path = utils.rreplace(playurl, filename, "", 1)
kodi_pathid = self.kodidb.add_path(path,
content='movies',
scraper='metadata.local')
if do_indirect:
# Set plugin path and media flags using real filename
filename = api.file_name(force_first_media=True)
path = 'plugin://%s.movies/' % v.ADDON_ID
filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
% (path, plex_id, v.PLEX_TYPE_MOVIE, filename))
playurl = filename
fullpath, path, filename = api.fullpath()
if app.SYNC.direct_paths and not fullpath.startswith('http'):
kodi_pathid = self.kodidb.add_path(path,
content='movies',
scraper='metadata.local')
else:
kodi_pathid = self.kodidb.get_path(path)
if update_item:
@ -150,7 +127,7 @@ class Movie(ItemBase):
api.list_to_string(api.studios()),
api.trailer(),
api.list_to_string(api.countries()),
playurl,
fullpath,
kodi_pathid,
api.premiere_date(),
api.userrating())

View file

@ -7,7 +7,7 @@ from .common import ItemBase
from ..plex_api import API
from ..plex_db import PlexDB, PLEXDB_LOCK
from ..kodi_db import KodiMusicDB, KODIDB_LOCK
from .. import plex_functions as PF, db, timing, app, variables as v, utils
from .. import plex_functions as PF, db, timing, app, variables as v
LOG = getLogger('PLEX.music')
@ -520,33 +520,7 @@ class Song(MusicMixin, ItemBase):
if entry.tag == 'Mood':
moods.append(entry.attrib['tag'])
mood = api.list_to_string(moods)
# GET THE FILE AND PATH #####
do_indirect = not app.SYNC.direct_paths
if app.SYNC.direct_paths:
# Direct paths is set the Kodi way
playurl = api.file_path(force_first_media=True)
if playurl is None:
# Something went wrong, trying to use non-direct paths
do_indirect = True
else:
playurl = api.validate_playurl(playurl, api.plex_type)
if playurl is None:
return False
if "\\" in playurl:
# Local path
filename = playurl.rsplit("\\", 1)[1]
else:
# Network share
filename = playurl.rsplit("/", 1)[1]
path = utils.rreplace(playurl, filename, "", 1)
if do_indirect:
# Plex works a bit differently
path = "%s%s" % (app.CONN.server, xml[0][0].get('key'))
path = api.attach_plex_token_to_url(path)
filename = path.rsplit('/', 1)[1]
path = path.replace(filename, '')
_, path, filename = api.fullpath()
# UPDATE THE SONG #####
if update_item:
LOG.info("UPDATE song plex_id: %s - %s", plex_id, title)

View file

@ -5,7 +5,7 @@ from logging import getLogger
from .common import ItemBase, process_path
from ..plex_api import API
from .. import plex_functions as PF, app, variables as v, utils
from .. import plex_functions as PF, app, variables as v
LOG = getLogger('PLEX.tvshows')
@ -420,43 +420,22 @@ class Episode(TvShowMixin, ItemBase):
return
parent_id = season['kodi_id'] if season else None
# GET THE FILE AND PATH #####
do_indirect = not app.SYNC.direct_paths
if app.SYNC.direct_paths:
playurl = api.file_path(force_first_media=True)
if playurl is None:
do_indirect = True
else:
playurl = api.validate_playurl(playurl, v.PLEX_TYPE_EPISODE)
if "\\" in playurl:
# Local path
filename = playurl.rsplit("\\", 1)[1]
else:
# Network share
filename = playurl.rsplit("/", 1)[1]
path = utils.rreplace(playurl, filename, "", 1)
parent_path_id = self.kodidb.parent_path_id(path)
kodi_pathid = self.kodidb.add_path(path,
id_parent_path=parent_path_id)
if do_indirect:
# Set plugin path - do NOT use "intermediate" paths for the show
# as with direct paths!
filename = api.file_name(force_first_media=True)
path = 'plugin://%s.tvshows/%s/' % (v.ADDON_ID, api.show_id())
filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
% (path, plex_id, v.PLEX_TYPE_EPISODE, filename))
playurl = filename
fullpath, path, filename = api.fullpath()
if app.SYNC.direct_paths and not fullpath.startswith('http'):
parent_path_id = self.kodidb.parent_path_id(path)
kodi_pathid = self.kodidb.add_path(path,
id_parent_path=parent_path_id)
else:
# Root path tvshows/ already saved in Kodi DB
kodi_pathid = self.kodidb.add_path(path)
if not app.SYNC.direct_paths:
# need to set a 2nd file entry for a path without plex show id
# This fixes e.g. context menu and widgets working as they
# should
# A dirty hack, really
path_2 = 'plugin://%s.tvshows/' % v.ADDON_ID
# filename_2 is exactly the same as filename
# so WITH plex show id!
kodi_pathid_2 = self.kodidb.add_path(path_2)
# need to set a 2nd file entry for a path without plex show id
# This fixes e.g. context menu and widgets working as they
# should
# A dirty hack, really
path_2 = 'plugin://%s.tvshows/' % v.ADDON_ID
# filename_2 is exactly the same as filename
# so WITH plex show id!
kodi_pathid_2 = self.kodidb.add_path(path_2)
# UPDATE THE EPISODE #####
if update_item:
@ -512,7 +491,7 @@ class Episode(TvShowMixin, ItemBase):
api.title(),
airs_before_season,
airs_before_episode,
playurl,
fullpath,
kodi_pathid,
uniqueid,
kodi_fileid, # and NOT kodi_fileid_2
@ -594,7 +573,7 @@ class Episode(TvShowMixin, ItemBase):
grandparent_id,
airs_before_season,
airs_before_episode,
playurl,
fullpath,
kodi_pathid,
uniqueid,
parent_id,

View file

@ -575,7 +575,7 @@ def _notify_upnext(item):
full_artwork=True)
}
_complete_artwork_keys(info[key])
info['play_info'] = {'handle': next_api.path(force_addon=True)}
info['play_info'] = {'handle': next_api.fullpath(force_addon=True)[0]}
sender = v.ADDON_ID.encode('utf-8')
method = 'upnext_data'.encode('utf-8')
data = binascii.hexlify(json.dumps(info))

View file

@ -350,7 +350,7 @@ def _prep_playlist_stack(xml, resume):
api.part = part
if kodi_id is None:
# Need to redirect again to PKC to conclude playback
path = api.path(force_addon=True, force_first_media=True)
path = api.fullpath(force_addon=True)[0]
# Using different paths than the ones saved in the Kodi DB
# fixes Kodi immediately resuming the video if one restarts
# the same video again after playback

View file

@ -117,21 +117,8 @@ def _write_playlist_to_file(playlist, xml):
Returns None or raises PlaylistError
"""
text = '#EXTCPlayListM3U::M3U\n'
for element in xml:
api = API(element)
if api.plex_type == v.PLEX_TYPE_EPISODE:
if api.season_number() is not None and api.index() is not None:
text += ('#EXTINF:%s,%s S%.2dE%.2d - %s\n%s\n'
% (api.runtime(), api.show_title(),
api.season_number(), api.index(),
api.title(), api.path()))
else:
# Only append the TV show name
text += ('#EXTINF:%s,%s - %s\n%s\n'
% (api.runtime(), api.show_title(), api.title(), api.path()))
else:
text += ('#EXTINF:%s,%s\n%s\n'
% (api.runtime(), api.title(), api.path()))
for xml_element in xml:
text += _m3u_element(xml_element)
text += '\n'
text = text.encode(v.M3U_ENCODING, 'ignore')
try:
@ -142,3 +129,45 @@ def _write_playlist_to_file(playlist, xml):
LOG.error('Error message %s: %s', err.errno, err.strerror)
raise PlaylistError('Cannot write Kodi playlist to path for %s'
% playlist)
def _m3u_element(xml_element):
api = API(xml_element)
if api.plex_type == v.PLEX_TYPE_EPISODE:
if api.season_number() is not None and api.index() is not None:
return '#EXTINF:{},{} S{:2d}E{:2d} - {}\n{}\n'.format(
api.runtime(),
api.show_title(),
api.season_number(),
api.index(),
api.title(),
api.fullpath(force_addon=True)[0])
else:
# Only append the TV show name
return '#EXTINF:{},{} - {}\n{}\n'.format(
api.runtime(),
api.show_title(),
api.title(),
api.fullpath(force_addon=True)[0])
elif api.plex_type == v.PLEX_TYPE_SONG:
if api.index() is not None:
return '#EXTINF:{},{:02d}. {} - {}\n{}\n'.format(
api.runtime(),
api.index(),
api.grandparent_title(),
api.title(),
api.fullpath(force_first_media=True,
omit_check=True)[0])
else:
return '#EXTINF:{},{} - {}\n{}\n'.format(
api.runtime(),
api.grandparent_title(),
api.title(),
api.fullpath(force_first_media=True,
omit_check=True)[0])
else:
return '#EXTINF:{},{}\n{}\n'.format(
api.runtime(),
api.title(),
api.fullpath(force_first_media=True,
omit_check=True)[0])

View file

@ -41,43 +41,80 @@ def _transcode_image_path(key, AuthToken, path, width, height):
class File(object):
def path(self, force_first_media=True, force_addon=False,
direct_paths=None):
def fullpath(self, force_first_media=True, force_addon=False,
direct_paths=None, omit_check=False, force_check=False):
"""
Returns a "fully qualified path": add-on paths or direct paths
depending on the current settings. Will NOT valide the playurl
Returns unicode or None if something went wrong.
Returns a "fully qualified path" add-on paths or direct paths
depending on the current settings as the tupple
(fullpath, path, filename)
as unicode. Add-on paths are returned as a fallback. Returns None
if something went wrong.
Pass direct_path=True if you're calling from another Plex python
instance - because otherwise direct paths will evaluate to False!
firce_first_media=False prompts the user to choose which version of the
media should be returned, if several are present
force_addon=True will always return the add-on path
direct_path=True if you're calling from another Plex python
instance - because otherwise direct paths will
evaluate to False!
"""
direct_paths = direct_paths or app.SYNC.direct_paths
filename = self.file_path(force_first_media=force_first_media)
direct_paths = app.SYNC.direct_paths if direct_paths is None \
else direct_paths
if (not direct_paths or force_addon or
self.plex_type == v.PLEX_TYPE_CLIP):
if filename and '/' in filename:
filename = filename.rsplit('/', 1)
elif filename:
filename = filename.rsplit('\\', 1)
try:
filename = filename[1]
except (TypeError, IndexError):
filename = None
# Set plugin path and media flags using real filename
if self.plex_type == v.PLEX_TYPE_SONG:
return self._music_addon_paths(force_first_media)
if self.plex_type == v.PLEX_TYPE_EPISODE:
# need to include the plex show id in the path
path = ('plugin://plugin.video.plexkodiconnect.tvshows/%s/'
% self.grandparent_id())
else:
path = 'plugin://%s/' % v.ADDON_TYPE[self.plex_type]
path = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
# Filename in Kodi will end with actual filename - hopefully
# this is useful for other add-ons
filename = self.file_path(force_first_media=force_first_media)
try:
if '/' in filename:
filename = filename.rsplit('/', 1)[1]
else:
filename = filename.rsplit('\\', 1)[1]
except (TypeError, IndexError):
return
entirepath = ('%s?mode=play&plex_id=%s&plex_type=%s&filename=%s'
% (path, self.plex_id, self.plex_type, filename))
# For Kodi DB, we need to safe the ENTIRE path for filenames
filename = entirepath
else:
# Direct paths is set the Kodi way
path = self.validate_playurl(filename,
self.plex_type,
omit_check=True)
return path
entirepath = self.validate_playurl(
self.file_path(force_first_media=force_first_media),
self.plex_type,
force_check=force_check,
omit_check=omit_check)
try:
if '/' in entirepath:
filename = entirepath.rsplit('/', 1)[1]
else:
filename = entirepath.rsplit('\\', 1)[1]
except (TypeError, IndexError):
# Fallback to add-on paths
return self.fullpath(force_first_media=force_first_media,
force_addon=True)
path = utils.rreplace(entirepath, filename, "", 1)
return entirepath, path, filename
def _music_addon_paths(self, force_first_media):
"""
For songs only. Normal add-on paths plugin://... don't work with the
Kodi music DB, hence use a "direct" url to the music file on the PMS.
"""
if self.mediastream is None and force_first_media is False:
if self.mediastream_number() is None:
return
streamno = 0 if force_first_media else self.mediastream
entirepath = "%s%s" % (app.CONN.server,
self.xml[streamno][self.part].get('key'))
entirepath = self.attach_plex_token_to_url(entirepath)
path, filename = entirepath.rsplit('/', 1)
return entirepath, path + '/', filename
def directory_path(self, section_id=None, plex_type=None, old_key=None,
synched=True):

View file

@ -223,7 +223,7 @@ def _generate_content(api):
elif plex_type == v.PLEX_TYPE_PHOTO:
url = api.get_picture_path()
else:
url = api.path()
url = api.fullpath(force_first_media=True)[0]
if not api.kodi_id and plex_type == v.PLEX_TYPE_EPISODE:
# Hack - Item is not synched to the Kodi database
# We CANNOT use paths that show up in the Kodi paths table!