Merge pull request #1612 from croneter/python3-beta

Bump Python3 Master
This commit is contained in:
croneter 2021-09-08 11:37:39 +02:00 committed by GitHub
commit 3ac81c7a18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 599 additions and 34 deletions

View file

@ -53,6 +53,7 @@ Some people argue that PKC is 'hacky' because of the way it directly accesses th
### PKC Features
- Support for Kodi 18 Leia and Kodi 19 Matrix
- Preliminary support for Kodi 19 Nexus. Keep in mind that development for Kodi Nexus has not even officially reached alpha stage - any issues you encounter are probably caused by that
- [Amazon Alexa voice recognition](https://www.plex.tv/apps/streaming-devices/amazon-alexa)
- [Cinema Trailers & Extras](https://support.plex.tv/articles/202934883-cinema-trailers-extras/)
- If Plex did not provide a trailer, automatically get one using the Kodi add-on [The Movie Database](https://kodi.wiki/view/Add-on:The_Movie_Database)

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="3.4.2" provider-name="croneter">
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="3.4.4" provider-name="croneter">
<requires>
<import addon="xbmc.python" version="3.0.0"/>
<import addon="script.module.requests" version="2.22.0+matrix.1" />
@ -91,7 +91,17 @@
<summary lang="ko_KR">Plex를 Kodi에 기본 통합</summary>
<description lang="ko_KR">Kodi를 Plex Media Server에 연결합니다. 이 플러그인은 Plex로 모든 비디오를 관리하고 Kodi로는 관리하지 않는다고 가정합니다. Kodi 비디오 및 음악 데이터베이스에 이미 저장된 데이터가 손실 될 수 있습니다 (이 플러그인이 직접 변경하므로). 자신의 책임하에 사용하십시오!</description>
<disclaimer lang="ko_KR">자신의 책임하에 사용</disclaimer>
<news>version 3.4.2:
<news>version 3.4.4:
- Initial compatibility with Kodi 20 Nexus. Keep in mind that development for Kodi Nexus has not even officially reached alpha stage - any issues you encounter are probably caused by that
- version 3.4.3 for everyone
version 3.4.3 (beta ony):
- Use Plex settings for audio and subtitle stream selection. This is a best guess regarding subtitles as Plex and Kodi are not sharing much info
- Fix PlexKodiConnect setting the Plex subtitle to None
- Download landscape artwork from fanart.tv, thanks @geropan
- Revert "Fix PlexKodiConnect changing subtitles for all videos on the PMS"
version 3.4.2:
- Fix PlexKodiConnect changing or removing subtitles for every video on the PMS
version 3.4.1:

View file

@ -1,3 +1,13 @@
version 3.4.4:
- Initial compatibility with Kodi 20 Nexus. Keep in mind that development for Kodi Nexus has not even officially reached alpha stage - any issues you encounter are probably caused by that
- version 3.4.3 for everyone
version 3.4.3 (beta ony):
- Use Plex settings for audio and subtitle stream selection. This is a best guess regarding subtitles as Plex and Kodi are not sharing much info
- Fix PlexKodiConnect setting the Plex subtitle to None
- Download landscape artwork from fanart.tv, thanks @geropan
- Revert "Fix PlexKodiConnect changing subtitles for all videos on the PMS"
version 3.4.2:
- Fix PlexKodiConnect changing or removing subtitles for every video on the PMS

View file

@ -28,6 +28,7 @@ class KodiMonitor(xbmc.Monitor):
"""
def __init__(self):
self._already_slept = False
self._switch_to_plex_streams = None
xbmc.Monitor.__init__(self)
for playerid in app.PLAYSTATE.player_states:
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
@ -63,6 +64,9 @@ class KodiMonitor(xbmc.Monitor):
if method == "Player.OnPlay":
with app.APP.lock_playqueues:
self.PlayBackStart(data)
elif method == 'Player.OnAVChange':
with app.APP.lock_playqueues:
self.on_av_change()
elif method == "Player.OnStop":
with app.APP.lock_playqueues:
_playback_cleanup(ended=data.get('end'))
@ -358,8 +362,54 @@ class KodiMonitor(xbmc.Monitor):
# Kodi version < 17
pass
LOG.debug('Set the player state: %s', status)
# Workaround for the Kodi add-on Up Next
if not app.SYNC.direct_paths:
_notify_upnext(item)
self._switch_to_plex_streams = item
def on_av_change(self):
"""
Will be called when Kodi has a video, audio or subtitle stream. Also
happens when the stream changes.
"""
if self._switch_to_plex_streams is not None:
self.switch_to_plex_streams(self._switch_to_plex_streams)
self._switch_to_plex_streams = None
@staticmethod
def switch_to_plex_streams(item):
"""
Override Kodi audio and subtitle streams with Plex PMS' selection
"""
for typus in ('audio', 'subtitle'):
try:
plex_index, language_tag = item.active_plex_stream_index(typus)
except TypeError:
if typus == 'subtitle':
LOG.info('Deactivating Kodi subtitles because the PMS '
'told us to not show any subtitles')
app.APP.player.showSubtitles(False)
continue
LOG.info('The PMS wants to display %s stream with Plex id %s and '
'languageTag %s',
typus, plex_index, language_tag)
kodi_index = item.kodi_stream_index(plex_index,
typus)
if kodi_index is None:
LOG.info('Leaving Kodi %s stream settings untouched since we '
'could not parse Plex %s stream with id %s to a Kodi '
'index', typus, typus, plex_index)
else:
LOG.info('Switching to Kodi %s stream number %s because the '
'PMS told us to show stream with Plex id %s',
typus, kodi_index, plex_index)
# If we're choosing an "illegal" index, this function does
# need seem to fail nor log any errors
if typus == 'subtitle':
app.APP.player.setSubtitleStream(kodi_index)
else:
app.APP.player.setAudioStream(kodi_index)
def _playback_cleanup(ended=False):

View file

@ -339,13 +339,6 @@ def audio_subtitle_prefs(api, item):
return
part_id = mediastreams.attrib['id']
if item.playmethod != v.PLAYBACK_METHOD_TRANSCODE:
LOG.debug('Telling PMS we are not burning in any subtitles')
args = {
'subtitleStreamID': 0
}
DU().downloadUrl('{server}/library/parts/%s' % part_id,
action_type='PUT',
parameters=args)
return True
return setup_transcoding_audio_subtitle_prefs(mediastreams, part_id)
@ -456,7 +449,8 @@ def setup_transcoding_audio_subtitle_prefs(mediastreams, part_id):
select_subs_index = subtitle_streams_list[resp - 1]
# Now prep the PMS for our choice
args = {
'subtitleStreamID': select_subs_index
'subtitleStreamID': select_subs_index,
'allParts': 1
}
DU().downloadUrl('{server}/library/parts/%s' % part_id,
action_type='PUT',

View file

@ -14,13 +14,11 @@ from . import utils
from . import json_rpc as js
from . import variables as v
from . import app
from .subtitles import accessible_plex_subtitles
###############################################################################
LOG = getLogger('PLEX.playlist_func')
###############################################################################
class PlaylistError(Exception):
"""
@ -232,16 +230,16 @@ class PlaylistItem(object):
iterator = self.xml[0][self.part]
# Kodi indexes differently than Plex
for stream in iterator:
if (stream.attrib['streamType'] == stream_type and
if (stream.get('streamType') == stream_type and
'key' in stream.attrib):
if count == kodi_stream_index:
return stream.attrib['id']
return stream.get('id')
count += 1
for stream in iterator:
if (stream.attrib['streamType'] == stream_type and
if (stream.get('streamType') == stream_type and
'key' not in stream.attrib):
if count == kodi_stream_index:
return stream.attrib['id']
return stream.get('id')
count += 1
def kodi_stream_index(self, plex_stream_index, stream_type):
@ -253,20 +251,45 @@ class PlaylistItem(object):
Returns None if unsuccessful
"""
if plex_stream_index is None:
return
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
count = 0
streams = self.sorted_accessible_plex_subtitles(stream_type)
for stream in streams:
if utils.cast(int, stream.get('id')) == plex_stream_index:
return count
count += 1
def active_plex_stream_index(self, stream_type):
"""
Returns the following tuple for the active stream on the Plex side:
(id [int], languageTag [str])
Returns None if no stream has been selected
"""
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
for stream in self.xml[0][self.part]:
if (stream.attrib['streamType'] == stream_type and
'key' in stream.attrib):
if stream.attrib['id'] == plex_stream_index:
return count
count += 1
for stream in self.xml[0][self.part]:
if (stream.attrib['streamType'] == stream_type and
'key' not in stream.attrib):
if stream.attrib['id'] == plex_stream_index:
return count
count += 1
if stream.get('streamType') == stream_type \
and stream.get('selected') == '1':
return (utils.cast(int, stream.get('id')),
stream.get('languageTag'))
def sorted_accessible_plex_subtitles(self, stream_type):
"""
Returns only the subtitles that Kodi can access when PKC Direct Paths
are used; i.e. Kodi has access to a video's directory.
NOT supported: additional subtitles downloaded using the Plex interface
"""
# The playqueue response from the PMS does not contain a stream filename
# thanks Plex
if stream_type == '3':
streams = accessible_plex_subtitles(self.playmethod,
self.file,
self.xml[0][self.part])
else:
streams = [x for x in self.xml[0][self.part]
if x.get('streamType') == stream_type]
return streams
def playlist_item_from_kodi(kodi_item):

472
resources/lib/subtitles.py Normal file
View file

@ -0,0 +1,472 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from logging import getLogger
import re
from os import path
import xml.etree.ElementTree as etree
from . import app
from . import path_ops
from . import variables as v
LOG = getLogger('PLEX.subtitles')
# See https://kodi.wiki/view/Subtitles
SUBTITLE_LANGUAGE = re.compile(r'''(?i)[\. -]*(.*?)([\. -]forced)?$''')
# Plex support for external subtitles: srt, smi, ssa, aas, vtt
# https://support.plex.tv/articles/200471133-adding-local-subtitles-to-your-media/
# Which subtitles files are picked up by Kodi, what extensions do they need?
KODI_SUBTITLE_EXTENSIONS = ('srt', 'ssa', 'ass', 'usf', 'cdg', 'idx', 'sub',
'utf', 'aqt', 'jss', 'psb', 'rt', 'smi', 'txt',
'smil', 'stl', 'dks', 'pjs', 'mpl2', 'mks')
# Official language designations. Tuples consist of
# (ISO language name, ISO 639-1, ISO 639-2, ISO 639-2/B)
# source: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
LANGUAGE_ISO_CODES = (
('abkhazian', 'ab', 'abk', 'abk'),
('afar', 'aa', 'aar', 'aar'),
('afrikaans', 'af', 'afr', 'afr'),
('akan', 'ak', 'aka', 'aka'),
('albanian', 'sq', 'sqi', 'alb'),
('amharic', 'am', 'amh', 'amh'),
('arabic', 'ar', 'ara', 'ara'),
('aragonese', 'an', 'arg', 'arg'),
('armenian', 'hy', 'hye', 'arm'),
('assamese', 'as', 'asm', 'asm'),
('avaric', 'av', 'ava', 'ava'),
('avestan', 'ae', 'ave', 'ave'),
('aymara', 'ay', 'aym', 'aym'),
('azerbaijani', 'az', 'aze', 'aze'),
('bambara', 'bm', 'bam', 'bam'),
('bashkir', 'ba', 'bak', 'bak'),
('basque', 'eu', 'eus', 'baq'),
('belarusian', 'be', 'bel', 'bel'),
('bengali', 'bn', 'ben', 'ben'),
('bislama', 'bi', 'bis', 'bis'),
('bosnian', 'bs', 'bos', 'bos'),
('breton', 'br', 'bre', 'bre'),
('bulgarian', 'bg', 'bul', 'bul'),
('burmese', 'my', 'mya', 'bur'),
('catalan', 'ca', 'cat', 'cat'),
('chamorro', 'ch', 'cha', 'cha'),
('chechen', 'ce', 'che', 'che'),
('chichewa', 'ny', 'nya', 'nya'),
('chinese', 'zh', 'zho', 'chi'),
('chuvash', 'cv', 'chv', 'chv'),
('cornish', 'kw', 'cor', 'cor'),
('corsican', 'co', 'cos', 'cos'),
('cree', 'cr', 'cre', 'cre'),
('croatian', 'hr', 'hrv', 'hrv'),
('czech', 'cs', 'ces', 'cze'),
('danish', 'da', 'dan', 'dan'),
('divehi', 'dv', 'div', 'div'),
('dutch', 'nl', 'nld', 'dut'),
('dzongkha', 'dz', 'dzo', 'dzo'),
('english', 'en', 'eng', 'eng'),
('esperanto', 'eo', 'epo', 'epo'),
('estonian', 'et', 'est', 'est'),
('ewe', 'ee', 'ewe', 'ewe'),
('faroese', 'fo', 'fao', 'fao'),
('fijian', 'fj', 'fij', 'fij'),
('finnish', 'fi', 'fin', 'fin'),
('french', 'fr', 'fra', 'fre'),
('fulah', 'ff', 'ful', 'ful'),
('galician', 'gl', 'glg', 'glg'),
('georgian', 'ka', 'kat', 'geo'),
('german', 'de', 'deu', 'ger'),
('greek', 'el', 'ell', 'gre'),
('guarani', 'gn', 'grn', 'grn'),
('gujarati', 'gu', 'guj', 'guj'),
('haitian', 'ht', 'hat', 'hat'),
('hausa', 'ha', 'hau', 'hau'),
('hebrew', 'he', 'heb', 'heb'),
('herero', 'hz', 'her', 'her'),
('hindi', 'hi', 'hin', 'hin'),
('hiri motu', 'ho', 'hmo', 'hmo'),
('hungarian', 'hu', 'hun', 'hun'),
('interlingua', 'ia', 'ina', 'ina'),
('indonesian', 'id', 'ind', 'ind'),
('interlingue', 'ie', 'ile', 'ile'),
('irish', 'ga', 'gle', 'gle'),
('igbo', 'ig', 'ibo', 'ibo'),
('inupiaq', 'ik', 'ipk', 'ipk'),
('ido', 'io', 'ido', 'ido'),
('icelandic', 'is', 'isl', 'ice'),
('italian', 'it', 'ita', 'ita'),
('inuktitut', 'iu', 'iku', 'iku'),
('japanese', 'ja', 'jpn', 'jpn'),
('javanese', 'jv', 'jav', 'jav'),
('kalaallisut', 'kl', 'kal', 'kal'),
('kannada', 'kn', 'kan', 'kan'),
('kanuri', 'kr', 'kau', 'kau'),
('kashmiri', 'ks', 'kas', 'kas'),
('kazakh', 'kk', 'kaz', 'kaz'),
('central khmer', 'km', 'khm', 'khm'),
('kikuyu', 'ki', 'kik', 'kik'),
('kinyarwanda', 'rw', 'kin', 'kin'),
('kirghiz', 'ky', 'kir', 'kir'),
('komi', 'kv', 'kom', 'kom'),
('kongo', 'kg', 'kon', 'kon'),
('korean', 'ko', 'kor', 'kor'),
('kurdish', 'ku', 'kur', 'kur'),
('kuanyama', 'kj', 'kua', 'kua'),
('latin', 'la', 'lat', 'lat'),
('luxembourgish', 'lb', 'ltz', 'ltz'),
('ganda', 'lg', 'lug', 'lug'),
('limburgan', 'li', 'lim', 'lim'),
('lingala', 'ln', 'lin', 'lin'),
('lao', 'lo', 'lao', 'lao'),
('lithuanian', 'lt', 'lit', 'lit'),
('luba-katanga', 'lu', 'lub', 'lub'),
('latvian', 'lv', 'lav', 'lav'),
('manx', 'gv', 'glv', 'glv'),
('macedonian', 'mk', 'mkd', 'mac'),
('malagasy', 'mg', 'mlg', 'mlg'),
('malay', 'ms', 'msa', 'may'),
('malayalam', 'ml', 'mal', 'mal'),
('maltese', 'mt', 'mlt', 'mlt'),
('maori', 'mi', 'mri', 'mao'),
('marathi', 'mr', 'mar', 'mar'),
('marshallese', 'mh', 'mah', 'mah'),
('mongolian', 'mn', 'mon', 'mon'),
('nauru', 'na', 'nau', 'nau'),
('navajo', 'nv', 'nav', 'nav'),
('north ndebele', 'nd', 'nde', 'nde'),
('nepali', 'ne', 'nep', 'nep'),
('ndonga', 'ng', 'ndo', 'ndo'),
('norwegian bokmål', 'nb', 'nob', 'nob'),
('norwegian nynorsk', 'nn', 'nno', 'nno'),
('norwegian', 'no', 'nor', 'nor'),
('sichuan yi', 'ii', 'iii', 'iii'),
('south ndebele', 'nr', 'nbl', 'nbl'),
('occitan', 'oc', 'oci', 'oci'),
('ojibwa', 'oj', 'oji', 'oji'),
('church slavic', 'cu', 'chu', 'chu'),
('oromo', 'om', 'orm', 'orm'),
('oriya', 'or', 'ori', 'ori'),
('ossetian', 'os', 'oss', 'oss'),
('punjabi', 'pa', 'pan', 'pan'),
('pali', 'pi', 'pli', 'pli'),
('persian', 'fa', 'fas', 'per'),
('polish', 'pl', 'pol', 'pol'),
('pashto', 'ps', 'pus', 'pus'),
('portuguese', 'pt', 'por', 'por'),
('quechua', 'qu', 'que', 'que'),
('romansh', 'rm', 'roh', 'roh'),
('rundi', 'rn', 'run', 'run'),
('romanian', 'ro', 'ron', 'rum'),
('russian', 'ru', 'rus', 'rus'),
('sanskrit', 'sa', 'san', 'san'),
('sardinian', 'sc', 'srd', 'srd'),
('sindhi', 'sd', 'snd', 'snd'),
('northern sami', 'se', 'sme', 'sme'),
('samoan', 'sm', 'smo', 'smo'),
('sango', 'sg', 'sag', 'sag'),
('serbian', 'sr', 'srp', 'srp'),
('gaelic', 'gd', 'gla', 'gla'),
('shona', 'sn', 'sna', 'sna'),
('sinhala', 'si', 'sin', 'sin'),
('slovak', 'sk', 'slk', 'slo'),
('slovenian', 'sl', 'slv', 'slv'),
('somali', 'so', 'som', 'som'),
('southern sotho', 'st', 'sot', 'sot'),
('spanish', 'es', 'spa', 'spa'),
('sundanese', 'su', 'sun', 'sun'),
('swahili', 'sw', 'swa', 'swa'),
('swati', 'ss', 'ssw', 'ssw'),
('swedish', 'sv', 'swe', 'swe'),
('tamil', 'ta', 'tam', 'tam'),
('telugu', 'te', 'tel', 'tel'),
('tajik', 'tg', 'tgk', 'tgk'),
('thai', 'th', 'tha', 'tha'),
('tigrinya', 'ti', 'tir', 'tir'),
('tibetan', 'bo', 'bod', 'tib'),
('turkmen', 'tk', 'tuk', 'tuk'),
('tagalog', 'tl', 'tgl', 'tgl'),
('tswana', 'tn', 'tsn', 'tsn'),
('tonga', 'to', 'ton', 'ton'),
('turkish', 'tr', 'tur', 'tur'),
('tsonga', 'ts', 'tso', 'tso'),
('tatar', 'tt', 'tat', 'tat'),
('twi', 'tw', 'twi', 'twi'),
('tahitian', 'ty', 'tah', 'tah'),
('uighur', 'ug', 'uig', 'uig'),
('ukrainian', 'uk', 'ukr', 'ukr'),
('urdu', 'ur', 'urd', 'urd'),
('uzbek', 'uz', 'uzb', 'uzb'),
('venda', 've', 'ven', 'ven'),
('vietnamese', 'vi', 'vie', 'vie'),
('volapük', 'vo', 'vol', 'vol'),
('walloon', 'wa', 'wln', 'wln'),
('welsh', 'cy', 'cym', 'wel'),
('wolof', 'wo', 'wol', 'wol'),
('western frisian', 'fy', 'fry', 'fry'),
('xhosa', 'xh', 'xho', 'xho'),
('yiddish', 'yi', 'yid', 'yid'),
('yoruba', 'yo', 'yor', 'yor'),
('zhuang', 'za', 'zha', 'zha'),
('zulu', 'zu', 'zul', 'zul'),
)
def accessible_plex_subtitles(playmethod, playing_file, xml_streams):
if not playmethod == v.PLAYBACK_METHOD_DIRECT_PATH:
# We can access all subtitles because we're downloading additional
# external ones into the Kodi PKC add-on directory
streams = []
# Kodi ennumerates EXTERNAL subtitles first, then internal ones
for stream in xml_streams:
if stream.get('streamType') == '3' and 'key' in stream.attrib:
streams.append(stream)
for stream in xml_streams:
if stream.get('streamType') == '3' and 'key' not in stream.attrib:
streams.append(stream)
if streams:
LOG.debug('Working with the following Plex subtitle streams:')
log_plex_streams(streams)
return streams
kodi_subs = kodi_subs_from_player()
plex_streams_int, plex_streams_ext = accessible_plex_sub_streams(xml_streams)
# Kodi appends internal streams at the end of its list
kodi_subs_ext = kodi_subs[:len(kodi_subs) - len(plex_streams_int)]
LOG.debug('Kodi list of external subs: %s', kodi_subs_ext)
LOG.debug('Kodi has %s external subs, Plex %s, trying to match them',
len(kodi_subs_ext), len(plex_streams_ext))
dirname, basename = path.split(playing_file)
filename, _ = path.splitext(basename)
try:
kodi_subs_file = kodi_external_subs(dirname, filename, kodi_subs_ext)
reordered_plex_streams_ext = reorder_plex_streams(plex_streams_ext,
kodi_subs_file)
except SubtitleError:
# Add dummy subtitles so we won't match against Plex subtitles that
# are in an incorrect order - keeps Kodi order of subs intact
reordered_plex_streams_ext = [DummySub()
for _ in range(len(kodi_subs_ext))]
reordered_plex_streams_ext.extend(plex_streams_int)
return reordered_plex_streams_ext
def reorder_plex_streams(plex_streams_ext, kodi_subs_file):
"""
Returns the Plex streams in a "best-guess" order as indicated by the
Kodi external subtitles kodi_subs_file
"""
order = [None for i in range(len(kodi_subs_file))]
# Pick subtitles with known language, extension and "forced" True first
for i, kodi_sub in enumerate(kodi_subs_file):
if not kodi_sub['iso'] or not kodi_sub['forced']:
continue
for plex_stream in plex_streams_ext:
if not plex_stream.get('forced'):
continue
elif not kodi_sub['iso'][1] == plex_stream.get('languageTag'):
continue
elif not kodi_sub['codec'] == plex_stream.get('codec').lower():
continue
# Pick the first matching result - even though it's a best guess
order[i] = plex_stream
plex_streams_ext.remove(plex_stream)
break
# Pick non-forced
for i, kodi_sub in enumerate(kodi_subs_file):
if order[i] is not None or not kodi_sub['iso']:
continue
for plex_stream in plex_streams_ext:
if not kodi_sub['iso'][1] == plex_stream.get('languageTag'):
continue
elif not kodi_sub['codec'] == plex_stream.get('codec').lower():
continue
elif not (kodi_sub['forced'] is (plex_stream.get('forced') == '1')):
continue
# Pick the first matching result - even though it's a best guess
order[i] = plex_stream
plex_streams_ext.remove(plex_stream)
break
# Pick subs irrelevant of forced flag
for i, kodi_sub in enumerate(kodi_subs_file):
if order[i] is not None or not kodi_sub['iso']:
continue
for plex_stream in plex_streams_ext:
if not kodi_sub['iso'][1] == plex_stream.get('languageTag'):
continue
elif not kodi_sub['codec'] == plex_stream.get('codec').lower():
continue
# Pick the first matching result - even though it's a best guess
order[i] = plex_stream
plex_streams_ext.remove(plex_stream)
break
# Pick subs based on codec (Plex does not detect "English" as en). Forced
# ones first
for i, kodi_sub in enumerate(kodi_subs_file):
if order[i] is not None or not kodi_sub['forced']:
continue
for plex_stream in plex_streams_ext:
if not kodi_sub['codec'] == plex_stream.get('codec').lower():
continue
elif not plex_stream.get('forced'):
continue
elif plex_stream.get('languageTag') and kodi_sub['iso'] \
and not plex_stream.get('languageTag') == kodi_sub['iso'][1]:
continue
# Pick the first matching result - even though it's a best guess
order[i] = plex_stream
plex_streams_ext.remove(plex_stream)
break
# Pick subs based on codec alone (Plex does not detect "English" as en).
# Non-forced
for i, kodi_sub in enumerate(kodi_subs_file):
if order[i] is not None:
continue
for plex_stream in plex_streams_ext:
if not kodi_sub['codec'] == plex_stream.get('codec').lower():
continue
elif not (kodi_sub['forced'] is (plex_stream.get('forced') == '1')):
continue
elif plex_stream.get('languageTag') and kodi_sub['iso'] \
and not plex_stream.get('languageTag') == kodi_sub['iso'][1]:
continue
# Pick the first matching result - even though it's a best guess
order[i] = plex_stream
plex_streams_ext.remove(plex_stream)
break
# Pick subs based on codec alone (Plex does not detect "English" as en).
# Even with miss-matching forced flag
for i, kodi_sub in enumerate(kodi_subs_file):
if order[i] is not None:
continue
for plex_stream in plex_streams_ext:
if not kodi_sub['codec'] == plex_stream.get('codec').lower():
continue
elif plex_stream.get('languageTag') and kodi_sub['iso'] \
and not plex_stream.get('languageTag') == kodi_sub['iso'][1]:
continue
# Pick the first matching result - even though it's a best guess
order[i] = plex_stream
plex_streams_ext.remove(plex_stream)
break
# Now lets add dummies for Kodi subs we could not match
for i, kodi_sub in enumerate(kodi_subs_file):
if order[i] is not None:
continue
LOG.debug('Could not match Kodi sub number %s %s, adding a dummy',
i, kodi_sub)
order[i] = DummySub()
if plex_streams_ext:
LOG.debug('We could not match the following Plex subtitles:')
log_plex_streams(plex_streams_ext)
if order:
LOG.debug('Derived order of external subtitle streams:')
log_plex_streams(order)
return order
def log_plex_streams(plex_streams):
for i, stream in enumerate(plex_streams):
LOG.debug('Number %s: %s: %s', i, stream.tag, stream.attrib)
def accessible_plex_sub_streams(xml):
# Any additionally downloaded subtitles are not accessible for Kodi
# We're identifying them by the additional key 'providerTitle'
plex_streams = [stream for stream in xml
if stream.get('streamType') == '3'
and not stream.get('providerTitle')]
LOG.debug('Available Plex subtitle streams for currently playing item:')
log_plex_streams(plex_streams)
# Kodi can display internal subtitle streams for sure
plex_streams_int = [x for x in plex_streams if 'key' not in x.attrib]
# We need to check external ones
# If the movie name is 'The Dark Knight (2008).mkv', Kodi finds
# subtitles 'The Dark Knight (2008)*.*.<ext>'
plex_streams_ext = [x for x in plex_streams if 'key' in x.attrib]
return plex_streams_int, plex_streams_ext
def kodi_subs_from_player():
"""
Kodi can only play subtitles that it pickes up itself: They lie in the
same folder as the video file and are named similarly
"""
kodi_subs = app.APP.player.getAvailableSubtitleStreams()
LOG.debug('Kodi list of available subtitles: %s', kodi_subs)
return kodi_subs
def kodi_external_subs(dirname, filename, kodi_subs_ext):
file_subs = external_subs_from_filesystem(dirname, filename)
if len(file_subs) != len(kodi_subs_ext):
LOG.warn('Unexpected missmatch of number of Kodi subtitles')
LOG.warn('Kodi subs: %s', kodi_subs_ext)
LOG.warn('Subs from the filesystem: %s', file_subs)
raise SubtitleError()
for i, sub in enumerate(file_subs):
if sub['iso'] and kodi_subs_ext[i].lower() not in sub['iso']:
LOG.warn('Unexpected Kodi external subtitle language combo')
LOG.warn('Kodi subs: %s', kodi_subs_ext)
LOG.warn('Subs from the filesystem: %s', file_subs)
raise SubtitleError()
return file_subs
def external_subs_from_filesystem(dirname, filename):
"""
Returns a list of dicts of subtitles lying within the directory dirname:
{'iso': tuple of detected ISO language (see LANGUAGE_ISO_CODES) or None,
'language': language string that Kodi might show in its GUI,
'forced': has '[. -]forced' been appended to the filename?
'file': subtitle file name}
Supply with the currently playing filename as Kodi uses that to search
for subtitles. See https://kodi.wiki/view/Subtitles
"""
file_subs = []
for root, dirs, files in path_ops.walk(dirname):
for file in files:
name, extension = path.splitext(file)
# Get rid of the dot and force lowercase
extension = extension[1:].lower()
if extension not in KODI_SUBTITLE_EXTENSIONS:
# Not an extension Kodi supports
continue
elif not name.startswith(filename):
# Naming not up to standards, Kodi won't pick up this file
# (but Plex might!!)
continue
regex = SUBTITLE_LANGUAGE.search(name.replace(filename, '', 1))
language = (regex[1] or '').lower()
forced = True if regex[2] else False
iso = None
if len(language) == 2:
language_searchgrid = (1, )
elif len(language) == 3:
language_searchgrid = (2, 3)
else:
language_searchgrid = (0, )
for lang in LANGUAGE_ISO_CODES:
for i in language_searchgrid:
if lang[i] == language:
iso = lang
break
else:
continue
break
file_subs.append({'iso': iso,
'language': language,
'forced': forced,
'codec': extension,
'file': '%s.%s' % (name, extension)})
LOG.debug('Detected these external subtitles while scanning the file '
'system: %s', file_subs)
return file_subs
class DummySub(etree.Element):
def __init__(self):
super(DummySub, self).__init__('Stream-subtitle-dummy')
class SubtitleError(Exception):
pass

View file

@ -89,15 +89,18 @@ MIN_DB_VERSION = '3.2.1'
# Supported databases - version numbers in tuples should decrease
SUPPORTED_VIDEO_DB = {
# Kodi 19 - EXTREMLY EXPERIMENTAL!
19: (119, )
19: (119, ),
20: (119, ),
}
SUPPORTED_MUSIC_DB = {
# Kodi 19 - EXTREMLY EXPERIMENTAL!
19: (82, )
19: (82, ),
20: (82, ),
}
SUPPORTED_TEXTURE_DB = {
# Kodi 19 - EXTREMLY EXPERIMENTAL!
19: (13, )
19: (13, ),
20: (13, ),
}
DB_VIDEO_VERSION = None
DB_VIDEO_PATH = None
@ -514,7 +517,8 @@ ALL_KODI_ARTWORK = (
'clearart',
'clearlogo',
'fanart',
'discart'
'discart',
'landscape'
)
# we need to use a little mapping between fanart.tv arttypes and kodi artttypes
@ -528,7 +532,8 @@ FANART_TV_TO_KODI_TYPE = [
('clearlogo', 'clearlogo'),
('background', 'fanart'),
('showbackground', 'fanart'),
('characterart', 'characterart')
('characterart', 'characterart'),
('thumb', 'landscape')
]
# How many different backgrounds do we want to load from fanart.tv?
MAX_BACKGROUND_COUNT = 10
@ -682,7 +687,7 @@ def database_paths():
unsupported version
'''
# Check Kodi version first
if KODIVERSION not in (19, ):
if KODIVERSION not in (19, 20):
raise RuntimeError('Kodiversion %s not supported by PKC' % KODIVERSION)
database_path = xbmcvfs.translatePath('special://database')