Merge pull request #1615 from croneter/beta-version
Bump Python2 Master
This commit is contained in:
commit
560fc5b9c8
8 changed files with 591 additions and 30 deletions
|
@ -49,6 +49,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
|
||||
- [Skip intros](https://support.plex.tv/articles/skip-content/)
|
||||
- [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/)
|
||||
|
|
13
addon.xml
13
addon.xml
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.14.0" provider-name="croneter">
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.14.2" provider-name="croneter">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
<import addon="script.module.requests" version="2.9.1" />
|
||||
|
@ -88,7 +88,16 @@
|
|||
<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 2.14.0:
|
||||
<news>version 2.14.2:
|
||||
- version 2.14.1 for everyone
|
||||
|
||||
version 2.14.1 (beta only):
|
||||
- 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 2.14.0:
|
||||
- Fix PlexKodiConnect changing or removing subtitles for every video on the PMS
|
||||
- version 2.13.1-2.13.2 for everyone
|
||||
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
version 2.14.2:
|
||||
- version 2.14.1 for everyone
|
||||
|
||||
version 2.14.1 (beta only):
|
||||
- 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 2.14.0:
|
||||
- Fix PlexKodiConnect changing or removing subtitles for every video on the PMS
|
||||
- version 2.13.1-2.13.2 for everyone
|
||||
|
|
|
@ -29,6 +29,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)
|
||||
|
@ -64,6 +65,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'))
|
||||
|
@ -359,8 +363,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):
|
||||
|
|
|
@ -340,13 +340,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)
|
||||
|
||||
|
@ -457,7 +450,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',
|
||||
|
|
|
@ -15,13 +15,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):
|
||||
"""
|
||||
|
@ -240,16 +238,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):
|
||||
|
@ -261,20 +259,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
|
||||
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:
|
||||
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' 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):
|
||||
|
|
473
resources/lib/subtitles.py
Normal file
473
resources/lib/subtitles.py
Normal file
|
@ -0,0 +1,473 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
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.group(1) if regex.group(1) else '').lower()
|
||||
forced = True if regex.group(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
|
|
@ -542,7 +542,8 @@ ALL_KODI_ARTWORK = (
|
|||
'clearart',
|
||||
'clearlogo',
|
||||
'fanart',
|
||||
'discart'
|
||||
'discart',
|
||||
'landscape'
|
||||
)
|
||||
|
||||
# we need to use a little mapping between fanart.tv arttypes and kodi artttypes
|
||||
|
@ -556,7 +557,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
|
||||
|
|
Loading…
Reference in a new issue