Merge pull request #1016 from croneter/beta-version

Bump master
This commit is contained in:
croneter 2019-10-12 12:56:25 +02:00 committed by GitHub
commit 45cd1aa0fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 203 additions and 92 deletions

View file

@ -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)
[![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)
[![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.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)
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)

View file

@ -1,5 +1,5 @@
<?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>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.9.1" />
@ -83,7 +83,18 @@
<summary lang="lt_LT">Natūralioji „Plex“ integracija į „Kodi“</summary>
<description lang="lt_LT">Prijunkite „Kodi“ prie „Plex Medija Serverio“. Šiame papildinyje daroma prielaida, kad valdote visus savo vaizdo įrašus naudodami „Plex“ (ir nė vieno su „Kodi“). Galite prarasti jau saugomus „Kodi“ vaizdo įrašų ir muzikos duomenų bazių duomenis (kadangi šis papildinys juos tiesiogiai pakeičia). Naudokite savo pačių rizika!</description>
<disclaimer lang="lt_LT">Naudokite savo pačių rizika</disclaimer>
<news>version 2.9.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
version 2.9.8 (beta only):

View file

@ -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:
- Versions 2.9.6 - 2.9.8 for everyone

View file

@ -1164,7 +1164,7 @@ msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr ""
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 ""
msgctxt "#39218"

View file

@ -53,6 +53,9 @@ class PlayState(object):
}
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
# beginning?
# Set to None if resume dialog has not been shown

View file

@ -85,8 +85,7 @@ class InitialSetup(object):
if not port:
return False
url = '%s:%s' % (address, port)
# "Use HTTPS (SSL) connections? With Kodi 18 or later, HTTPS will likely
# not work!"
# "Use HTTPS (SSL) connections? Answer should probably be yes."
https = utils.yesno_dialog(utils.lang(29999), utils.lang(39217))
if https:
url = 'https://%s' % url

View file

