Merge pull request #1626 from croneter/py3-streams
Large refactoring of playlist and playqueue code
This commit is contained in:
commit
bce51224f2
15 changed files with 112 additions and 93 deletions
|
@ -4,19 +4,13 @@ import sqlite3
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from . import variables as v, app
|
from . import variables as v, app
|
||||||
|
from .exceptions import LockedDatabase
|
||||||
|
|
||||||
DB_WRITE_ATTEMPTS = 100
|
DB_WRITE_ATTEMPTS = 100
|
||||||
DB_WRITE_ATTEMPTS_TIMEOUT = 1 # in seconds
|
DB_WRITE_ATTEMPTS_TIMEOUT = 1 # in seconds
|
||||||
DB_CONNECTION_TIMEOUT = 10
|
DB_CONNECTION_TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
class LockedDatabase(Exception):
|
|
||||||
"""
|
|
||||||
Dedicated class to make sure we're not silently catching locked DBs.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def catch_operationalerrors(method):
|
def catch_operationalerrors(method):
|
||||||
"""
|
"""
|
||||||
sqlite.OperationalError is raised immediately if another DB connection
|
sqlite.OperationalError is raised immediately if another DB connection
|
||||||
|
|
31
resources/lib/exceptions.py
Normal file
31
resources/lib/exceptions.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistError(Exception):
|
||||||
|
"""
|
||||||
|
Exception for our playlist constructs
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LockedDatabase(Exception):
|
||||||
|
"""
|
||||||
|
Dedicated class to make sure we're not silently catching locked DBs.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SubtitleError(Exception):
|
||||||
|
"""
|
||||||
|
Exceptions relating to subtitles
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessingNotDone(Exception):
|
||||||
|
"""
|
||||||
|
Exception to detect whether we've completed our sync and did not have to
|
||||||
|
abort or suspend.
|
||||||
|
"""
|
||||||
|
pass
|
|
@ -19,6 +19,7 @@ from .downloadutils import DownloadUtils as DU
|
||||||
from . import utils, timing, plex_functions as PF
|
from . import utils, timing, plex_functions as PF
|
||||||
from . import json_rpc as js, playqueue as PQ, playlist_func as PL
|
from . import json_rpc as js, playqueue as PQ, playlist_func as PL
|
||||||
from . import backgroundthread, app, variables as v
|
from . import backgroundthread, app, variables as v
|
||||||
|
from . import exceptions
|
||||||
|
|
||||||
LOG = getLogger('PLEX.kodimonitor')
|
LOG = getLogger('PLEX.kodimonitor')
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._already_slept = False
|
self._already_slept = False
|
||||||
self._switch_to_plex_streams = None
|
self._switched_to_plex_streams = True
|
||||||
xbmc.Monitor.__init__(self)
|
xbmc.Monitor.__init__(self)
|
||||||
for playerid in app.PLAYSTATE.player_states:
|
for playerid in app.PLAYSTATE.player_states:
|
||||||
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
|
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
|
||||||
|
@ -67,7 +68,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
self.PlayBackStart(data)
|
self.PlayBackStart(data)
|
||||||
elif method == 'Player.OnAVChange':
|
elif method == 'Player.OnAVChange':
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
self.on_av_change()
|
self._on_av_change(data)
|
||||||
elif method == "Player.OnStop":
|
elif method == "Player.OnStop":
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
_playback_cleanup(ended=data.get('end'))
|
_playback_cleanup(ended=data.get('end'))
|
||||||
|
@ -180,7 +181,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
try:
|
try:
|
||||||
for i, item in enumerate(items):
|
for i, item in enumerate(items):
|
||||||
PL.add_item_to_plex_playqueue(playqueue, i + 1, kodi_item=item)
|
PL.add_item_to_plex_playqueue(playqueue, i + 1, kodi_item=item)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.info('Could not build Plex playlist for: %s', items)
|
LOG.info('Could not build Plex playlist for: %s', items)
|
||||||
|
|
||||||
def _json_item(self, playerid):
|
def _json_item(self, playerid):
|
||||||
|
@ -317,7 +318,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
item = PL.init_plex_playqueue(playqueue, plex_id=plex_id)
|
item = PL.init_plex_playqueue(playqueue, plex_id=plex_id)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.info('Could not initialize the Plex playlist')
|
LOG.info('Could not initialize the Plex playlist')
|
||||||
return
|
return
|
||||||
item.file = path
|
item.file = path
|
||||||
|
@ -342,8 +343,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
container_key = '/library/metadata/%s' % plex_id
|
container_key = '/library/metadata/%s' % plex_id
|
||||||
# Mechanik for Plex skip intro feature
|
# Mechanik for Plex skip intro feature
|
||||||
if utils.settings('enableSkipIntro') == 'true':
|
if utils.settings('enableSkipIntro') == 'true':
|
||||||
api = API(item.xml)
|
status['intro_markers'] = item.api.intro_markers()
|
||||||
status['intro_markers'] = api.intro_markers()
|
|
||||||
# Remember the currently playing item
|
# Remember the currently playing item
|
||||||
app.PLAYSTATE.item = item
|
app.PLAYSTATE.item = item
|
||||||
# Remember that this player has been active
|
# Remember that this player has been active
|
||||||
|
@ -358,32 +358,36 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
status['plex_type'] = plex_type
|
status['plex_type'] = plex_type
|
||||||
status['playmethod'] = item.playmethod
|
status['playmethod'] = item.playmethod
|
||||||
status['playcount'] = item.playcount
|
status['playcount'] = item.playcount
|
||||||
try:
|
|
||||||
status['external_player'] = app.APP.player.isExternalPlayer() == 1
|
status['external_player'] = app.APP.player.isExternalPlayer() == 1
|
||||||
except AttributeError:
|
|
||||||
# Kodi version < 17
|
|
||||||
pass
|
|
||||||
LOG.debug('Set the player state: %s', status)
|
LOG.debug('Set the player state: %s', status)
|
||||||
|
|
||||||
# Workaround for the Kodi add-on Up Next
|
# Workaround for the Kodi add-on Up Next
|
||||||
if not app.SYNC.direct_paths:
|
if not app.SYNC.direct_paths:
|
||||||
_notify_upnext(item)
|
_notify_upnext(item)
|
||||||
self._switch_to_plex_streams = item
|
self._switched_to_plex_streams = False
|
||||||
|
|
||||||
def on_av_change(self):
|
def _on_av_change(self, data):
|
||||||
"""
|
"""
|
||||||
Will be called when Kodi has a video, audio or subtitle stream. Also
|
Will be called when Kodi has a video, audio or subtitle stream. Also
|
||||||
happens when the stream changes.
|
happens when the stream changes.
|
||||||
|
|
||||||
|
Example data as returned by Kodi:
|
||||||
|
{'item': {'id': 5, 'type': 'movie'},
|
||||||
|
'player': {'playerid': 1, 'speed': 1}}
|
||||||
"""
|
"""
|
||||||
if self._switch_to_plex_streams is not None:
|
if not self._switched_to_plex_streams:
|
||||||
self.switch_to_plex_streams(self._switch_to_plex_streams)
|
self.switch_to_plex_streams()
|
||||||
self._switch_to_plex_streams = None
|
self._switched_to_plex_streams = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def switch_to_plex_streams(item):
|
def switch_to_plex_streams():
|
||||||
"""
|
"""
|
||||||
Override Kodi audio and subtitle streams with Plex PMS' selection
|
Override Kodi audio and subtitle streams with Plex PMS' selection
|
||||||
"""
|
"""
|
||||||
|
item = app.PLAYSTATE.item
|
||||||
|
if item is None:
|
||||||
|
# Player might've quit
|
||||||
|
return
|
||||||
for typus in ('audio', 'subtitle'):
|
for typus in ('audio', 'subtitle'):
|
||||||
try:
|
try:
|
||||||
plex_index, language_tag = item.active_plex_stream_index(typus)
|
plex_index, language_tag = item.active_plex_stream_index(typus)
|
||||||
|
@ -587,11 +591,10 @@ def _next_episode(current_api):
|
||||||
current_api.grandparent_title())
|
current_api.grandparent_title())
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
next_api = API(xml[counter + 1])
|
return API(xml[counter + 1])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# Was the last episode
|
# Was the last episode
|
||||||
return
|
pass
|
||||||
return next_api
|
|
||||||
|
|
||||||
|
|
||||||
def _complete_artwork_keys(info):
|
def _complete_artwork_keys(info):
|
||||||
|
@ -617,7 +620,7 @@ def _notify_upnext(item):
|
||||||
"""
|
"""
|
||||||
if not item.plex_type == v.PLEX_TYPE_EPISODE:
|
if not item.plex_type == v.PLEX_TYPE_EPISODE:
|
||||||
return
|
return
|
||||||
this_api = API(item.xml)
|
this_api = item.api
|
||||||
next_api = _next_episode(this_api)
|
next_api = _next_episode(this_api)
|
||||||
if next_api is None:
|
if next_api is None:
|
||||||
return
|
return
|
||||||
|
|
|
@ -5,6 +5,7 @@ from . import additional_metadata_tmdb
|
||||||
from ..plex_db import PlexDB
|
from ..plex_db import PlexDB
|
||||||
from .. import backgroundthread, utils
|
from .. import backgroundthread, utils
|
||||||
from .. import variables as v, app
|
from .. import variables as v, app
|
||||||
|
from ..exceptions import ProcessingNotDone
|
||||||
|
|
||||||
|
|
||||||
logger = getLogger('PLEX.sync.metadata')
|
logger = getLogger('PLEX.sync.metadata')
|
||||||
|
@ -22,12 +23,6 @@ SUPPORTED_METADATA = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ProcessingNotDone(Exception):
|
|
||||||
"""Exception to detect whether we've completed our sync and did not have to
|
|
||||||
abort or suspend."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def processing_is_activated(item_getter):
|
def processing_is_activated(item_getter):
|
||||||
"""Checks the PKC settings whether processing is even activated."""
|
"""Checks the PKC settings whether processing is even activated."""
|
||||||
if item_getter == 'missing_fanart':
|
if item_getter == 'missing_fanart':
|
||||||
|
|
|
@ -15,6 +15,7 @@ from .kodi_db import KodiVideoDB
|
||||||
from . import plex_functions as PF, playlist_func as PL, playqueue as PQ
|
from . import plex_functions as PF, playlist_func as PL, playqueue as PQ
|
||||||
from . import json_rpc as js, variables as v, utils, transfer
|
from . import json_rpc as js, variables as v, utils, transfer
|
||||||
from . import playback_decision, app
|
from . import playback_decision, app
|
||||||
|
from . import exceptions
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playback')
|
LOG = getLogger('PLEX.playback')
|
||||||
|
@ -191,7 +192,7 @@ def _playback_init(plex_id, plex_type, playqueue, pos, resume):
|
||||||
# Special case - we already got a filled Kodi playqueue
|
# Special case - we already got a filled Kodi playqueue
|
||||||
try:
|
try:
|
||||||
_init_existing_kodi_playlist(playqueue, pos)
|
_init_existing_kodi_playlist(playqueue, pos)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.error('Playback_init for existing Kodi playlist failed')
|
LOG.error('Playback_init for existing Kodi playlist failed')
|
||||||
_ensure_resolve(abort=True)
|
_ensure_resolve(abort=True)
|
||||||
return
|
return
|
||||||
|
@ -311,7 +312,7 @@ def _init_existing_kodi_playlist(playqueue, pos):
|
||||||
kodi_items = js.playlist_get_items(playqueue.playlistid)
|
kodi_items = js.playlist_get_items(playqueue.playlistid)
|
||||||
if not kodi_items:
|
if not kodi_items:
|
||||||
LOG.error('No Kodi items returned')
|
LOG.error('No Kodi items returned')
|
||||||
raise PL.PlaylistError('No Kodi items returned')
|
raise exceptions.PlaylistError('No Kodi items returned')
|
||||||
item = PL.init_plex_playqueue(playqueue, kodi_item=kodi_items[pos])
|
item = PL.init_plex_playqueue(playqueue, kodi_item=kodi_items[pos])
|
||||||
item.force_transcode = app.PLAYSTATE.force_transcode
|
item.force_transcode = app.PLAYSTATE.force_transcode
|
||||||
# playqueue.py will add the rest - this will likely put the PMS under
|
# playqueue.py will add the rest - this will likely put the PMS under
|
||||||
|
@ -443,27 +444,26 @@ def _conclude_playback(playqueue, pos):
|
||||||
"""
|
"""
|
||||||
LOG.debug('Concluding playback for playqueue position %s', pos)
|
LOG.debug('Concluding playback for playqueue position %s', pos)
|
||||||
item = playqueue.items[pos]
|
item = playqueue.items[pos]
|
||||||
api = API(item.xml)
|
if item.api.mediastream_number() is None:
|
||||||
if api.mediastream_number() is None:
|
|
||||||
# E.g. user could choose between several media streams and cancelled
|
# E.g. user could choose between several media streams and cancelled
|
||||||
LOG.debug('Did not get a mediastream_number')
|
LOG.debug('Did not get a mediastream_number')
|
||||||
_ensure_resolve()
|
_ensure_resolve()
|
||||||
return
|
return
|
||||||
api.part = item.part or 0
|
item.api.part = item.part or 0
|
||||||
playback_decision.set_pkc_playmethod(api, item)
|
playback_decision.set_pkc_playmethod(item.api, item)
|
||||||
if not playback_decision.audio_subtitle_prefs(api, item):
|
if not playback_decision.audio_subtitle_prefs(item.api, item):
|
||||||
LOG.info('Did not set audio subtitle prefs, aborting silently')
|
LOG.info('Did not set audio subtitle prefs, aborting silently')
|
||||||
_ensure_resolve()
|
_ensure_resolve()
|
||||||
return
|
return
|
||||||
playback_decision.set_playurl(api, item)
|
playback_decision.set_playurl(item.api, item)
|
||||||
if not item.file:
|
if not item.file:
|
||||||
LOG.info('Did not get a playurl, aborting playback silently')
|
LOG.info('Did not get a playurl, aborting playback silently')
|
||||||
_ensure_resolve()
|
_ensure_resolve()
|
||||||
return
|
return
|
||||||
listitem = api.listitem(listitem=transfer.PKCListItem, resume=False)
|
listitem = item.api.listitem(listitem=transfer.PKCListItem, resume=False)
|
||||||
listitem.setPath(item.file)
|
listitem.setPath(item.file)
|
||||||
if item.playmethod != v.PLAYBACK_METHOD_DIRECT_PATH:
|
if item.playmethod != v.PLAYBACK_METHOD_DIRECT_PATH:
|
||||||
listitem.setSubtitles(api.cache_external_subs())
|
listitem.setSubtitles(item.api.cache_external_subs())
|
||||||
transfer.send(listitem)
|
transfer.send(listitem)
|
||||||
LOG.debug('Done concluding playback')
|
LOG.debug('Done concluding playback')
|
||||||
|
|
||||||
|
|
|
@ -14,19 +14,13 @@ from . import utils
|
||||||
from . import json_rpc as js
|
from . import json_rpc as js
|
||||||
from . import variables as v
|
from . import variables as v
|
||||||
from . import app
|
from . import app
|
||||||
|
from .exceptions import PlaylistError
|
||||||
from .subtitles import accessible_plex_subtitles
|
from .subtitles import accessible_plex_subtitles
|
||||||
|
|
||||||
|
|
||||||
LOG = getLogger('PLEX.playlist_func')
|
LOG = getLogger('PLEX.playlist_func')
|
||||||
|
|
||||||
|
|
||||||
class PlaylistError(Exception):
|
|
||||||
"""
|
|
||||||
Exception for our playlist constructs
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Playqueue_Object(object):
|
class Playqueue_Object(object):
|
||||||
"""
|
"""
|
||||||
PKC object to represent PMS playQueues and Kodi playlist for queueing
|
PKC object to represent PMS playQueues and Kodi playlist for queueing
|
||||||
|
@ -150,13 +144,14 @@ class PlaylistItem(object):
|
||||||
file = None [str] Path to the item's file. STRING!!
|
file = None [str] Path to the item's file. STRING!!
|
||||||
uri = None [str] PMS path to item; will be auto-set with plex_id
|
uri = None [str] PMS path to item; will be auto-set with plex_id
|
||||||
guid = None [str] Weird Plex guid
|
guid = None [str] Weird Plex guid
|
||||||
xml = None [etree] XML from PMS, 1 lvl below <MediaContainer>
|
api = None [API] API of xml 1 lvl below <MediaContainer>
|
||||||
playmethod = None [str] either 'DirectPath', 'DirectStream', 'Transcode'
|
playmethod = None [str] either 'DirectPath', 'DirectStream', 'Transcode'
|
||||||
playcount = None [int] how many times the item has already been played
|
playcount = None [int] how many times the item has already been played
|
||||||
offset = None [int] the item's view offset UPON START in Plex time
|
offset = None [int] the item's view offset UPON START in Plex time
|
||||||
part = 0 [int] part number if Plex video consists of mult. parts
|
part = 0 [int] part number if Plex video consists of mult. parts
|
||||||
force_transcode [bool] defaults to False
|
force_transcode [bool] defaults to False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.id = None
|
self.id = None
|
||||||
self._plex_id = None
|
self._plex_id = None
|
||||||
|
@ -166,7 +161,7 @@ class PlaylistItem(object):
|
||||||
self.file = None
|
self.file = None
|
||||||
self._uri = None
|
self._uri = None
|
||||||
self.guid = None
|
self.guid = None
|
||||||
self.xml = None
|
self.api = None
|
||||||
self.playmethod = None
|
self.playmethod = None
|
||||||
self.playcount = None
|
self.playcount = None
|
||||||
self.offset = None
|
self.offset = None
|
||||||
|
@ -224,10 +219,10 @@ class PlaylistItem(object):
|
||||||
count = 0
|
count = 0
|
||||||
if kodi_stream_index == -1:
|
if kodi_stream_index == -1:
|
||||||
# Kodi telling us "it's the last one"
|
# Kodi telling us "it's the last one"
|
||||||
iterator = list(reversed(self.xml[0][self.part]))
|
iterator = list(reversed(self.api.plex_media_streams()))
|
||||||
kodi_stream_index = 0
|
kodi_stream_index = 0
|
||||||
else:
|
else:
|
||||||
iterator = self.xml[0][self.part]
|
iterator = self.api.plex_media_streams()
|
||||||
# Kodi indexes differently than Plex
|
# Kodi indexes differently than Plex
|
||||||
for stream in iterator:
|
for stream in iterator:
|
||||||
if (stream.get('streamType') == stream_type and
|
if (stream.get('streamType') == stream_type and
|
||||||
|
@ -268,7 +263,7 @@ class PlaylistItem(object):
|
||||||
Returns None if no stream has been selected
|
Returns None if no stream has been selected
|
||||||
"""
|
"""
|
||||||
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
|
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
|
||||||
for stream in self.xml[0][self.part]:
|
for stream in self.api.plex_media_streams():
|
||||||
if stream.get('streamType') == stream_type \
|
if stream.get('streamType') == stream_type \
|
||||||
and stream.get('selected') == '1':
|
and stream.get('selected') == '1':
|
||||||
return (utils.cast(int, stream.get('id')),
|
return (utils.cast(int, stream.get('id')),
|
||||||
|
@ -285,9 +280,9 @@ class PlaylistItem(object):
|
||||||
if stream_type == '3':
|
if stream_type == '3':
|
||||||
streams = accessible_plex_subtitles(self.playmethod,
|
streams = accessible_plex_subtitles(self.playmethod,
|
||||||
self.file,
|
self.file,
|
||||||
self.xml[0][self.part])
|
self.api.plex_media_streams())
|
||||||
else:
|
else:
|
||||||
streams = [x for x in self.xml[0][self.part]
|
streams = [x for x in self.api.plex_media_streams()
|
||||||
if x.get('streamType') == stream_type]
|
if x.get('streamType') == stream_type]
|
||||||
return streams
|
return streams
|
||||||
|
|
||||||
|
@ -413,14 +408,15 @@ def playlist_item_from_xml(xml_video_element, kodi_id=None, kodi_type=None):
|
||||||
item.guid = api.guid_html_escaped()
|
item.guid = api.guid_html_escaped()
|
||||||
item.playcount = api.viewcount()
|
item.playcount = api.viewcount()
|
||||||
item.offset = api.resume_point()
|
item.offset = api.resume_point()
|
||||||
item.xml = xml_video_element
|
item.api = api
|
||||||
LOG.debug('Created new playlist item from xml: %s', item)
|
LOG.debug('Created new playlist item from xml: %s', item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
def _get_playListVersion_from_xml(playlist, xml):
|
def _update_playlist_version(playlist, xml):
|
||||||
"""
|
"""
|
||||||
Takes a PMS xml as input to overwrite the playlist version (e.g. Plex
|
Takes a PMS xml (one level above the xml-depth where we're usually applying
|
||||||
|
API()) as input to overwrite the playlist version (e.g. Plex
|
||||||
playQueueVersion).
|
playQueueVersion).
|
||||||
|
|
||||||
Raises PlaylistError if unsuccessful
|
Raises PlaylistError if unsuccessful
|
||||||
|
@ -603,7 +599,7 @@ def add_item_to_plex_playqueue(playlist, pos, plex_id=None, kodi_item=None):
|
||||||
raise PlaylistError('Could not add item %s to playlist %s'
|
raise PlaylistError('Could not add item %s to playlist %s'
|
||||||
% (kodi_item, playlist))
|
% (kodi_item, playlist))
|
||||||
api = API(xml[-1])
|
api = API(xml[-1])
|
||||||
item.xml = xml[-1]
|
item.api = api
|
||||||
item.id = api.item_id()
|
item.id = api.item_id()
|
||||||
item.guid = api.guid_html_escaped()
|
item.guid = api.guid_html_escaped()
|
||||||
item.offset = api.resume_point()
|
item.offset = api.resume_point()
|
||||||
|
@ -611,7 +607,7 @@ def add_item_to_plex_playqueue(playlist, pos, plex_id=None, kodi_item=None):
|
||||||
playlist.items.append(item)
|
playlist.items.append(item)
|
||||||
if pos == len(playlist.items) - 1:
|
if pos == len(playlist.items) - 1:
|
||||||
# Item was added at the end
|
# Item was added at the end
|
||||||
_get_playListVersion_from_xml(playlist, xml)
|
_update_playlist_version(playlist, xml)
|
||||||
else:
|
else:
|
||||||
# Move the new item to the correct position
|
# Move the new item to the correct position
|
||||||
move_playlist_item(playlist,
|
move_playlist_item(playlist,
|
||||||
|
@ -655,7 +651,7 @@ def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
|
||||||
{'id': kodi_id, 'type': kodi_type, 'file': file})
|
{'id': kodi_id, 'type': kodi_type, 'file': file})
|
||||||
if item.plex_id is not None:
|
if item.plex_id is not None:
|
||||||
xml = PF.GetPlexMetadata(item.plex_id)
|
xml = PF.GetPlexMetadata(item.plex_id)
|
||||||
item.xml = xml[-1]
|
item.api = API(xml[-1])
|
||||||
playlist.items.insert(pos, item)
|
playlist.items.insert(pos, item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
@ -679,9 +675,10 @@ def move_playlist_item(playlist, before_pos, after_pos):
|
||||||
playlist.id,
|
playlist.id,
|
||||||
playlist.items[before_pos].id,
|
playlist.items[before_pos].id,
|
||||||
playlist.items[after_pos - 1].id)
|
playlist.items[after_pos - 1].id)
|
||||||
# We need to increment the playlistVersion
|
# Tell the PMS that we're moving items around
|
||||||
_get_playListVersion_from_xml(
|
xml = DU().downloadUrl(url, action_type="PUT")
|
||||||
playlist, DU().downloadUrl(url, action_type="PUT"))
|
# We need to increment the playlist version for communicating with the PMS
|
||||||
|
_update_playlist_version(playlist, xml)
|
||||||
# Move our item's position in our internal playlist
|
# Move our item's position in our internal playlist
|
||||||
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
|
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
|
||||||
LOG.debug('Done moving for %s', playlist)
|
LOG.debug('Done moving for %s', playlist)
|
||||||
|
@ -729,7 +726,7 @@ def delete_playlist_item_from_PMS(playlist, pos):
|
||||||
playlist.repeat),
|
playlist.repeat),
|
||||||
action_type="DELETE")
|
action_type="DELETE")
|
||||||
del playlist.items[pos]
|
del playlist.items[pos]
|
||||||
_get_playListVersion_from_xml(playlist, xml)
|
_update_playlist_version(playlist, xml)
|
||||||
|
|
||||||
|
|
||||||
# Functions operating on the Kodi playlist objects ##########
|
# Functions operating on the Kodi playlist objects ##########
|
||||||
|
|
|
@ -14,13 +14,13 @@
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from sqlite3 import OperationalError
|
from sqlite3 import OperationalError
|
||||||
|
|
||||||
from .common import Playlist, PlaylistError, PlaylistObserver, \
|
from .common import Playlist, PlaylistObserver, kodi_playlist_hash
|
||||||
kodi_playlist_hash
|
|
||||||
from . import pms, db, kodi_pl, plex_pl
|
from . import pms, db, kodi_pl, plex_pl
|
||||||
|
|
||||||
from ..watchdog import events
|
from ..watchdog import events
|
||||||
from ..plex_api import API
|
from ..plex_api import API
|
||||||
from .. import utils, path_ops, variables as v, app
|
from .. import utils, path_ops, variables as v, app
|
||||||
|
from ..exceptions import PlaylistError
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playlists')
|
LOG = getLogger('PLEX.playlists')
|
||||||
|
|
|
@ -11,6 +11,8 @@ from ..watchdog.observers import Observer
|
||||||
from ..watchdog.utils.bricks import OrderedSetQueue
|
from ..watchdog.utils.bricks import OrderedSetQueue
|
||||||
|
|
||||||
from .. import path_ops, variables as v, app
|
from .. import path_ops, variables as v, app
|
||||||
|
from ..exceptions import PlaylistError
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playlists.common')
|
LOG = getLogger('PLEX.playlists.common')
|
||||||
|
|
||||||
|
@ -19,13 +21,6 @@ SIMILAR_EVENTS = (events.EVENT_TYPE_CREATED, events.EVENT_TYPE_MODIFIED)
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
class PlaylistError(Exception):
|
|
||||||
"""
|
|
||||||
The one main exception thrown if anything goes awry
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Playlist(object):
|
class Playlist(object):
|
||||||
"""
|
"""
|
||||||
Class representing a synced Playlist with info for both Kodi and Plex.
|
Class representing a synced Playlist with info for both Kodi and Plex.
|
||||||
|
|
|
@ -6,10 +6,12 @@ module
|
||||||
"""
|
"""
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from .common import Playlist, PlaylistError
|
from .common import Playlist
|
||||||
from ..plex_db import PlexDB
|
from ..plex_db import PlexDB
|
||||||
from ..kodi_db import kodiid_from_filename
|
from ..kodi_db import kodiid_from_filename
|
||||||
from .. import utils, variables as v
|
from .. import utils, variables as v
|
||||||
|
from ..exceptions import PlaylistError
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playlists.db')
|
LOG = getLogger('PLEX.playlists.db')
|
||||||
|
|
||||||
|
@ -120,7 +122,7 @@ def m3u_to_plex_ids(playlist):
|
||||||
def playlist_file_to_plex_ids(playlist):
|
def playlist_file_to_plex_ids(playlist):
|
||||||
"""
|
"""
|
||||||
Takes the playlist file located at path [unicode] and parses it.
|
Takes the playlist file located at path [unicode] and parses it.
|
||||||
Returns a list of plex_ids (str) or raises PL.PlaylistError if a single
|
Returns a list of plex_ids (str) or raises PlaylistError if a single
|
||||||
item cannot be parsed from Kodi to Plex.
|
item cannot be parsed from Kodi to Plex.
|
||||||
"""
|
"""
|
||||||
if playlist.kodi_extension == 'm3u':
|
if playlist.kodi_extension == 'm3u':
|
||||||
|
|
|
@ -6,11 +6,13 @@ Create and delete playlists on the Kodi side of things
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import Playlist, PlaylistError, kodi_playlist_hash
|
from .common import Playlist, kodi_playlist_hash
|
||||||
from . import db, pms
|
from . import db, pms
|
||||||
|
|
||||||
from ..plex_api import API
|
from ..plex_api import API
|
||||||
from .. import utils, path_ops, variables as v
|
from .. import utils, path_ops, variables as v
|
||||||
|
from ..exceptions import PlaylistError
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playlists.kodi_pl')
|
LOG = getLogger('PLEX.playlists.kodi_pl')
|
||||||
REGEX_FILE_NUMBERING = re.compile(r'''_(\d\d)\.\w+$''')
|
REGEX_FILE_NUMBERING = re.compile(r'''_(\d\d)\.\w+$''')
|
||||||
|
|
|
@ -5,8 +5,9 @@ Create and delete playlists on the Plex side of things
|
||||||
"""
|
"""
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from .common import PlaylistError
|
|
||||||
from . import pms, db
|
from . import pms, db
|
||||||
|
from ..exceptions import PlaylistError
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playlists.plex_pl')
|
LOG = getLogger('PLEX.playlists.plex_pl')
|
||||||
# Used for updating Plex playlists due to Kodi changes - Plex playlist
|
# Used for updating Plex playlists due to Kodi changes - Plex playlist
|
||||||
|
|
|
@ -6,11 +6,11 @@ manipulate playlists
|
||||||
"""
|
"""
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from .common import PlaylistError
|
|
||||||
|
|
||||||
from ..plex_api import API
|
from ..plex_api import API
|
||||||
from ..downloadutils import DownloadUtils as DU
|
from ..downloadutils import DownloadUtils as DU
|
||||||
from .. import utils, app, variables as v
|
from .. import utils, app, variables as v
|
||||||
|
from ..exceptions import PlaylistError
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playlists.pms')
|
LOG = getLogger('PLEX.playlists.pms')
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import xbmc
|
||||||
from .plex_api import API
|
from .plex_api import API
|
||||||
from . import playlist_func as PL, plex_functions as PF
|
from . import playlist_func as PL, plex_functions as PF
|
||||||
from . import backgroundthread, utils, json_rpc as js, app, variables as v
|
from . import backgroundthread, utils, json_rpc as js, app, variables as v
|
||||||
|
from . import exceptions
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playqueue')
|
LOG = getLogger('PLEX.playqueue')
|
||||||
|
@ -87,7 +88,7 @@ def init_playqueue_from_plex_children(plex_id, transient_token=None):
|
||||||
api = API(child)
|
api = API(child)
|
||||||
try:
|
try:
|
||||||
PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id)
|
PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.error('Could not add Plex item to our playlist: %s, %s',
|
LOG.error('Could not add Plex item to our playlist: %s, %s',
|
||||||
child.tag, child.attrib)
|
child.tag, child.attrib)
|
||||||
playqueue.plex_transient_token = transient_token
|
playqueue.plex_transient_token = transient_token
|
||||||
|
@ -150,7 +151,7 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||||
i + j, i)
|
i + j, i)
|
||||||
try:
|
try:
|
||||||
PL.move_playlist_item(playqueue, i + j, i)
|
PL.move_playlist_item(playqueue, i + j, i)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.error('Could not modify playqueue positions')
|
LOG.error('Could not modify playqueue positions')
|
||||||
LOG.error('This is likely caused by mixing audio and '
|
LOG.error('This is likely caused by mixing audio and '
|
||||||
'video tracks in the Kodi playqueue')
|
'video tracks in the Kodi playqueue')
|
||||||
|
@ -166,7 +167,7 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||||
PL.add_item_to_plex_playqueue(playqueue,
|
PL.add_item_to_plex_playqueue(playqueue,
|
||||||
i,
|
i,
|
||||||
kodi_item=new_item)
|
kodi_item=new_item)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
# Could not add the element
|
# Could not add the element
|
||||||
pass
|
pass
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -195,7 +196,7 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||||
LOG.debug('Detected deletion of playqueue element at pos %s', i)
|
LOG.debug('Detected deletion of playqueue element at pos %s', i)
|
||||||
try:
|
try:
|
||||||
PL.delete_playlist_item_from_PMS(playqueue, i)
|
PL.delete_playlist_item_from_PMS(playqueue, i)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.error('Could not delete PMS element from position %s', i)
|
LOG.error('Could not delete PMS element from position %s', i)
|
||||||
LOG.error('This is likely caused by mixing audio and '
|
LOG.error('This is likely caused by mixing audio and '
|
||||||
'video tracks in the Kodi playqueue')
|
'video tracks in the Kodi playqueue')
|
||||||
|
|
|
@ -20,6 +20,7 @@ from . import playqueue as PQ
|
||||||
from . import variables as v
|
from . import variables as v
|
||||||
from . import backgroundthread
|
from . import backgroundthread
|
||||||
from . import app
|
from . import app
|
||||||
|
from . import exceptions
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ def update_playqueue_from_PMS(playqueue,
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
try:
|
try:
|
||||||
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.error('Could now download playqueue %s', playqueue_id)
|
LOG.error('Could now download playqueue %s', playqueue_id)
|
||||||
return
|
return
|
||||||
if playqueue.id == playqueue_id:
|
if playqueue.id == playqueue_id:
|
||||||
|
@ -63,7 +64,7 @@ def update_playqueue_from_PMS(playqueue,
|
||||||
# Get new metadata for the playqueue first
|
# Get new metadata for the playqueue first
|
||||||
try:
|
try:
|
||||||
PL.get_playlist_details_from_xml(playqueue, xml)
|
PL.get_playlist_details_from_xml(playqueue, xml)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.error('Could not get playqueue ID %s', playqueue_id)
|
LOG.error('Could not get playqueue ID %s', playqueue_id)
|
||||||
return
|
return
|
||||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import xml.etree.ElementTree as etree
|
||||||
from . import app
|
from . import app
|
||||||
from . import path_ops
|
from . import path_ops
|
||||||
from . import variables as v
|
from . import variables as v
|
||||||
|
from .exceptions import SubtitleError
|
||||||
|
|
||||||
LOG = getLogger('PLEX.subtitles')
|
LOG = getLogger('PLEX.subtitles')
|
||||||
|
|
||||||
|
@ -466,7 +467,3 @@ def external_subs_from_filesystem(dirname, filename):
|
||||||
class DummySub(etree.Element):
|
class DummySub(etree.Element):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(DummySub, self).__init__('Stream-subtitle-dummy')
|
super(DummySub, self).__init__('Stream-subtitle-dummy')
|
||||||
|
|
||||||
|
|
||||||
class SubtitleError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
Loading…
Reference in a new issue