Refactor direct and add-on paths. Enables use of Plex music playlists synched to Kodi
This commit is contained in:
parent
6c9870f581
commit
25f972f30f
9 changed files with 136 additions and 140 deletions
|
@ -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'))
|
||||
|
||||
|
|
|
@ -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)
|
||||
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')
|
||||
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
|
||||
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())
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,35 +420,14 @@ 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)
|
||||
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)
|
||||
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
|
||||
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
|
||||
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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'
|
||||
% (path, self.plex_id, self.plex_type, filename))
|
||||
# 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:
|
||||
# Direct paths is set the Kodi way
|
||||
path = self.validate_playurl(filename,
|
||||
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:
|
||||
entirepath = self.validate_playurl(
|
||||
self.file_path(force_first_media=force_first_media),
|
||||
self.plex_type,
|
||||
omit_check=True)
|
||||
return path
|
||||
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):
|
||||
|
|
|
@ -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!
|
||||
|
|
Loading…
Reference in a new issue