commit
45cd1aa0fc
15 changed files with 203 additions and 92 deletions
|
@ -1,5 +1,5 @@
|
||||||
[![stable version](https://img.shields.io/badge/stable_version-2.9.9-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.11-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.9.9-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.11-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)
|
||||||
|
|
15
addon.xml
15
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.9.9" provider-name="croneter">
|
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.9.11" 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" />
|
||||||
|
@ -83,7 +83,18 @@
|
||||||
<summary lang="lt_LT">Natūralioji „Plex“ integracija į „Kodi“</summary>
|
<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>
|
<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>
|
<disclaimer lang="lt_LT">Naudokite savo pačių rizika</disclaimer>
|
||||||
<news>version 2.9.9:
|
<news>version 2.9.11:
|
||||||
|
- version 2.9.10 for everyone
|
||||||
|
|
||||||
|
version 2.9.10 (beta only):
|
||||||
|
- Add tmdb provider sync
|
||||||
|
- Fix external subtitles not being available
|
||||||
|
- Fix PKC increasing the Plex watch count by 2 instead of 1
|
||||||
|
- Improve subtitle naming
|
||||||
|
- Delete temporary subtitles on playback stop
|
||||||
|
- Fix a missleading string
|
||||||
|
|
||||||
|
version 2.9.9:
|
||||||
- Versions 2.9.6 - 2.9.8 for everyone
|
- Versions 2.9.6 - 2.9.8 for everyone
|
||||||
|
|
||||||
version 2.9.8 (beta only):
|
version 2.9.8 (beta only):
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
|
version 2.9.11:
|
||||||
|
- version 2.9.10 for everyone
|
||||||
|
|
||||||
|
version 2.9.10 (beta only):
|
||||||
|
- Add tmdb provider sync
|
||||||
|
- Fix external subtitles not being available
|
||||||
|
- Fix PKC increasing the Plex watch count by 2 instead of 1
|
||||||
|
- Improve subtitle naming
|
||||||
|
- Delete temporary subtitles on playback stop
|
||||||
|
- Fix a missleading string
|
||||||
|
|
||||||
version 2.9.9:
|
version 2.9.9:
|
||||||
- Versions 2.9.6 - 2.9.8 for everyone
|
- Versions 2.9.6 - 2.9.8 for everyone
|
||||||
|
|
||||||
|
|
|
@ -1164,7 +1164,7 @@ msgid "Enter your Plex Media Server's IP or URL, Examples are:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#39217"
|
msgctxt "#39217"
|
||||||
msgid "Use HTTPS (SSL) connections? With Kodi 18 or later, HTTPS will likely not work!"
|
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#39218"
|
msgctxt "#39218"
|
||||||
|
|
|
@ -53,6 +53,9 @@ class PlayState(object):
|
||||||
}
|
}
|
||||||
self.played_info = {}
|
self.played_info = {}
|
||||||
|
|
||||||
|
# Currently playing PKC item, a PlaylistItem()
|
||||||
|
self.item = None
|
||||||
|
|
||||||
# Set by SpecialMonitor - did user choose to resume playback or start from the
|
# Set by SpecialMonitor - did user choose to resume playback or start from the
|
||||||
# beginning?
|
# beginning?
|
||||||
# Set to None if resume dialog has not been shown
|
# Set to None if resume dialog has not been shown
|
||||||
|
|
|
@ -85,8 +85,7 @@ class InitialSetup(object):
|
||||||
if not port:
|
if not port:
|
||||||
return False
|
return False
|
||||||
url = '%s:%s' % (address, port)
|
url = '%s:%s' % (address, port)
|
||||||
# "Use HTTPS (SSL) connections? With Kodi 18 or later, HTTPS will likely
|
# "Use HTTPS (SSL) connections? Answer should probably be yes."
|
||||||
# not work!"
|
|
||||||
https = utils.yesno_dialog(utils.lang(29999), utils.lang(39217))
|
https = utils.yesno_dialog(utils.lang(29999), utils.lang(39217))
|
||||||
if https:
|
if https:
|
||||||
url = 'https://%s' % url
|
url = 'https://%s' % url
|
||||||
|
|
|
@ -91,6 +91,14 @@ class Movie(ItemBase):
|
||||||
api.provider('imdb'),
|
api.provider('imdb'),
|
||||||
"imdb",
|
"imdb",
|
||||||
uniqueid)
|
uniqueid)
|
||||||
|
elif api.provider('tmdb') is not None:
|
||||||
|
uniqueid = self.kodidb.get_uniqueid(kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE)
|
||||||
|
self.kodidb.update_uniqueid(kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE,
|
||||||
|
api.provider('tmdb'),
|
||||||
|
"tmdb",
|
||||||
|
uniqueid)
|
||||||
else:
|
else:
|
||||||
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_MOVIE)
|
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_MOVIE)
|
||||||
uniqueid = -1
|
uniqueid = -1
|
||||||
|
@ -120,6 +128,13 @@ class Movie(ItemBase):
|
||||||
v.KODI_TYPE_MOVIE,
|
v.KODI_TYPE_MOVIE,
|
||||||
api.provider('imdb'),
|
api.provider('imdb'),
|
||||||
"imdb")
|
"imdb")
|
||||||
|
elif api.provider('tmdb') is not None:
|
||||||
|
uniqueid = self.kodidb.add_uniqueid_id()
|
||||||
|
self.kodidb.add_uniqueid(uniqueid,
|
||||||
|
kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE,
|
||||||
|
api.provider('tmdb'),
|
||||||
|
"tmdb")
|
||||||
else:
|
else:
|
||||||
uniqueid = -1
|
uniqueid = -1
|
||||||
self.kodidb.add_people(kodi_id,
|
self.kodidb.add_people(kodi_id,
|
||||||
|
|
|
@ -204,6 +204,14 @@ class Show(TvShowMixin, ItemBase):
|
||||||
api.provider('tvdb'),
|
api.provider('tvdb'),
|
||||||
'tvdb',
|
'tvdb',
|
||||||
uniqueid)
|
uniqueid)
|
||||||
|
elif api.provider('tmdb') is not None:
|
||||||
|
uniqueid = self.kodidb.get_uniqueid(kodi_id,
|
||||||
|
v.KODI_TYPE_SHOW)
|
||||||
|
self.kodidb.update_uniqueid(kodi_id,
|
||||||
|
v.KODI_TYPE_SHOW,
|
||||||
|
api.provider('tmdb'),
|
||||||
|
'tmdb',
|
||||||
|
uniqueid)
|
||||||
else:
|
else:
|
||||||
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
|
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
|
||||||
uniqueid = -1
|
uniqueid = -1
|
||||||
|
@ -245,6 +253,13 @@ class Show(TvShowMixin, ItemBase):
|
||||||
v.KODI_TYPE_SHOW,
|
v.KODI_TYPE_SHOW,
|
||||||
api.provider('tvdb'),
|
api.provider('tvdb'),
|
||||||
'tvdb')
|
'tvdb')
|
||||||
|
if api.provider('tmdb'):
|
||||||
|
uniqueid = self.kodidb.add_uniqueid_id()
|
||||||
|
self.kodidb.add_uniqueid(uniqueid,
|
||||||
|
kodi_id,
|
||||||
|
v.KODI_TYPE_SHOW,
|
||||||
|
api.provider('tmdb'),
|
||||||
|
'tmdb')
|
||||||
else:
|
else:
|
||||||
uniqueid = -1
|
uniqueid = -1
|
||||||
self.kodidb.add_people(kodi_id,
|
self.kodidb.add_people(kodi_id,
|
||||||
|
@ -490,6 +505,14 @@ class Episode(TvShowMixin, ItemBase):
|
||||||
api.provider('tvdb'),
|
api.provider('tvdb'),
|
||||||
"tvdb",
|
"tvdb",
|
||||||
uniqueid)
|
uniqueid)
|
||||||
|
elif api.provider('tmdb'):
|
||||||
|
uniqueid = self.kodidb.get_uniqueid(kodi_id,
|
||||||
|
v.KODI_TYPE_EPISODE)
|
||||||
|
self.kodidb.update_uniqueid(kodi_id,
|
||||||
|
v.KODI_TYPE_EPISODE,
|
||||||
|
api.provider('tmdb'),
|
||||||
|
"tmdb",
|
||||||
|
uniqueid)
|
||||||
else:
|
else:
|
||||||
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE)
|
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE)
|
||||||
uniqueid = -1
|
uniqueid = -1
|
||||||
|
@ -568,6 +591,15 @@ class Episode(TvShowMixin, ItemBase):
|
||||||
v.KODI_TYPE_EPISODE,
|
v.KODI_TYPE_EPISODE,
|
||||||
api.provider('tvdb'),
|
api.provider('tvdb'),
|
||||||
"tvdb")
|
"tvdb")
|
||||||
|
elif api.provider('tmdb'):
|
||||||
|
uniqueid = self.kodidb.add_uniqueid_id()
|
||||||
|
self.kodidb.add_uniqueid(uniqueid,
|
||||||
|
kodi_id,
|
||||||
|
v.KODI_TYPE_EPISODE,
|
||||||
|
api.provider('tmdb'),
|
||||||
|
"tmdb")
|
||||||
|
else:
|
||||||
|
uniqueid = -1
|
||||||
self.kodidb.add_people(kodi_id,
|
self.kodidb.add_people(kodi_id,
|
||||||
v.KODI_TYPE_EPISODE,
|
v.KODI_TYPE_EPISODE,
|
||||||
api.people())
|
api.people())
|
||||||
|
|
|
@ -24,8 +24,7 @@ from . import backgroundthread, app, variables as v
|
||||||
LOG = getLogger('PLEX.kodimonitor')
|
LOG = getLogger('PLEX.kodimonitor')
|
||||||
|
|
||||||
# "Start from beginning", "Play from beginning"
|
# "Start from beginning", "Play from beginning"
|
||||||
STRINGS = (utils.try_encode(utils.lang(12021)),
|
STRINGS = (utils.lang(12021).encode('utf-8'), utils.lang(12023).encode('utf-8'))
|
||||||
utils.try_encode(utils.lang(12023)))
|
|
||||||
|
|
||||||
|
|
||||||
class KodiMonitor(xbmc.Monitor):
|
class KodiMonitor(xbmc.Monitor):
|
||||||
|
@ -107,29 +106,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
self._playlist_onclear(data)
|
self._playlist_onclear(data)
|
||||||
elif method == "VideoLibrary.OnUpdate":
|
elif method == "VideoLibrary.OnUpdate":
|
||||||
# Manually marking as watched/unwatched
|
_videolibrary_onupdate(data)
|
||||||
playcount = data.get('playcount')
|
|
||||||
item = data.get('item')
|
|
||||||
if playcount is None or item is None:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
kodi_id = item['id']
|
|
||||||
kodi_type = item['type']
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
LOG.info("Item is invalid for playstate update.")
|
|
||||||
return
|
|
||||||
# Send notification to the server.
|
|
||||||
with PlexDB() as plexdb:
|
|
||||||
db_item = plexdb.item_by_kodi_id(kodi_id, kodi_type)
|
|
||||||
if not db_item:
|
|
||||||
LOG.error("Could not find plex_id in plex database for a "
|
|
||||||
"video library update")
|
|
||||||
else:
|
|
||||||
# notify the server
|
|
||||||
if playcount > 0:
|
|
||||||
PF.scrobble(db_item['plex_id'], 'watched')
|
|
||||||
else:
|
|
||||||
PF.scrobble(db_item['plex_id'], 'unwatched')
|
|
||||||
elif method == "VideoLibrary.OnRemove":
|
elif method == "VideoLibrary.OnRemove":
|
||||||
pass
|
pass
|
||||||
elif method == "System.OnSleep":
|
elif method == "System.OnSleep":
|
||||||
|
@ -423,6 +400,8 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
container_key = '/playQueues/%s' % playqueue.id
|
container_key = '/playQueues/%s' % playqueue.id
|
||||||
else:
|
else:
|
||||||
container_key = '/library/metadata/%s' % plex_id
|
container_key = '/library/metadata/%s' % plex_id
|
||||||
|
# Remember the currently playing item
|
||||||
|
app.PLAYSTATE.item = item
|
||||||
# Remember that this player has been active
|
# Remember that this player has been active
|
||||||
app.PLAYSTATE.active_players.add(playerid)
|
app.PLAYSTATE.active_players.add(playerid)
|
||||||
status.update(info)
|
status.update(info)
|
||||||
|
@ -456,7 +435,7 @@ def _playback_cleanup(ended=False):
|
||||||
# Remember the last played item later
|
# Remember the last played item later
|
||||||
app.PLAYSTATE.old_player_states[playerid] = copy.deepcopy(status)
|
app.PLAYSTATE.old_player_states[playerid] = copy.deepcopy(status)
|
||||||
# Stop transcoding
|
# Stop transcoding
|
||||||
if status['playmethod'] == 'Transcode':
|
if status['playmethod'] == v.PLAYBACK_METHOD_TRANSCODE:
|
||||||
LOG.debug('Tell the PMS to stop transcoding')
|
LOG.debug('Tell the PMS to stop transcoding')
|
||||||
DU().downloadUrl(
|
DU().downloadUrl(
|
||||||
'{server}/video/:/transcode/universal/stop',
|
'{server}/video/:/transcode/universal/stop',
|
||||||
|
@ -470,6 +449,8 @@ def _playback_cleanup(ended=False):
|
||||||
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
|
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
|
||||||
# As all playback has halted, reset the players that have been active
|
# As all playback has halted, reset the players that have been active
|
||||||
app.PLAYSTATE.active_players = set()
|
app.PLAYSTATE.active_players = set()
|
||||||
|
app.PLAYSTATE.item = None
|
||||||
|
utils.delete_temporary_subtitles()
|
||||||
LOG.info('Finished PKC playback cleanup')
|
LOG.info('Finished PKC playback cleanup')
|
||||||
|
|
||||||
|
|
||||||
|
@ -639,6 +620,41 @@ def _notify_upnext(item):
|
||||||
xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data))
|
xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data))
|
||||||
|
|
||||||
|
|
||||||
|
def _videolibrary_onupdate(data):
|
||||||
|
"""
|
||||||
|
A specific Kodi library item has been updated. This seems to happen if the
|
||||||
|
user marks an item as watched/unwatched or if playback of the item just
|
||||||
|
stopped
|
||||||
|
"""
|
||||||
|
playcount = data.get('playcount')
|
||||||
|
item = data.get('item')
|
||||||
|
if playcount is None or item is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
kodi_id = item['id']
|
||||||
|
kodi_type = item['type']
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
LOG.info("Item is invalid for playstate update.")
|
||||||
|
return
|
||||||
|
if app.PLAYSTATE.item and kodi_id == app.PLAYSTATE.item.kodi_id and \
|
||||||
|
kodi_type == app.PLAYSTATE.item.kodi_type:
|
||||||
|
# Kodi updates an item immediately after playback. Hence we do NOT
|
||||||
|
# increase or decrease the viewcount
|
||||||
|
return
|
||||||
|
# Send notification to the server.
|
||||||
|
with PlexDB(lock=False) as plexdb:
|
||||||
|
db_item = plexdb.item_by_kodi_id(kodi_id, kodi_type)
|
||||||
|
if not db_item:
|
||||||
|
LOG.error("Could not find plex_id in plex database for a "
|
||||||
|
"video library update")
|
||||||
|
return
|
||||||
|
# notify the server
|
||||||
|
if playcount > 0:
|
||||||
|
PF.scrobble(db_item['plex_id'], 'watched')
|
||||||
|
else:
|
||||||
|
PF.scrobble(db_item['plex_id'], 'unwatched')
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -19,6 +19,8 @@ import shutil
|
||||||
import os
|
import os
|
||||||
from os import path # allows to use path_ops.path.join, for example
|
from os import path # allows to use path_ops.path.join, for example
|
||||||
from distutils import dir_util
|
from distutils import dir_util
|
||||||
|
import re
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
|
|
||||||
|
@ -26,6 +28,7 @@ from .tools import unicode_paths
|
||||||
|
|
||||||
# Kodi seems to encode in utf-8 in ALL cases (unlike e.g. the OS filesystem)
|
# Kodi seems to encode in utf-8 in ALL cases (unlike e.g. the OS filesystem)
|
||||||
KODI_ENCODING = 'utf-8'
|
KODI_ENCODING = 'utf-8'
|
||||||
|
REGEX_FILE_NUMBERING = re.compile(r'''_(\d\d)\.\w+$''')
|
||||||
|
|
||||||
|
|
||||||
def encode_path(path):
|
def encode_path(path):
|
||||||
|
@ -216,3 +219,25 @@ def basename(path):
|
||||||
return path.rsplit('\\', 1)[1]
|
return path.rsplit('\\', 1)[1]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def create_unique_path(directory, filename, extension):
|
||||||
|
"""
|
||||||
|
Checks whether 'directory/filename.extension' exists. If so, will start
|
||||||
|
numbering the filename until the file does not exist yet (up to 99)
|
||||||
|
"""
|
||||||
|
res = path.join(directory, '.'.join((filename, extension)))
|
||||||
|
while exists(res):
|
||||||
|
occurance = REGEX_FILE_NUMBERING.search(res)
|
||||||
|
if not occurance:
|
||||||
|
filename = '{}_00'.format(filename[:min(len(filename),
|
||||||
|
251 - len(extension))])
|
||||||
|
res = path.join(directory, '.'.join((filename, extension)))
|
||||||
|
else:
|
||||||
|
number = int(occurance.group(1)) + 1
|
||||||
|
if number > 99:
|
||||||
|
raise RuntimeError('Could not create unique file: {} {} {}'.format(
|
||||||
|
directory, filename, extension))
|
||||||
|
basename = re.sub(REGEX_FILE_NUMBERING, '', res)
|
||||||
|
res = '{}_{:02d}.{}'.format(basename, number, extension)
|
||||||
|
return res
|
||||||
|
|
|
@ -19,7 +19,7 @@ from . import playlist_func as PL
|
||||||
from . import playqueue as PQ
|
from . import playqueue as PQ
|
||||||
from . import json_rpc as js
|
from . import json_rpc as js
|
||||||
from . import transfer
|
from . import transfer
|
||||||
from .playback_decision import set_playurl
|
from .playback_decision import set_playurl, audio_subtitle_prefs
|
||||||
from . import variables as v
|
from . import variables as v
|
||||||
from . import app
|
from . import app
|
||||||
|
|
||||||
|
@ -473,10 +473,11 @@ def _conclude_playback(playqueue, pos):
|
||||||
_ensure_resolve()
|
_ensure_resolve()
|
||||||
return
|
return
|
||||||
listitem.setPath(item.file.encode('utf-8'))
|
listitem.setPath(item.file.encode('utf-8'))
|
||||||
if item.playmethod == 'DirectStream':
|
if item.playmethod == v.PLAYBACK_METHOD_DIRECT_PLAY:
|
||||||
listitem.setSubtitles(api.cache_external_subs())
|
listitem.setSubtitles(api.cache_external_subs())
|
||||||
elif item.playmethod == 'Transcode':
|
elif item.playmethod in (v.PLAYBACK_METHOD_DIRECT_STREAM,
|
||||||
playutils.audio_subtitle_prefs(listitem)
|
v.PLAYBACK_METHOD_TRANSCODE):
|
||||||
|
audio_subtitle_prefs(api, listitem)
|
||||||
_set_resume(listitem, item, api)
|
_set_resume(listitem, item, api)
|
||||||
transfer.send(listitem)
|
transfer.send(listitem)
|
||||||
LOG.info('Done concluding playback')
|
LOG.info('Done concluding playback')
|
||||||
|
@ -518,7 +519,7 @@ def process_indirect(key, offset, resolve=True):
|
||||||
playqueue.clear()
|
playqueue.clear()
|
||||||
item = PL.playlist_item_from_xml(xml[0])
|
item = PL.playlist_item_from_xml(xml[0])
|
||||||
item.offset = offset
|
item.offset = offset
|
||||||
item.playmethod = 'DirectStream'
|
item.playmethod = v.PLAYBACK_METHOD_DIRECT_PLAY
|
||||||
|
|
||||||
# Need to get yet another xml to get the final playback url
|
# Need to get yet another xml to get the final playback url
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -351,12 +351,12 @@ def audio_subtitle_prefs(api, listitem):
|
||||||
for stream in mediastreams:
|
for stream in mediastreams:
|
||||||
# Since Plex returns all possible tracks together, have to sort
|
# Since Plex returns all possible tracks together, have to sort
|
||||||
# them.
|
# them.
|
||||||
index = stream.attrib.get('id')
|
index = stream.get('id')
|
||||||
typus = stream.attrib.get('streamType')
|
typus = stream.get('streamType')
|
||||||
# Audio
|
# Audio
|
||||||
if typus == "2":
|
if typus == "2":
|
||||||
codec = stream.attrib.get('codec')
|
codec = stream.get('codec')
|
||||||
channellayout = stream.attrib.get('audioChannelLayout', "")
|
channellayout = stream.get('audioChannelLayout', "")
|
||||||
try:
|
try:
|
||||||
track = "%s %s - %s %s" % (audio_numb + 1,
|
track = "%s %s - %s %s" % (audio_numb + 1,
|
||||||
stream.attrib['language'],
|
stream.attrib['language'],
|
||||||
|
@ -368,48 +368,42 @@ def audio_subtitle_prefs(api, listitem):
|
||||||
codec,
|
codec,
|
||||||
channellayout)
|
channellayout)
|
||||||
audio_streams_list.append(index)
|
audio_streams_list.append(index)
|
||||||
audio_streams.append(utils.try_encode(track))
|
audio_streams.append(track.encode('utf-8'))
|
||||||
audio_numb += 1
|
audio_numb += 1
|
||||||
|
|
||||||
# Subtitles
|
# Subtitles
|
||||||
elif typus == "3":
|
elif typus == "3":
|
||||||
try:
|
try:
|
||||||
track = "%s %s" % (sub_num + 1, stream.attrib['language'])
|
track = '{} {}'.format(sub_num, stream.attrib['displayTitle'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
track = "%s %s (%s)" % (sub_num + 1,
|
track = '{} {} ({})'.format(sub_num + 1,
|
||||||
utils.lang(39707), # unknown
|
utils.lang(39707), # unknown
|
||||||
stream.attrib.get('codec'))
|
stream.get('codec'))
|
||||||
default = stream.attrib.get('default')
|
default = stream.get('default')
|
||||||
forced = stream.attrib.get('forced')
|
forced = stream.get('forced')
|
||||||
downloadable = stream.attrib.get('key')
|
downloadable = stream.get('key')
|
||||||
|
|
||||||
if default:
|
if default:
|
||||||
track = "%s - %s" % (track, utils.lang(39708)) # Default
|
track = "%s - %s" % (track, utils.lang(39708)) # Default
|
||||||
if forced:
|
if forced:
|
||||||
track = "%s - %s" % (track, utils.lang(39709)) # Forced
|
track = "%s - %s" % (track, utils.lang(39709)) # Forced
|
||||||
if downloadable:
|
if downloadable:
|
||||||
# We do know the language - temporarily download
|
path = api.download_external_subtitles(
|
||||||
if 'language' in stream.attrib:
|
'{{server}}{}'.format(stream.get('key')),
|
||||||
path = api.download_external_subtitles(
|
stream.get('displayTitle'),
|
||||||
'{server}%s' % stream.attrib['key'],
|
stream.get('codec'))
|
||||||
"subtitle.%s.%s" % (stream.attrib['languageCode'],
|
if path:
|
||||||
stream.attrib['codec']))
|
downloadable_streams.append(index)
|
||||||
# We don't know the language - no need to download
|
download_subs.append(path.encode('utf-8'))
|
||||||
else:
|
|
||||||
path = api.attach_plex_token_to_url(
|
|
||||||
"%s%s" % (app.CONN.server,
|
|
||||||
stream.attrib['key']))
|
|
||||||
downloadable_streams.append(index)
|
|
||||||
download_subs.append(utils.try_encode(path))
|
|
||||||
else:
|
else:
|
||||||
track = "%s (%s)" % (track, utils.lang(39710)) # burn-in
|
track = "%s (%s)" % (track, utils.lang(39710)) # burn-in
|
||||||
if stream.attrib.get('selected') == '1' and downloadable:
|
if stream.get('selected') == '1' and downloadable:
|
||||||
# Only show subs without asking user if they can be
|
# Only show subs without asking user if they can be
|
||||||
# turned off
|
# turned off
|
||||||
default_sub = index
|
default_sub = index
|
||||||
|
|
||||||
subtitle_streams_list.append(index)
|
subtitle_streams_list.append(index)
|
||||||
subtitle_streams.append(utils.try_encode(track))
|
subtitle_streams.append(track.encode('utf-8'))
|
||||||
sub_num += 1
|
sub_num += 1
|
||||||
|
|
||||||
if audio_numb > 1:
|
if audio_numb > 1:
|
||||||
|
|
|
@ -552,6 +552,9 @@ class Base(object):
|
||||||
elif providername == 'tvdb':
|
elif providername == 'tvdb':
|
||||||
# originally e.g. com.plexapp.agents.thetvdb://276564?lang=en
|
# originally e.g. com.plexapp.agents.thetvdb://276564?lang=en
|
||||||
regex = utils.REGEX_TVDB
|
regex = utils.REGEX_TVDB
|
||||||
|
elif providername == 'tmdb':
|
||||||
|
# originally e.g. com.plexapp.agents.themoviedb://603?lang=en
|
||||||
|
regex = utils.REGEX_TMDB
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('Not implemented: %s' % providername)
|
raise NotImplementedError('Not implemented: %s' % providername)
|
||||||
|
|
||||||
|
|
|
@ -254,46 +254,33 @@ class Media(object):
|
||||||
try:
|
try:
|
||||||
mediastreams = self.xml[0][self.part]
|
mediastreams = self.xml[0][self.part]
|
||||||
except (TypeError, KeyError, IndexError):
|
except (TypeError, KeyError, IndexError):
|
||||||
return
|
return externalsubs
|
||||||
kodiindex = 0
|
|
||||||
fileindex = 0
|
|
||||||
for stream in mediastreams:
|
for stream in mediastreams:
|
||||||
# Since plex returns all possible tracks together, have to pull
|
# Since plex returns all possible tracks together, have to pull
|
||||||
# only external subtitles - only for these a 'key' exists
|
# only external subtitles - only for these a 'key' exists
|
||||||
if cast(int, stream.get('streamType')) != 3:
|
if int(stream.get('streamType')) != 3 or 'key' not in stream.attrib:
|
||||||
# Not a subtitle
|
# Not a subtitle or not not an external subtitle
|
||||||
continue
|
continue
|
||||||
# Only set for additional external subtitles NOT lying beside video
|
path = self.download_external_subtitles(
|
||||||
key = stream.get('key')
|
'{server}%s' % stream.get('key'),
|
||||||
# Only set for dedicated subtitle files lying beside video
|
stream.get('displayTitle'),
|
||||||
# ext = stream.attrib.get('format')
|
stream.get('codec'))
|
||||||
if key:
|
if path:
|
||||||
# We do know the language - temporarily download
|
|
||||||
if stream.get('languageCode') is not None:
|
|
||||||
language = stream.get('languageCode')
|
|
||||||
codec = stream.get('codec')
|
|
||||||
path = self.download_external_subtitles(
|
|
||||||
"{server}%s" % key,
|
|
||||||
"subtitle%02d.%s.%s" % (fileindex, language, codec))
|
|
||||||
fileindex += 1
|
|
||||||
# We don't know the language - no need to download
|
|
||||||
else:
|
|
||||||
path = self.attach_plex_token_to_url(
|
|
||||||
"%s%s" % (app.CONN.server, key))
|
|
||||||
externalsubs.append(path)
|
externalsubs.append(path)
|
||||||
kodiindex += 1
|
|
||||||
LOG.info('Found external subs: %s', externalsubs)
|
LOG.info('Found external subs: %s', externalsubs)
|
||||||
return externalsubs
|
return externalsubs
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def download_external_subtitles(url, filename):
|
def download_external_subtitles(url, filename, extension):
|
||||||
"""
|
"""
|
||||||
One cannot pass the subtitle language for ListItems. Workaround; will
|
One cannot pass the subtitle language for ListItems. Workaround; will
|
||||||
download the subtitle at url to the Kodi PKC directory in a temp dir
|
download the subtitle at url to the Kodi PKC directory in a temp dir
|
||||||
|
|
||||||
Returns the path to the downloaded subtitle or None
|
Returns the path to the downloaded subtitle or None
|
||||||
"""
|
"""
|
||||||
path = path_ops.path.join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename)
|
path = path_ops.create_unique_path(v.EXTERNAL_SUBTITLE_TEMP_PATH,
|
||||||
|
filename,
|
||||||
|
extension)
|
||||||
response = DU().downloadUrl(url, return_response=True)
|
response = DU().downloadUrl(url, return_response=True)
|
||||||
try:
|
try:
|
||||||
response.status_code
|
response.status_code
|
||||||
|
@ -302,8 +289,8 @@ class Media(object):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
LOG.debug('Writing temp subtitle to %s', path)
|
LOG.debug('Writing temp subtitle to %s', path)
|
||||||
with open(path_ops.encode_path(path), 'wb') as filer:
|
with open(path_ops.encode_path(path), 'wb') as f:
|
||||||
filer.write(response.content)
|
f.write(response.content)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def validate_playurl(self, path, typus, force_check=False, folder=False,
|
def validate_playurl(self, path, typus, force_check=False, folder=False,
|
||||||
|
|
|
@ -49,6 +49,7 @@ REGEX_PLEX_DIRECT = re.compile(r'''\.plex\.direct:\d+$''')
|
||||||
# Plex API
|
# Plex API
|
||||||
REGEX_IMDB = re.compile(r'''/(tt\d+)''')
|
REGEX_IMDB = re.compile(r'''/(tt\d+)''')
|
||||||
REGEX_TVDB = re.compile(r'''thetvdb:\/\/(.+?)\?''')
|
REGEX_TVDB = re.compile(r'''thetvdb:\/\/(.+?)\?''')
|
||||||
|
REGEX_TMDB = re.compile(r'''themoviedb:\/\/(.+?)\?''')
|
||||||
# Plex music
|
# Plex music
|
||||||
REGEX_MUSICPATH = re.compile(r'''^\^(.+)\$$''')
|
REGEX_MUSICPATH = re.compile(r'''^\^(.+)\$$''')
|
||||||
# Grab Plex id from an URL-encoded string
|
# Grab Plex id from an URL-encoded string
|
||||||
|
@ -511,6 +512,19 @@ def escape_html(string):
|
||||||
return string
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
def delete_temporary_subtitles():
|
||||||
|
"""
|
||||||
|
Permanently deletes all temporary subtitle files
|
||||||
|
"""
|
||||||
|
for root, _, files in path_ops.walk(v.EXTERNAL_SUBTITLE_TEMP_PATH):
|
||||||
|
for file in files:
|
||||||
|
try:
|
||||||
|
path_ops.remove(path_ops.path.join(root, file))
|
||||||
|
except (OSError, IOError) as err:
|
||||||
|
LOG.error('Could not delete temporary subtitle: %s, %s: %s',
|
||||||
|
root, file, err)
|
||||||
|
|
||||||
|
|
||||||
def kodi_sql(media_type=None):
|
def kodi_sql(media_type=None):
|
||||||
"""
|
"""
|
||||||
Open a connection to the Kodi database.
|
Open a connection to the Kodi database.
|
||||||
|
|
Loading…
Reference in a new issue