@ -91,6 +91,14 @@ class Movie(ItemBase):
api.provider('imdb'),
"imdb",
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:
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_MOVIE)
uniqueid = -1
@ -120,6 +128,13 @@ class Movie(ItemBase):
v.KODI_TYPE_MOVIE,
api.provider('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:
uniqueid = -1
self.kodidb.add_people(kodi_id,

View file

@ -204,6 +204,14 @@ class Show(TvShowMixin, ItemBase):
api.provider('tvdb'),
'tvdb',
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:
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
uniqueid = -1
@ -245,6 +253,13 @@ class Show(TvShowMixin, ItemBase):
v.KODI_TYPE_SHOW,
api.provider('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:
uniqueid = -1
self.kodidb.add_people(kodi_id,
@ -490,6 +505,14 @@ class Episode(TvShowMixin, ItemBase):
api.provider('tvdb'),
"tvdb",
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:
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE)
uniqueid = -1
@ -568,6 +591,15 @@ class Episode(TvShowMixin, ItemBase):
v.KODI_TYPE_EPISODE,
api.provider('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,
v.KODI_TYPE_EPISODE,
api.people())

View file

@ -24,8 +24,7 @@ from . import backgroundthread, app, variables as v
LOG = getLogger('PLEX.kodimonitor')
# "Start from beginning", "Play from beginning"
STRINGS = (utils.try_encode(utils.lang(12021)),
utils.try_encode(utils.lang(12023)))
STRINGS = (utils.lang(12021).encode('utf-8'), utils.lang(12023).encode('utf-8'))
class KodiMonitor(xbmc.Monitor):
@ -107,29 +106,7 @@ class KodiMonitor(xbmc.Monitor):
with app.APP.lock_playqueues:
self._playlist_onclear(data)
elif method == "VideoLibrary.OnUpdate":
# Manually marking as watched/unwatched
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')
_videolibrary_onupdate(data)
elif method == "VideoLibrary.OnRemove":
pass
elif method == "System.OnSleep":
@ -423,6 +400,8 @@ class KodiMonitor(xbmc.Monitor):
container_key = '/playQueues/%s' % playqueue.id
else:
container_key = '/library/metadata/%s' % plex_id
# Remember the currently playing item
app.PLAYSTATE.item = item
# Remember that this player has been active
app.PLAYSTATE.active_players.add(playerid)
status.update(info)
@ -456,7 +435,7 @@ def _playback_cleanup(ended=False):
# Remember the last played item later
app.PLAYSTATE.old_player_states[playerid] = copy.deepcopy(status)
# Stop transcoding
if status['playmethod'] == 'Transcode':
if status['playmethod'] == v.PLAYBACK_METHOD_TRANSCODE:
LOG.debug('Tell the PMS to stop transcoding')
DU().downloadUrl(
'{server}/video/:/transcode/universal/stop',
@ -470,6 +449,8 @@ def _playback_cleanup(ended=False):
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
# As all playback has halted, reset the players that have been active
app.PLAYSTATE.active_players = set()
app.PLAYSTATE.item = None
utils.delete_temporary_subtitles()
LOG.info('Finished PKC playback cleanup')
@ -639,6 +620,41 @@ def _notify_upnext(item):
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):
"""
Detect the resume dialog for widgets. Could also be used to detect

View file

@ -19,6 +19,8 @@ import shutil
import os
from os import path # allows to use path_ops.path.join, for example
from distutils import dir_util
import re
import xbmc
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_ENCODING = 'utf-8'
REGEX_FILE_NUMBERING = re.compile(r'''_(\d\d)\.\w+$''')
def encode_path(path):
@ -216,3 +219,25 @@ def basename(path):
return path.rsplit('\\', 1)[1]
except IndexError:
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

View file

@ -19,7 +19,7 @@ from . import playlist_func as PL
from . import playqueue as PQ
from . import json_rpc as js
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 app
@ -473,10 +473,11 @@ def _conclude_playback(playqueue, pos):
_ensure_resolve()
return
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())
elif item.playmethod == 'Transcode':
playutils.audio_subtitle_prefs(listitem)
elif item.playmethod in (v.PLAYBACK_METHOD_DIRECT_STREAM,
v.PLAYBACK_METHOD_TRANSCODE):
audio_subtitle_prefs(api, listitem)
_set_resume(listitem, item, api)
transfer.send(listitem)
LOG.info('Done concluding playback')
@ -518,7 +519,7 @@ def process_indirect(key, offset, resolve=True):
playqueue.clear()
item = PL.playlist_item_from_xml(xml[0])
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
try:

View file

@ -351,12 +351,12 @@ def audio_subtitle_prefs(api, listitem):
for stream in mediastreams:
# Since Plex returns all possible tracks together, have to sort
# them.
index = stream.attrib.get('id')
typus = stream.attrib.get('streamType')
index = stream.get('id')
typus = stream.get('streamType')
# Audio
if typus == "2":
codec = stream.attrib.get('codec')
channellayout = stream.attrib.get('audioChannelLayout', "")
codec = stream.get('codec')
channellayout = stream.get('audioChannelLayout', "")
try:
track = "%s %s - %s %s" % (audio_numb + 1,
stream.attrib['language'],
@ -368,48 +368,42 @@ def audio_subtitle_prefs(api, listitem):
codec,
channellayout)
audio_streams_list.append(index)
audio_streams.append(utils.try_encode(track))
audio_streams.append(track.encode('utf-8'))
audio_numb += 1
# Subtitles
elif typus == "3":
try:
track = "%s %s" % (sub_num + 1, stream.attrib['language'])
track = '{} {}'.format(sub_num, stream.attrib['displayTitle'])
except KeyError:
track = "%s %s (%s)" % (sub_num + 1,
utils.lang(39707), # unknown
stream.attrib.get('codec'))
default = stream.attrib.get('default')
forced = stream.attrib.get('forced')
downloadable = stream.attrib.get('key')
track = '{} {} ({})'.format(sub_num + 1,
utils.lang(39707), # unknown
stream.get('codec'))
default = stream.get('default')
forced = stream.get('forced')
downloadable = stream.get('key')
if default:
track = "%s - %s" % (track, utils.lang(39708)) # Default
if forced:
track = "%s - %s" % (track, utils.lang(39709)) # Forced
if downloadable:
# We do know the language - temporarily download
if 'language' in stream.attrib:
path = api.download_external_subtitles(
'{server}%s' % stream.attrib['key'],
"subtitle.%s.%s" % (stream.attrib['languageCode'],
stream.attrib['codec']))
# We don't know the language - no need to download
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))
path = api.download_external_subtitles(
'{{server}}{}'.format(stream.get('key')),
stream.get('displayTitle'),
stream.get('codec'))
if path:
downloadable_streams.append(index)
download_subs.append(path.encode('utf-8'))
else:
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
# turned off
default_sub = index
subtitle_streams_list.append(index)
subtitle_streams.append(utils.try_encode(track))
subtitle_streams.append(track.encode('utf-8'))
sub_num += 1
if audio_numb > 1:

View file

@ -552,6 +552,9 @@ class Base(object):
elif providername == 'tvdb':
# originally e.g. com.plexapp.agents.thetvdb://276564?lang=en
regex = utils.REGEX_TVDB
elif providername == 'tmdb':
# originally e.g. com.plexapp.agents.themoviedb://603?lang=en
regex = utils.REGEX_TMDB
else:
raise NotImplementedError('Not implemented: %s' % providername)

View file

@ -254,46 +254,33 @@ class Media(object):
try:
mediastreams = self.xml[0][self.part]
except (TypeError, KeyError, IndexError):
return
kodiindex = 0
fileindex = 0
return externalsubs
for stream in mediastreams:
# Since plex returns all possible tracks together, have to pull
# only external subtitles - only for these a 'key' exists
if cast(int, stream.get('streamType')) != 3:
# Not a subtitle
if int(stream.get('streamType')) != 3 or 'key' not in stream.attrib:
# Not a subtitle or not not an external subtitle
continue
# Only set for additional external subtitles NOT lying beside video
key = stream.get('key')
# Only set for dedicated subtitle files lying beside video
# ext = stream.attrib.get('format')
if key:
# 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))
path = self.download_external_subtitles(
'{server}%s' % stream.get('key'),
stream.get('displayTitle'),
stream.get('codec'))
if path:
externalsubs.append(path)
kodiindex += 1
LOG.info('Found external subs: %s', externalsubs)
return externalsubs
@staticmethod
def download_external_subtitles(url, filename):
def download_external_subtitles(url, filename, extension):
"""
One cannot pass the subtitle language for ListItems. Workaround; will
download the subtitle at url to the Kodi PKC directory in a temp dir
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)
try:
response.status_code
@ -302,8 +289,8 @@ class Media(object):
return
else:
LOG.debug('Writing temp subtitle to %s', path)
with open(path_ops.encode_path(path), 'wb') as filer:
filer.write(response.content)
with open(path_ops.encode_path(path), 'wb') as f:
f.write(response.content)
return path
def validate_playurl(self, path, typus, force_check=False, folder=False,

View file

@ -49,6 +49,7 @@ REGEX_PLEX_DIRECT = re.compile(r'''\.plex\.direct:\d+$''')
# Plex API
REGEX_IMDB = re.compile(r'''/(tt\d+)''')
REGEX_TVDB = re.compile(r'''thetvdb:\/\/(.+?)\?''')
REGEX_TMDB = re.compile(r'''themoviedb:\/\/(.+?)\?''')
# Plex music
REGEX_MUSICPATH = re.compile(r'''^\^(.+)\$$''')
# Grab Plex id from an URL-encoded string
@ -511,6 +512,19 @@ def escape_html(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):
"""
Open a connection to the Kodi database.