Merge pull request #1672 from croneter/beta-version
Bump Python 2 Master
This commit is contained in:
commit
ee1eb14476
51 changed files with 839 additions and 230 deletions
21
addon.xml
21
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.14.2" provider-name="croneter">
|
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.15.0" 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" />
|
||||||
|
@ -88,7 +88,24 @@
|
||||||
<summary lang="ko_KR">Plex를 Kodi에 기본 통합</summary>
|
<summary lang="ko_KR">Plex를 Kodi에 기본 통합</summary>
|
||||||
<description lang="ko_KR">Kodi를 Plex Media Server에 연결합니다. 이 플러그인은 Plex로 모든 비디오를 관리하고 Kodi로는 관리하지 않는다고 가정합니다. Kodi 비디오 및 음악 데이터베이스에 이미 저장된 데이터가 손실 될 수 있습니다 (이 플러그인이 직접 변경하므로). 자신의 책임하에 사용하십시오!</description>
|
<description lang="ko_KR">Kodi를 Plex Media Server에 연결합니다. 이 플러그인은 Plex로 모든 비디오를 관리하고 Kodi로는 관리하지 않는다고 가정합니다. Kodi 비디오 및 음악 데이터베이스에 이미 저장된 데이터가 손실 될 수 있습니다 (이 플러그인이 직접 변경하므로). 자신의 책임하에 사용하십시오!</description>
|
||||||
<disclaimer lang="ko_KR">자신의 책임하에 사용</disclaimer>
|
<disclaimer lang="ko_KR">자신의 책임하에 사용</disclaimer>
|
||||||
<news>version 2.14.2:
|
<news>version 2.15.0:
|
||||||
|
- versions 2.14.3-2.14.4 for everyone
|
||||||
|
- Direct Paths: Fix TypeError: "element indices must be integers" on playback startup [backport]
|
||||||
|
- Refactor stream code and fix Kodi not activating subtitle when it should [backport]
|
||||||
|
- Add playback settings to let the user choose whether Plex or Kodi provides the default audio or subtitle stream on playback start [backport]
|
||||||
|
- Update translations from Transifex [backport]
|
||||||
|
|
||||||
|
version 2.14.4 (beta only):
|
||||||
|
- Tell the PMS if a video's audio stream or potentially subtitle stream has changed. For subtitles, this functionality is broken due to a Kodi bug
|
||||||
|
- Transcoding: Fix Plex burning-in subtitles when it should not
|
||||||
|
- Fix logging if fanart.tv lookup fails: be less verbose
|
||||||
|
- Large refactoring of playlist and playqueue code
|
||||||
|
- Refactor usage of a media part's id
|
||||||
|
|
||||||
|
version 2.14.3 (beta only):
|
||||||
|
- Implement "Reset resume position" from the Kodi context menu
|
||||||
|
|
||||||
|
version 2.14.2:
|
||||||
- version 2.14.1 for everyone
|
- version 2.14.1 for everyone
|
||||||
|
|
||||||
version 2.14.1 (beta only):
|
version 2.14.1 (beta only):
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
version 2.15.0:
|
||||||
|
- versions 2.14.3-2.14.4 for everyone
|
||||||
|
- Direct Paths: Fix TypeError: "element indices must be integers" on playback startup [backport]
|
||||||
|
- Refactor stream code and fix Kodi not activating subtitle when it should [backport]
|
||||||
|
- Add playback settings to let the user choose whether Plex or Kodi provides the default audio or subtitle stream on playback start [backport]
|
||||||
|
- Update translations from Transifex [backport]
|
||||||
|
|
||||||
|
version 2.14.4 (beta only):
|
||||||
|
- Tell the PMS if a video's audio stream or potentially subtitle stream has changed. For subtitles, this functionality is broken due to a Kodi bug
|
||||||
|
- Transcoding: Fix Plex burning-in subtitles when it should not
|
||||||
|
- Fix logging if fanart.tv lookup fails: be less verbose
|
||||||
|
- Large refactoring of playlist and playqueue code
|
||||||
|
- Refactor usage of a media part's id
|
||||||
|
|
||||||
|
version 2.14.3 (beta only):
|
||||||
|
- Implement "Reset resume position" from the Kodi context menu
|
||||||
|
|
||||||
version 2.14.2:
|
version 2.14.2:
|
||||||
- version 2.14.1 for everyone
|
- version 2.14.1 for everyone
|
||||||
|
|
||||||
|
|
|
@ -702,6 +702,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Vynutit překódování obrázků"
|
msgstr "Vynutit překódování obrázků"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -702,6 +702,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Transcode billeder"
|
msgstr "Transcode billeder"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -715,6 +715,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Bilder immer transkodieren"
|
msgstr "Bilder immer transkodieren"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr "Ersten Videostream wählen, wenn mehrere Versionen vorhanden sind"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr "Wer wählt den Audiotrack beim Start der Wiedergabe?"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr "Wer wählt Untertitel beim Start der Wiedergabe?"
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -690,6 +690,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -665,6 +665,16 @@ msgctxt "#30546"
|
||||||
msgid "Pick the first video if several versions are present"
|
msgid "Pick the first video if several versions are present"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -710,6 +710,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Obligar transcodificar fotografías"
|
msgstr "Obligar transcodificar fotografías"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -712,6 +712,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Obligar transcodificar fotografías"
|
msgstr "Obligar transcodificar fotografías"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -710,6 +710,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Obligar transcodificar fotografías"
|
msgstr "Obligar transcodificar fotografías"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -715,6 +715,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Forcer le transcodage des images"
|
msgstr "Forcer le transcodage des images"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -719,6 +719,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Forcer le transcodage des images"
|
msgstr "Forcer le transcodage des images"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -718,6 +718,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Képek transzkódolásának kényszerítése"
|
msgstr "Képek transzkódolásának kényszerítése"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -710,6 +710,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Forza transcodifica immagini"
|
msgstr "Forza transcodifica immagini"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -697,6 +697,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "사진 트랜스 코딩 강제"
|
msgstr "사진 트랜스 코딩 강제"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -706,6 +706,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Priverstinai perkoduoti nuotraukas"
|
msgstr "Priverstinai perkoduoti nuotraukas"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -699,6 +699,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Uzspiest attēlu pārkodēšanu"
|
msgstr "Uzspiest attēlu pārkodēšanu"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -707,6 +707,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Forceer transcoden van foto's"
|
msgstr "Forceer transcoden van foto's"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -704,6 +704,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Tving transkoding av bilde"
|
msgstr "Tving transkoding av bilde"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# Croneter None <croneter@gmail.com>, 2017
|
# Croneter None <croneter@gmail.com>, 2017
|
||||||
# Wiktor Dackiewicz <wiktor.dackiewicz@gmail.com>, 2017
|
# Wiktor Dackiewicz <wiktor.dackiewicz@gmail.com>, 2017
|
||||||
# Kacpolz <kacperbrzozowski123@gmail.com>, 2019
|
# Kacpolz <kacperbrzozowski123@gmail.com>, 2019
|
||||||
# Top Secret <spam210195@gmail.com>, 2020
|
# Ziuta <spam210195@gmail.com>, 2020
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -11,7 +11,7 @@ msgstr ""
|
||||||
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
|
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
|
||||||
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
|
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
|
||||||
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
|
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
|
||||||
"Last-Translator: Top Secret <spam210195@gmail.com>, 2020\n"
|
"Last-Translator: Ziuta <spam210195@gmail.com>, 2020\n"
|
||||||
"Language-Team: Polish (Poland) (https://www.transifex.com/croneter/teams/73837/pl_PL/)\n"
|
"Language-Team: Polish (Poland) (https://www.transifex.com/croneter/teams/73837/pl_PL/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
@ -695,6 +695,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -694,6 +694,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Forçar transcodificação de imagens"
|
msgstr "Forçar transcodificação de imagens"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -697,6 +697,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Forçar transcodificação de imagens"
|
msgstr "Forçar transcodificação de imagens"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -711,6 +711,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Принудительно транскодировать изображения"
|
msgstr "Принудительно транскодировать изображения"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -706,6 +706,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Tvinga omkodning av bilder"
|
msgstr "Tvinga omkodning av bilder"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -706,6 +706,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "Примусове перекодування зображень"
|
msgstr "Примусове перекодування зображень"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -689,6 +689,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "强制图片转码"
|
msgstr "强制图片转码"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -687,6 +687,21 @@ msgctxt "#30545"
|
||||||
msgid "Force transcode pictures"
|
msgid "Force transcode pictures"
|
||||||
msgstr "強制圖片轉碼"
|
msgstr "強制圖片轉碼"
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30546"
|
||||||
|
msgid "Pick the first video if several versions are present"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30547"
|
||||||
|
msgid "Who picks the audio stream on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# PKC Settings - Playback
|
||||||
|
msgctxt "#30548"
|
||||||
|
msgid "Who picks subtitles on playback start?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# Welcome to Plex notification
|
# Welcome to Plex notification
|
||||||
msgctxt "#33000"
|
msgctxt "#33000"
|
||||||
msgid "Welcome"
|
msgid "Welcome"
|
||||||
|
|
|
@ -4,19 +4,13 @@ import sqlite3
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from . import variables as v, app
|
from . import variables as v, app
|
||||||
|
from .exceptions import LockedDatabase
|
||||||
|
|
||||||
DB_WRITE_ATTEMPTS = 100
|
DB_WRITE_ATTEMPTS = 100
|
||||||
DB_WRITE_ATTEMPTS_TIMEOUT = 1 # in seconds
|
DB_WRITE_ATTEMPTS_TIMEOUT = 1 # in seconds
|
||||||
DB_CONNECTION_TIMEOUT = 10
|
DB_CONNECTION_TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
class LockedDatabase(Exception):
|
|
||||||
"""
|
|
||||||
Dedicated class to make sure we're not silently catching locked DBs.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def catch_operationalerrors(method):
|
def catch_operationalerrors(method):
|
||||||
"""
|
"""
|
||||||
sqlite.OperationalError is raised immediately if another DB connection
|
sqlite.OperationalError is raised immediately if another DB connection
|
||||||
|
|
|
@ -224,7 +224,11 @@ class DownloadUtils():
|
||||||
if r.status_code != 401:
|
if r.status_code != 401:
|
||||||
self.count_unauthorized = 0
|
self.count_unauthorized = 0
|
||||||
|
|
||||||
if r.status_code == 204:
|
if return_response is True:
|
||||||
|
# return the entire response object
|
||||||
|
return r
|
||||||
|
|
||||||
|
elif r.status_code == 204:
|
||||||
# No body in the response
|
# No body in the response
|
||||||
# But read (empty) content to release connection back to pool
|
# But read (empty) content to release connection back to pool
|
||||||
# (see requests: keep-alive documentation)
|
# (see requests: keep-alive documentation)
|
||||||
|
@ -258,9 +262,6 @@ class DownloadUtils():
|
||||||
elif r.status_code in (200, 201):
|
elif r.status_code in (200, 201):
|
||||||
# 200: OK
|
# 200: OK
|
||||||
# 201: Created
|
# 201: Created
|
||||||
if return_response is True:
|
|
||||||
# return the entire response object
|
|
||||||
return r
|
|
||||||
try:
|
try:
|
||||||
# xml response
|
# xml response
|
||||||
r = utils.defused_etree.fromstring(r.content)
|
r = utils.defused_etree.fromstring(r.content)
|
||||||
|
|
32
resources/lib/exceptions.py
Normal file
32
resources/lib/exceptions.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistError(Exception):
|
||||||
|
"""
|
||||||
|
Exception for our playlist constructs
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LockedDatabase(Exception):
|
||||||
|
"""
|
||||||
|
Dedicated class to make sure we're not silently catching locked DBs.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SubtitleError(Exception):
|
||||||
|
"""
|
||||||
|
Exceptions relating to subtitles
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessingNotDone(Exception):
|
||||||
|
"""
|
||||||
|
Exception to detect whether we've completed our sync and did not have to
|
||||||
|
abort or suspend.
|
||||||
|
"""
|
||||||
|
pass
|
|
@ -421,6 +421,41 @@ def get_item(playerid):
|
||||||
'properties': ['title', 'file']})['result']['item']
|
'properties': ['title', 'file']})['result']['item']
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_audio_stream_index(playerid):
|
||||||
|
"""
|
||||||
|
Returns the currently active audio stream index [int]
|
||||||
|
"""
|
||||||
|
return JsonRPC('Player.GetProperties').execute({
|
||||||
|
'playerid': playerid,
|
||||||
|
'properties': ['currentaudiostream']})['result']['currentaudiostream']['index']
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_subtitle_stream_index(playerid):
|
||||||
|
"""
|
||||||
|
Returns the currently active subtitle stream index [int] or None if there
|
||||||
|
are no subs
|
||||||
|
PICKING UP CHANGES ON SUBTITLES IS CURRENTLY BROKEN ON THE KODI SIDE! The
|
||||||
|
JSON reply won't change even though subtitles are changed :-(
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return JsonRPC('Player.GetProperties').execute({
|
||||||
|
'playerid': playerid,
|
||||||
|
'properties': ['currentsubtitle', ]})['result']['currentsubtitle']['index']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_subtitle_enabled(playerid):
|
||||||
|
"""
|
||||||
|
Returns True if a subtitle is currently enabled, False otherwise.
|
||||||
|
PICKING UP CHANGES ON SUBTITLES IS CURRENTLY BROKEN ON THE KODI SIDE! The
|
||||||
|
JSON reply won't change even though subtitles are changed :-(
|
||||||
|
"""
|
||||||
|
return JsonRPC('Player.GetProperties').execute({
|
||||||
|
'playerid': playerid,
|
||||||
|
'properties': ['subtitleenabled', ]})['result']['subtitleenabled']
|
||||||
|
|
||||||
|
|
||||||
def get_player_props(playerid):
|
def get_player_props(playerid):
|
||||||
"""
|
"""
|
||||||
Returns a dict for the active Kodi player with the following values:
|
Returns a dict for the active Kodi player with the following values:
|
||||||
|
|
|
@ -575,6 +575,22 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
return
|
return
|
||||||
return movie_id, typus
|
return movie_id, typus
|
||||||
|
|
||||||
|
def file_id_from_id(self, kodi_id, kodi_type):
|
||||||
|
"""
|
||||||
|
Returns the Kodi file_id for the item with kodi_id and kodi_type or
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
if kodi_type == v.KODI_TYPE_MOVIE:
|
||||||
|
identifier = 'idMovie'
|
||||||
|
elif kodi_type == v.KODI_TYPE_EPISODE:
|
||||||
|
identifier = 'idEpisode'
|
||||||
|
self.cursor.execute('SELECT idFile FROM %s WHERE %s = ? LIMIT 1'
|
||||||
|
% (kodi_type, identifier), (kodi_id, ))
|
||||||
|
try:
|
||||||
|
return self.cursor.fetchone()[0]
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
def get_resume(self, file_id):
|
def get_resume(self, file_id):
|
||||||
"""
|
"""
|
||||||
Returns the first resume point in seconds (int) if found, else None for
|
Returns the first resume point in seconds (int) if found, else None for
|
||||||
|
|
|
@ -14,11 +14,13 @@ import xbmc
|
||||||
|
|
||||||
from .plex_api import API
|
from .plex_api import API
|
||||||
from .plex_db import PlexDB
|
from .plex_db import PlexDB
|
||||||
|
from .kodi_db import KodiVideoDB
|
||||||
from . import kodi_db
|
from . import kodi_db
|
||||||
from .downloadutils import DownloadUtils as DU
|
from .downloadutils import DownloadUtils as DU
|
||||||
from . import utils, timing, plex_functions as PF
|
from . import utils, timing, plex_functions as PF
|
||||||
from . import json_rpc as js, playqueue as PQ, playlist_func as PL
|
from . import json_rpc as js, playqueue as PQ, playlist_func as PL
|
||||||
from . import backgroundthread, app, variables as v
|
from . import backgroundthread, app, variables as v
|
||||||
|
from . import exceptions
|
||||||
|
|
||||||
LOG = getLogger('PLEX.kodimonitor')
|
LOG = getLogger('PLEX.kodimonitor')
|
||||||
|
|
||||||
|
@ -27,9 +29,10 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
"""
|
"""
|
||||||
PKC implementation of the Kodi Monitor class. Invoke only once.
|
PKC implementation of the Kodi Monitor class. Invoke only once.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._already_slept = False
|
self._already_slept = False
|
||||||
self._switch_to_plex_streams = None
|
self._switched_to_plex_streams = True
|
||||||
xbmc.Monitor.__init__(self)
|
xbmc.Monitor.__init__(self)
|
||||||
for playerid in app.PLAYSTATE.player_states:
|
for playerid in app.PLAYSTATE.player_states:
|
||||||
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
|
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
|
||||||
|
@ -67,7 +70,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
self.PlayBackStart(data)
|
self.PlayBackStart(data)
|
||||||
elif method == 'Player.OnAVChange':
|
elif method == 'Player.OnAVChange':
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
self.on_av_change()
|
self._on_av_change(data)
|
||||||
elif method == "Player.OnStop":
|
elif method == "Player.OnStop":
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
_playback_cleanup(ended=data.get('end'))
|
_playback_cleanup(ended=data.get('end'))
|
||||||
|
@ -86,7 +89,8 @@ 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":
|
||||||
_videolibrary_onupdate(data)
|
with app.APP.lock_playqueues:
|
||||||
|
_videolibrary_onupdate(data)
|
||||||
elif method == "VideoLibrary.OnRemove":
|
elif method == "VideoLibrary.OnRemove":
|
||||||
pass
|
pass
|
||||||
elif method == "System.OnSleep":
|
elif method == "System.OnSleep":
|
||||||
|
@ -179,7 +183,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
try:
|
try:
|
||||||
for i, item in enumerate(items):
|
for i, item in enumerate(items):
|
||||||
PL.add_item_to_plex_playqueue(playqueue, i + 1, kodi_item=item)
|
PL.add_item_to_plex_playqueue(playqueue, i + 1, kodi_item=item)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.info('Could not build Plex playlist for: %s', items)
|
LOG.info('Could not build Plex playlist for: %s', items)
|
||||||
|
|
||||||
def _json_item(self, playerid):
|
def _json_item(self, playerid):
|
||||||
|
@ -316,7 +320,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
item = PL.init_plex_playqueue(playqueue, plex_id=plex_id)
|
item = PL.init_plex_playqueue(playqueue, plex_id=plex_id)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.info('Could not initialize the Plex playlist')
|
LOG.info('Could not initialize the Plex playlist')
|
||||||
return
|
return
|
||||||
item.file = path
|
item.file = path
|
||||||
|
@ -341,8 +345,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
container_key = '/library/metadata/%s' % plex_id
|
container_key = '/library/metadata/%s' % plex_id
|
||||||
# Mechanik for Plex skip intro feature
|
# Mechanik for Plex skip intro feature
|
||||||
if utils.settings('enableSkipIntro') == 'true':
|
if utils.settings('enableSkipIntro') == 'true':
|
||||||
api = API(item.xml)
|
status['intro_markers'] = item.api.intro_markers()
|
||||||
status['intro_markers'] = api.intro_markers()
|
|
||||||
# Remember the currently playing item
|
# Remember the currently playing item
|
||||||
app.PLAYSTATE.item = item
|
app.PLAYSTATE.item = item
|
||||||
# Remember that this player has been active
|
# Remember that this player has been active
|
||||||
|
@ -357,60 +360,44 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
status['plex_type'] = plex_type
|
status['plex_type'] = plex_type
|
||||||
status['playmethod'] = item.playmethod
|
status['playmethod'] = item.playmethod
|
||||||
status['playcount'] = item.playcount
|
status['playcount'] = item.playcount
|
||||||
try:
|
status['external_player'] = app.APP.player.isExternalPlayer() == 1
|
||||||
status['external_player'] = app.APP.player.isExternalPlayer() == 1
|
|
||||||
except AttributeError:
|
|
||||||
# Kodi version < 17
|
|
||||||
pass
|
|
||||||
LOG.debug('Set the player state: %s', status)
|
LOG.debug('Set the player state: %s', status)
|
||||||
|
|
||||||
# Workaround for the Kodi add-on Up Next
|
# Workaround for the Kodi add-on Up Next
|
||||||
if not app.SYNC.direct_paths:
|
if not app.SYNC.direct_paths:
|
||||||
_notify_upnext(item)
|
_notify_upnext(item)
|
||||||
self._switch_to_plex_streams = item
|
self._switched_to_plex_streams = False
|
||||||
|
|
||||||
def on_av_change(self):
|
def _on_av_change(self, data):
|
||||||
"""
|
"""
|
||||||
Will be called when Kodi has a video, audio or subtitle stream. Also
|
Will be called when Kodi has a video, audio or subtitle stream. Also
|
||||||
happens when the stream changes.
|
happens when the stream changes.
|
||||||
"""
|
|
||||||
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
|
Example data as returned by Kodi:
|
||||||
def switch_to_plex_streams(item):
|
{'item': {'id': 5, 'type': 'movie'},
|
||||||
|
'player': {'playerid': 1, 'speed': 1}}
|
||||||
|
|
||||||
|
PICKING UP CHANGES ON SUBTITLES IS CURRENTLY BROKEN ON THE KODI SIDE!
|
||||||
|
Kodi subs will never change. Also see json_rpc.py
|
||||||
"""
|
"""
|
||||||
Override Kodi audio and subtitle streams with Plex PMS' selection
|
playerid = data['player']['playerid']
|
||||||
"""
|
if not playerid == v.KODI_VIDEO_PLAYER_ID:
|
||||||
for typus in ('audio', 'subtitle'):
|
# We're just messing with Kodi's videoplayer
|
||||||
try:
|
return
|
||||||
plex_index, language_tag = item.active_plex_stream_index(typus)
|
item = app.PLAYSTATE.item
|
||||||
except TypeError:
|
if item is None:
|
||||||
if typus == 'subtitle':
|
# Player might've quit
|
||||||
LOG.info('Deactivating Kodi subtitles because the PMS '
|
return
|
||||||
'told us to not show any subtitles')
|
if not self._switched_to_plex_streams:
|
||||||
app.APP.player.showSubtitles(False)
|
# We need to switch to the Plex streams ONCE upon playback start
|
||||||
continue
|
# after onavchange has been fired
|
||||||
LOG.info('The PMS wants to display %s stream with Plex id %s and '
|
if utils.settings('audioStreamPick') == '0':
|
||||||
'languageTag %s',
|
item.switch_to_plex_stream('audio')
|
||||||
typus, plex_index, language_tag)
|
if utils.settings('subtitleStreamPick') == '0':
|
||||||
kodi_index = item.kodi_stream_index(plex_index,
|
item.switch_to_plex_stream('subtitle')
|
||||||
typus)
|
self._switched_to_plex_streams = True
|
||||||
if kodi_index is None:
|
else:
|
||||||
LOG.info('Leaving Kodi %s stream settings untouched since we '
|
item.on_av_change(playerid)
|
||||||
'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):
|
def _playback_cleanup(ended=False):
|
||||||
|
@ -586,11 +573,10 @@ def _next_episode(current_api):
|
||||||
current_api.grandparent_title())
|
current_api.grandparent_title())
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
next_api = API(xml[counter + 1])
|
return API(xml[counter + 1])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# Was the last episode
|
# Was the last episode
|
||||||
return
|
pass
|
||||||
return next_api
|
|
||||||
|
|
||||||
|
|
||||||
def _complete_artwork_keys(info):
|
def _complete_artwork_keys(info):
|
||||||
|
@ -616,7 +602,7 @@ def _notify_upnext(item):
|
||||||
"""
|
"""
|
||||||
if not item.plex_type == v.PLEX_TYPE_EPISODE:
|
if not item.plex_type == v.PLEX_TYPE_EPISODE:
|
||||||
return
|
return
|
||||||
this_api = API(item.xml)
|
this_api = item.api
|
||||||
next_api = _next_episode(this_api)
|
next_api = _next_episode(this_api)
|
||||||
if next_api is None:
|
if next_api is None:
|
||||||
return
|
return
|
||||||
|
@ -652,17 +638,34 @@ def _videolibrary_onupdate(data):
|
||||||
A specific Kodi library item has been updated. This seems to happen if the
|
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
|
user marks an item as watched/unwatched or if playback of the item just
|
||||||
stopped
|
stopped
|
||||||
|
|
||||||
|
2 kinds of messages possible, e.g.
|
||||||
|
Method: VideoLibrary.OnUpdate Data: ("Reset resume position" and also
|
||||||
|
fired just after stopping playback - BEFORE OnStop fires)
|
||||||
|
{'id': 1, 'type': 'movie'}
|
||||||
|
Method: VideoLibrary.OnUpdate Data: ("Mark as watched")
|
||||||
|
{'item': {'id': 1, 'type': 'movie'}, 'playcount': 1}
|
||||||
"""
|
"""
|
||||||
playcount = data.get('playcount')
|
item = data.get('item') if 'item' in data else data
|
||||||
item = data.get('item')
|
|
||||||
if playcount is None or item is None:
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
kodi_id = item['id']
|
kodi_id = item['id']
|
||||||
kodi_type = item['type']
|
kodi_type = item['type']
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
LOG.info("Item is invalid for playstate update.")
|
LOG.debug("Item is invalid for a Plex playstate update")
|
||||||
return
|
return
|
||||||
|
playcount = data.get('playcount')
|
||||||
|
if playcount is None:
|
||||||
|
# "Reset resume position"
|
||||||
|
# Kodi might set as watched or unwatched!
|
||||||
|
with KodiVideoDB(lock=False) as kodidb:
|
||||||
|
file_id = kodidb.file_id_from_id(kodi_id, kodi_type)
|
||||||
|
if file_id is None:
|
||||||
|
return
|
||||||
|
if kodidb.get_resume(file_id):
|
||||||
|
# We do have an existing bookmark entry - not toggling to
|
||||||
|
# either watched or unwatched on the Plex side
|
||||||
|
return
|
||||||
|
playcount = kodidb.get_playcount(file_id) or 0
|
||||||
if app.PLAYSTATE.item and kodi_id == app.PLAYSTATE.item.kodi_id and \
|
if app.PLAYSTATE.item and kodi_id == app.PLAYSTATE.item.kodi_id and \
|
||||||
kodi_type == app.PLAYSTATE.item.kodi_type:
|
kodi_type == app.PLAYSTATE.item.kodi_type:
|
||||||
# Kodi updates an item immediately after playback. Hence we do NOT
|
# Kodi updates an item immediately after playback. Hence we do NOT
|
||||||
|
|
|
@ -16,6 +16,7 @@ from .kodi_db import KodiVideoDB
|
||||||
from . import plex_functions as PF, playlist_func as PL, playqueue as PQ
|
from . import plex_functions as PF, playlist_func as PL, playqueue as PQ
|
||||||
from . import json_rpc as js, variables as v, utils, transfer
|
from . import json_rpc as js, variables as v, utils, transfer
|
||||||
from . import playback_decision, app
|
from . import playback_decision, app
|
||||||
|
from . import exceptions
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playback')
|
LOG = getLogger('PLEX.playback')
|
||||||
|
@ -192,7 +193,7 @@ def _playback_init(plex_id, plex_type, playqueue, pos, resume):
|
||||||
# Special case - we already got a filled Kodi playqueue
|
# Special case - we already got a filled Kodi playqueue
|
||||||
try:
|
try:
|
||||||
_init_existing_kodi_playlist(playqueue, pos)
|
_init_existing_kodi_playlist(playqueue, pos)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.error('Playback_init for existing Kodi playlist failed')
|
LOG.error('Playback_init for existing Kodi playlist failed')
|
||||||
_ensure_resolve(abort=True)
|
_ensure_resolve(abort=True)
|
||||||
return
|
return
|
||||||
|
@ -312,7 +313,7 @@ def _init_existing_kodi_playlist(playqueue, pos):
|
||||||
kodi_items = js.playlist_get_items(playqueue.playlistid)
|
kodi_items = js.playlist_get_items(playqueue.playlistid)
|
||||||
if not kodi_items:
|
if not kodi_items:
|
||||||
LOG.error('No Kodi items returned')
|
LOG.error('No Kodi items returned')
|
||||||
raise PL.PlaylistError('No Kodi items returned')
|
raise exceptions.PlaylistError('No Kodi items returned')
|
||||||
item = PL.init_plex_playqueue(playqueue, kodi_item=kodi_items[pos])
|
item = PL.init_plex_playqueue(playqueue, kodi_item=kodi_items[pos])
|
||||||
item.force_transcode = app.PLAYSTATE.force_transcode
|
item.force_transcode = app.PLAYSTATE.force_transcode
|
||||||
# playqueue.py will add the rest - this will likely put the PMS under
|
# playqueue.py will add the rest - this will likely put the PMS under
|
||||||
|
@ -444,27 +445,26 @@ def _conclude_playback(playqueue, pos):
|
||||||
"""
|
"""
|
||||||
LOG.debug('Concluding playback for playqueue position %s', pos)
|
LOG.debug('Concluding playback for playqueue position %s', pos)
|
||||||
item = playqueue.items[pos]
|
item = playqueue.items[pos]
|
||||||
api = API(item.xml)
|
if item.api.mediastream_number() is None:
|
||||||
if api.mediastream_number() is None:
|
|
||||||
# E.g. user could choose between several media streams and cancelled
|
# E.g. user could choose between several media streams and cancelled
|
||||||
LOG.debug('Did not get a mediastream_number')
|
LOG.debug('Did not get a mediastream_number')
|
||||||
_ensure_resolve()
|
_ensure_resolve()
|
||||||
return
|
return
|
||||||
api.part = item.part or 0
|
item.api.part = item.part or 0
|
||||||
playback_decision.set_pkc_playmethod(api, item)
|
playback_decision.set_pkc_playmethod(item.api, item)
|
||||||
if not playback_decision.audio_subtitle_prefs(api, item):
|
if not playback_decision.audio_subtitle_prefs(item.api, item):
|
||||||
LOG.info('Did not set audio subtitle prefs, aborting silently')
|
LOG.info('Did not set audio subtitle prefs, aborting silently')
|
||||||
_ensure_resolve()
|
_ensure_resolve()
|
||||||
return
|
return
|
||||||
playback_decision.set_playurl(api, item)
|
playback_decision.set_playurl(item.api, item)
|
||||||
if not item.file:
|
if not item.file:
|
||||||
LOG.info('Did not get a playurl, aborting playback silently')
|
LOG.info('Did not get a playurl, aborting playback silently')
|
||||||
_ensure_resolve()
|
_ensure_resolve()
|
||||||
return
|
return
|
||||||
listitem = api.listitem(listitem=transfer.PKCListItem, resume=False)
|
listitem = item.api.listitem(listitem=transfer.PKCListItem, resume=False)
|
||||||
listitem.setPath(item.file.encode('utf-8'))
|
listitem.setPath(item.file.encode('utf-8'))
|
||||||
if item.playmethod != v.PLAYBACK_METHOD_DIRECT_PATH:
|
if item.playmethod != v.PLAYBACK_METHOD_DIRECT_PATH:
|
||||||
listitem.setSubtitles(api.cache_external_subs())
|
listitem.setSubtitles(item.api.cache_external_subs())
|
||||||
transfer.send(listitem)
|
transfer.send(listitem)
|
||||||
LOG.debug('Done concluding playback')
|
LOG.debug('Done concluding playback')
|
||||||
|
|
||||||
|
|
|
@ -329,19 +329,12 @@ def audio_subtitle_prefs(api, item):
|
||||||
Returns None if user cancelled or we need to abort, True otherwise
|
Returns None if user cancelled or we need to abort, True otherwise
|
||||||
"""
|
"""
|
||||||
# Set media and part where we're at
|
# Set media and part where we're at
|
||||||
if (api.mediastream is None and
|
if api.mediastream is None and api.mediastream_number() is None:
|
||||||
api.mediastream_number() is None):
|
|
||||||
return
|
return
|
||||||
try:
|
|
||||||
mediastreams = api.plex_media_streams()
|
|
||||||
except (TypeError, IndexError):
|
|
||||||
LOG.error('Could not get media %s, part %s',
|
|
||||||
api.mediastream, api.part)
|
|
||||||
return
|
|
||||||
part_id = mediastreams.attrib['id']
|
|
||||||
if item.playmethod != v.PLAYBACK_METHOD_TRANSCODE:
|
if item.playmethod != v.PLAYBACK_METHOD_TRANSCODE:
|
||||||
return True
|
return True
|
||||||
return setup_transcoding_audio_subtitle_prefs(mediastreams, part_id)
|
return setup_transcoding_audio_subtitle_prefs(api.plex_media_streams(),
|
||||||
|
api.part_id())
|
||||||
|
|
||||||
|
|
||||||
def setup_transcoding_audio_subtitle_prefs(mediastreams, part_id):
|
def setup_transcoding_audio_subtitle_prefs(mediastreams, part_id):
|
||||||
|
@ -426,7 +419,8 @@ def setup_transcoding_audio_subtitle_prefs(mediastreams, part_id):
|
||||||
action_type='PUT',
|
action_type='PUT',
|
||||||
parameters=args)
|
parameters=args)
|
||||||
|
|
||||||
select_subs_index = ''
|
# Zero telling the PMS to deactivate subs altogether
|
||||||
|
select_subs_index = 0
|
||||||
if sub_num == 1:
|
if sub_num == 1:
|
||||||
# Note: we DO need to tell the PMS that we DONT want any sub
|
# Note: we DO need to tell the PMS that we DONT want any sub
|
||||||
# Otherwise, the PMS might pick-up the last one
|
# Otherwise, the PMS might pick-up the last one
|
||||||
|
@ -445,15 +439,9 @@ def setup_transcoding_audio_subtitle_prefs(mediastreams, part_id):
|
||||||
LOG.info('User chose to not burn-in any subtitles')
|
LOG.info('User chose to not burn-in any subtitles')
|
||||||
else:
|
else:
|
||||||
LOG.info('User chose to burn-in subtitle %s: %s',
|
LOG.info('User chose to burn-in subtitle %s: %s',
|
||||||
select_subs_index,
|
select_subs_index,
|
||||||
subtitle_streams[resp].decode('utf-8'))
|
subtitle_streams[resp].decode('utf-8'))
|
||||||
select_subs_index = subtitle_streams_list[resp - 1]
|
select_subs_index = subtitle_streams_list[resp - 1]
|
||||||
# Now prep the PMS for our choice
|
# Now prep the PMS for our choice
|
||||||
args = {
|
PF.change_subtitle(select_subs_index, part_id)
|
||||||
'subtitleStreamID': select_subs_index,
|
|
||||||
'allParts': 1
|
|
||||||
}
|
|
||||||
DU().downloadUrl('{server}/library/parts/%s' % part_id,
|
|
||||||
action_type='PUT',
|
|
||||||
parameters=args)
|
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -12,22 +12,17 @@ from . import plex_functions as PF
|
||||||
from .kodi_db import kodiid_from_filename
|
from .kodi_db import kodiid_from_filename
|
||||||
from .downloadutils import DownloadUtils as DU
|
from .downloadutils import DownloadUtils as DU
|
||||||
from . import utils
|
from . import utils
|
||||||
|
from .utils import cast
|
||||||
from . import json_rpc as js
|
from . import json_rpc as js
|
||||||
from . import variables as v
|
from . import variables as v
|
||||||
from . import app
|
from . import app
|
||||||
|
from .exceptions import PlaylistError
|
||||||
from .subtitles import accessible_plex_subtitles
|
from .subtitles import accessible_plex_subtitles
|
||||||
|
|
||||||
|
|
||||||
LOG = getLogger('PLEX.playlist_func')
|
LOG = getLogger('PLEX.playlist_func')
|
||||||
|
|
||||||
|
|
||||||
class PlaylistError(Exception):
|
|
||||||
"""
|
|
||||||
Exception for our playlist constructs
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Playqueue_Object(object):
|
class Playqueue_Object(object):
|
||||||
"""
|
"""
|
||||||
PKC object to represent PMS playQueues and Kodi playlist for queueing
|
PKC object to represent PMS playQueues and Kodi playlist for queueing
|
||||||
|
@ -155,13 +150,14 @@ class PlaylistItem(object):
|
||||||
file = None [str] Path to the item's file. STRING!!
|
file = None [str] Path to the item's file. STRING!!
|
||||||
uri = None [str] PMS path to item; will be auto-set with plex_id
|
uri = None [str] PMS path to item; will be auto-set with plex_id
|
||||||
guid = None [str] Weird Plex guid
|
guid = None [str] Weird Plex guid
|
||||||
xml = None [etree] XML from PMS, 1 lvl below <MediaContainer>
|
api = None [API] API of xml 1 lvl below <MediaContainer>
|
||||||
playmethod = None [str] either 'DirectPath', 'DirectStream', 'Transcode'
|
playmethod = None [str] either 'DirectPath', 'DirectStream', 'Transcode'
|
||||||
playcount = None [int] how many times the item has already been played
|
playcount = None [int] how many times the item has already been played
|
||||||
offset = None [int] the item's view offset UPON START in Plex time
|
offset = None [int] the item's view offset UPON START in Plex time
|
||||||
part = 0 [int] part number if Plex video consists of mult. parts
|
part = 0 [int] part number if Plex video consists of mult. parts
|
||||||
force_transcode [bool] defaults to False
|
force_transcode [bool] defaults to False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.id = None
|
self.id = None
|
||||||
self._plex_id = None
|
self._plex_id = None
|
||||||
|
@ -171,7 +167,7 @@ class PlaylistItem(object):
|
||||||
self.file = None
|
self.file = None
|
||||||
self._uri = None
|
self._uri = None
|
||||||
self.guid = None
|
self.guid = None
|
||||||
self.xml = None
|
self.api = None
|
||||||
self.playmethod = None
|
self.playmethod = None
|
||||||
self.playcount = None
|
self.playcount = None
|
||||||
self.offset = None
|
self.offset = None
|
||||||
|
@ -185,6 +181,16 @@ class PlaylistItem(object):
|
||||||
# False: do NOT resume, don't ask user
|
# False: do NOT resume, don't ask user
|
||||||
# True: do resume, don't ask user
|
# True: do resume, don't ask user
|
||||||
self.resume = None
|
self.resume = None
|
||||||
|
# Get the Plex audio and subtitle streams in the same order as Kodi
|
||||||
|
# uses them (Kodi uses indexes to activate them, not ids like Plex)
|
||||||
|
self._streams_have_been_processed = False
|
||||||
|
self._audio_streams = None
|
||||||
|
self._subtitle_streams = None
|
||||||
|
# Which Kodi streams are active?
|
||||||
|
self.current_kodi_audio_stream = None
|
||||||
|
# False means "deactivated", None means "we do not have a Kodi
|
||||||
|
# equivalent for this Plex subtitle"
|
||||||
|
self.current_kodi_sub_stream = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def plex_id(self):
|
def plex_id(self):
|
||||||
|
@ -200,6 +206,18 @@ class PlaylistItem(object):
|
||||||
def uri(self):
|
def uri(self):
|
||||||
return self._uri
|
return self._uri
|
||||||
|
|
||||||
|
@property
|
||||||
|
def audio_streams(self):
|
||||||
|
if not self._streams_have_been_processed:
|
||||||
|
self._process_streams()
|
||||||
|
return self._audio_streams
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subtitle_streams(self):
|
||||||
|
if not self._streams_have_been_processed:
|
||||||
|
self._process_streams()
|
||||||
|
return self._subtitle_streams
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return ("{{"
|
return ("{{"
|
||||||
"'id': {self.id}, "
|
"'id': {self.id}, "
|
||||||
|
@ -219,85 +237,152 @@ class PlaylistItem(object):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.__unicode__().encode('utf-8')
|
return self.__unicode__().encode('utf-8')
|
||||||
|
|
||||||
|
def _process_streams(self):
|
||||||
|
"""
|
||||||
|
Builds audio and subtitle streams and enables matching between Plex
|
||||||
|
and Kodi using self.audio_streams and self.subtitle_streams
|
||||||
|
"""
|
||||||
|
# The playqueue response from the PMS does not contain a stream filename
|
||||||
|
# thanks Plex
|
||||||
|
self._subtitle_streams = accessible_plex_subtitles(
|
||||||
|
self.playmethod,
|
||||||
|
self.file,
|
||||||
|
self.api.plex_media_streams())
|
||||||
|
# Audio streams are much easier - they're always available and sorted
|
||||||
|
# the same in Kodi and Plex
|
||||||
|
self._audio_streams = [x for x in self.api.plex_media_streams()
|
||||||
|
if x.get('streamType') == '2']
|
||||||
|
self._streams_have_been_processed = True
|
||||||
|
|
||||||
|
def _get_iterator(self, stream_type):
|
||||||
|
if stream_type == 'audio':
|
||||||
|
return self.audio_streams
|
||||||
|
elif stream_type == 'subtitle':
|
||||||
|
return self.subtitle_streams
|
||||||
|
|
||||||
def plex_stream_index(self, kodi_stream_index, stream_type):
|
def plex_stream_index(self, kodi_stream_index, stream_type):
|
||||||
"""
|
"""
|
||||||
Pass in the kodi_stream_index [int] in order to receive the Plex stream
|
Pass in the kodi_stream_index [int] in order to receive the Plex stream
|
||||||
index.
|
index [int].
|
||||||
|
|
||||||
stream_type: 'video', 'audio', 'subtitle'
|
stream_type: 'video', 'audio', 'subtitle'
|
||||||
|
|
||||||
Returns None if unsuccessful
|
Returns None if unsuccessful
|
||||||
"""
|
"""
|
||||||
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
|
if stream_type == 'audio':
|
||||||
count = 0
|
return int(self.audio_streams[kodi_stream_index].get('id'))
|
||||||
if kodi_stream_index == -1:
|
elif stream_type == 'subtitle':
|
||||||
# Kodi telling us "it's the last one"
|
try:
|
||||||
iterator = list(reversed(self.xml[0][self.part]))
|
return int(self.subtitle_streams[kodi_stream_index].get('id'))
|
||||||
kodi_stream_index = 0
|
except (IndexError, TypeError):
|
||||||
else:
|
pass
|
||||||
iterator = self.xml[0][self.part]
|
|
||||||
# Kodi indexes differently than Plex
|
|
||||||
for stream in iterator:
|
|
||||||
if (stream.get('streamType') == stream_type and
|
|
||||||
'key' in stream.attrib):
|
|
||||||
if count == kodi_stream_index:
|
|
||||||
return stream.get('id')
|
|
||||||
count += 1
|
|
||||||
for stream in iterator:
|
|
||||||
if (stream.get('streamType') == stream_type and
|
|
||||||
'key' not in stream.attrib):
|
|
||||||
if count == kodi_stream_index:
|
|
||||||
return stream.get('id')
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
def kodi_stream_index(self, plex_stream_index, stream_type):
|
def kodi_stream_index(self, plex_stream_index, stream_type):
|
||||||
"""
|
"""
|
||||||
Pass in the kodi_stream_index [int] in order to receive the Plex stream
|
Pass in the plex_stream_index [int] in order to receive the Kodi stream
|
||||||
index.
|
index [int].
|
||||||
|
|
||||||
stream_type: 'video', 'audio', 'subtitle'
|
stream_type: 'video', 'audio', 'subtitle'
|
||||||
|
|
||||||
Returns None if unsuccessful
|
Returns None if unsuccessful
|
||||||
"""
|
"""
|
||||||
if plex_stream_index is None:
|
if plex_stream_index is None:
|
||||||
return
|
return
|
||||||
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
|
for i, stream in enumerate(self._get_iterator(stream_type)):
|
||||||
count = 0
|
if cast(int, stream.get('id')) == plex_stream_index:
|
||||||
streams = self.sorted_accessible_plex_subtitles(stream_type)
|
return i
|
||||||
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):
|
def active_plex_stream_index(self, stream_type):
|
||||||
"""
|
"""
|
||||||
Returns the following tuple for the active stream on the Plex side:
|
Returns the following tuple for the active stream on the Plex side:
|
||||||
(id [int], languageTag [str])
|
(Plex stream id [int], languageTag [str] or None)
|
||||||
Returns None if no stream has been selected
|
Returns None if no stream has been selected
|
||||||
"""
|
"""
|
||||||
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
|
for i, stream in enumerate(self._get_iterator(stream_type)):
|
||||||
for stream in self.xml[0][self.part]:
|
if stream.get('selected') == '1':
|
||||||
if stream.get('streamType') == stream_type \
|
return (int(stream.get('id')), stream.get('languageTag'))
|
||||||
and stream.get('selected') == '1':
|
|
||||||
return (utils.cast(int, stream.get('id')),
|
|
||||||
stream.get('languageTag'))
|
|
||||||
|
|
||||||
def sorted_accessible_plex_subtitles(self, stream_type):
|
def on_kodi_subtitle_stream_change(self, kodi_stream_index, subs_enabled):
|
||||||
"""
|
"""
|
||||||
Returns only the subtitles that Kodi can access when PKC Direct Paths
|
Call this method if Kodi changed its subtitle and you want Plex to
|
||||||
are used; i.e. Kodi has access to a video's directory.
|
know.
|
||||||
NOT supported: additional subtitles downloaded using the Plex interface
|
|
||||||
"""
|
"""
|
||||||
# The playqueue response from the PMS does not contain a stream filename
|
if subs_enabled:
|
||||||
# thanks Plex
|
try:
|
||||||
if stream_type == '3':
|
plex_stream_index = int(self.subtitle_streams[kodi_stream_index].get('id'))
|
||||||
streams = accessible_plex_subtitles(self.playmethod,
|
except (IndexError, TypeError):
|
||||||
self.file,
|
LOG.debug('Kodi subtitle change detected to a sub %s that is '
|
||||||
self.xml[0][self.part])
|
'NOT available on the Plex side', kodi_stream_index)
|
||||||
|
self.current_kodi_sub_stream = None
|
||||||
|
return
|
||||||
|
LOG.debug('Kodi subtitle change detected: telling Plex about '
|
||||||
|
'switch to index %s, Plex stream id %s',
|
||||||
|
kodi_stream_index, plex_stream_index)
|
||||||
|
self.current_kodi_sub_stream = kodi_stream_index
|
||||||
else:
|
else:
|
||||||
streams = [x for x in self.xml[0][self.part]
|
plex_stream_index = 0
|
||||||
if x.get('streamType') == stream_type]
|
LOG.debug('Kodi subtitle has been deactivated, telling Plex')
|
||||||
return streams
|
self.current_kodi_sub_stream = False
|
||||||
|
PF.change_subtitle(plex_stream_index, self.api.part_id())
|
||||||
|
|
||||||
|
def on_kodi_audio_stream_change(self, kodi_stream_index):
|
||||||
|
"""
|
||||||
|
Call this method if Kodi changed its audio stream and you want Plex to
|
||||||
|
know. kodi_stream_index [int]
|
||||||
|
"""
|
||||||
|
plex_stream_index = int(self.audio_streams[kodi_stream_index].get('id'))
|
||||||
|
LOG.debug('Changing Plex audio stream to %s, Kodi index %s',
|
||||||
|
plex_stream_index, kodi_stream_index)
|
||||||
|
PF.change_audio_stream(plex_stream_index, self.api.part_id())
|
||||||
|
self.current_kodi_audio_stream = kodi_stream_index
|
||||||
|
|
||||||
|
def switch_to_plex_streams(self):
|
||||||
|
self.switch_to_plex_stream('audio')
|
||||||
|
self.switch_to_plex_stream('subtitle')
|
||||||
|
|
||||||
|
def switch_to_plex_stream(self, typus):
|
||||||
|
try:
|
||||||
|
plex_index, language_tag = self.active_plex_stream_index(typus)
|
||||||
|
except TypeError:
|
||||||
|
LOG.debug('Deactivating Kodi subtitles because the PMS '
|
||||||
|
'told us to not show any subtitles')
|
||||||
|
app.APP.player.showSubtitles(False)
|
||||||
|
self.current_kodi_sub_stream = False
|
||||||
|
return
|
||||||
|
LOG.debug('The PMS wants to display %s stream with Plex id %s and '
|
||||||
|
'languageTag %s', typus, plex_index, language_tag)
|
||||||
|
kodi_index = self.kodi_stream_index(plex_index, typus)
|
||||||
|
if kodi_index is None:
|
||||||
|
LOG.debug('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.debug('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 == 'audio':
|
||||||
|
app.APP.player.setAudioStream(kodi_index)
|
||||||
|
else:
|
||||||
|
app.APP.player.setSubtitleStream(kodi_index)
|
||||||
|
app.APP.player.showSubtitles(True)
|
||||||
|
if typus == 'audio':
|
||||||
|
self.current_kodi_audio_stream = kodi_index
|
||||||
|
else:
|
||||||
|
self.current_kodi_sub_stream = kodi_index
|
||||||
|
|
||||||
|
def on_av_change(self, playerid):
|
||||||
|
kodi_audio_stream = js.get_current_audio_stream_index(playerid)
|
||||||
|
sub_enabled = js.get_subtitle_enabled(playerid)
|
||||||
|
kodi_sub_stream = js.get_current_subtitle_stream_index(playerid)
|
||||||
|
# Audio
|
||||||
|
if kodi_audio_stream != self.current_kodi_audio_stream:
|
||||||
|
self.on_kodi_audio_stream_change(kodi_audio_stream)
|
||||||
|
# Subtitles - CURRENTLY BROKEN ON THE KODI SIDE!
|
||||||
|
# current_kodi_sub_stream may also be zero
|
||||||
|
subs_off = (None, False)
|
||||||
|
if ((sub_enabled and self.current_kodi_sub_stream in subs_off)
|
||||||
|
or (not sub_enabled and self.current_kodi_sub_stream not in subs_off)
|
||||||
|
or (kodi_sub_stream is not None
|
||||||
|
and kodi_sub_stream != self.current_kodi_sub_stream)):
|
||||||
|
self.on_kodi_subtitle_stream_change(kodi_sub_stream, sub_enabled)
|
||||||
|
|
||||||
|
|
||||||
def playlist_item_from_kodi(kodi_item):
|
def playlist_item_from_kodi(kodi_item):
|
||||||
|
@ -323,7 +408,7 @@ def playlist_item_from_kodi(kodi_item):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
query = ''
|
query = ''
|
||||||
query = dict(utils.parse_qsl(query))
|
query = dict(utils.parse_qsl(query))
|
||||||
item.plex_id = utils.cast(int, query.get('plex_id'))
|
item.plex_id = cast(int, query.get('plex_id'))
|
||||||
item.plex_type = query.get('itemType')
|
item.plex_type = query.get('itemType')
|
||||||
LOG.debug('Made playlist item from Kodi: %s', item)
|
LOG.debug('Made playlist item from Kodi: %s', item)
|
||||||
return item
|
return item
|
||||||
|
@ -421,14 +506,15 @@ def playlist_item_from_xml(xml_video_element, kodi_id=None, kodi_type=None):
|
||||||
item.guid = api.guid_html_escaped()
|
item.guid = api.guid_html_escaped()
|
||||||
item.playcount = api.viewcount()
|
item.playcount = api.viewcount()
|
||||||
item.offset = api.resume_point()
|
item.offset = api.resume_point()
|
||||||
item.xml = xml_video_element
|
item.api = api
|
||||||
LOG.debug('Created new playlist item from xml: %s', item)
|
LOG.debug('Created new playlist item from xml: %s', item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
def _get_playListVersion_from_xml(playlist, xml):
|
def _update_playlist_version(playlist, xml):
|
||||||
"""
|
"""
|
||||||
Takes a PMS xml as input to overwrite the playlist version (e.g. Plex
|
Takes a PMS xml (one level above the xml-depth where we're usually applying
|
||||||
|
API()) as input to overwrite the playlist version (e.g. Plex
|
||||||
playQueueVersion).
|
playQueueVersion).
|
||||||
|
|
||||||
Raises PlaylistError if unsuccessful
|
Raises PlaylistError if unsuccessful
|
||||||
|
@ -449,18 +535,13 @@ def get_playlist_details_from_xml(playlist, xml):
|
||||||
"""
|
"""
|
||||||
if xml is None:
|
if xml is None:
|
||||||
raise PlaylistError('No playlist received for playlist %s' % playlist)
|
raise PlaylistError('No playlist received for playlist %s' % playlist)
|
||||||
playlist.id = utils.cast(int,
|
playlist.id = cast(int, xml.get('%sID' % playlist.kind))
|
||||||
xml.get('%sID' % playlist.kind))
|
playlist.version = cast(int, xml.get('%sVersion' % playlist.kind))
|
||||||
playlist.version = utils.cast(int,
|
playlist.shuffled = cast(int, xml.get('%sShuffled' % playlist.kind))
|
||||||
xml.get('%sVersion' % playlist.kind))
|
playlist.selectedItemID = cast(int, xml.get('%sSelectedItemID'
|
||||||
playlist.shuffled = utils.cast(int,
|
% playlist.kind))
|
||||||
xml.get('%sShuffled' % playlist.kind))
|
playlist.selectedItemOffset = cast(int, xml.get('%sSelectedItemOffset'
|
||||||
playlist.selectedItemID = utils.cast(int,
|
% playlist.kind))
|
||||||
xml.get('%sSelectedItemID'
|
|
||||||
% playlist.kind))
|
|
||||||
playlist.selectedItemOffset = utils.cast(int,
|
|
||||||
xml.get('%sSelectedItemOffset'
|
|
||||||
% playlist.kind))
|
|
||||||
LOG.debug('Updated playlist from xml: %s', playlist)
|
LOG.debug('Updated playlist from xml: %s', playlist)
|
||||||
|
|
||||||
|
|
||||||
|
@ -611,7 +692,7 @@ def add_item_to_plex_playqueue(playlist, pos, plex_id=None, kodi_item=None):
|
||||||
raise PlaylistError('Could not add item %s to playlist %s'
|
raise PlaylistError('Could not add item %s to playlist %s'
|
||||||
% (kodi_item, playlist))
|
% (kodi_item, playlist))
|
||||||
api = API(xml[-1])
|
api = API(xml[-1])
|
||||||
item.xml = xml[-1]
|
item.api = api
|
||||||
item.id = api.item_id()
|
item.id = api.item_id()
|
||||||
item.guid = api.guid_html_escaped()
|
item.guid = api.guid_html_escaped()
|
||||||
item.offset = api.resume_point()
|
item.offset = api.resume_point()
|
||||||
|
@ -619,7 +700,7 @@ def add_item_to_plex_playqueue(playlist, pos, plex_id=None, kodi_item=None):
|
||||||
playlist.items.append(item)
|
playlist.items.append(item)
|
||||||
if pos == len(playlist.items) - 1:
|
if pos == len(playlist.items) - 1:
|
||||||
# Item was added at the end
|
# Item was added at the end
|
||||||
_get_playListVersion_from_xml(playlist, xml)
|
_update_playlist_version(playlist, xml)
|
||||||
else:
|
else:
|
||||||
# Move the new item to the correct position
|
# Move the new item to the correct position
|
||||||
move_playlist_item(playlist,
|
move_playlist_item(playlist,
|
||||||
|
@ -663,7 +744,7 @@ def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
|
||||||
{'id': kodi_id, 'type': kodi_type, 'file': file})
|
{'id': kodi_id, 'type': kodi_type, 'file': file})
|
||||||
if item.plex_id is not None:
|
if item.plex_id is not None:
|
||||||
xml = PF.GetPlexMetadata(item.plex_id)
|
xml = PF.GetPlexMetadata(item.plex_id)
|
||||||
item.xml = xml[-1]
|
item.api = API(xml[-1])
|
||||||
playlist.items.insert(pos, item)
|
playlist.items.insert(pos, item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
@ -687,9 +768,10 @@ def move_playlist_item(playlist, before_pos, after_pos):
|
||||||
playlist.id,
|
playlist.id,
|
||||||
playlist.items[before_pos].id,
|
playlist.items[before_pos].id,
|
||||||
playlist.items[after_pos - 1].id)
|
playlist.items[after_pos - 1].id)
|
||||||
# We need to increment the playlistVersion
|
# Tell the PMS that we're moving items around
|
||||||
_get_playListVersion_from_xml(
|
xml = DU().downloadUrl(url, action_type="PUT")
|
||||||
playlist, DU().downloadUrl(url, action_type="PUT"))
|
# We need to increment the playlist version for communicating with the PMS
|
||||||
|
_update_playlist_version(playlist, xml)
|
||||||
# Move our item's position in our internal playlist
|
# Move our item's position in our internal playlist
|
||||||
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
|
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
|
||||||
LOG.debug('Done moving for %s', playlist)
|
LOG.debug('Done moving for %s', playlist)
|
||||||
|
@ -737,7 +819,7 @@ def delete_playlist_item_from_PMS(playlist, pos):
|
||||||
playlist.repeat),
|
playlist.repeat),
|
||||||
action_type="DELETE")
|
action_type="DELETE")
|
||||||
del playlist.items[pos]
|
del playlist.items[pos]
|
||||||
_get_playListVersion_from_xml(playlist, xml)
|
_update_playlist_version(playlist, xml)
|
||||||
|
|
||||||
|
|
||||||
# Functions operating on the Kodi playlist objects ##########
|
# Functions operating on the Kodi playlist objects ##########
|
||||||
|
|
|
@ -15,13 +15,13 @@ from __future__ import absolute_import, division, unicode_literals
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from sqlite3 import OperationalError
|
from sqlite3 import OperationalError
|
||||||
|
|
||||||
from .common import Playlist, PlaylistError, PlaylistObserver, \
|
from .common import Playlist, PlaylistObserver, kodi_playlist_hash
|
||||||
kodi_playlist_hash
|
|
||||||
from . import pms, db, kodi_pl, plex_pl
|
from . import pms, db, kodi_pl, plex_pl
|
||||||
|
|
||||||
from ..watchdog import events
|
from ..watchdog import events
|
||||||
from ..plex_api import API
|
from ..plex_api import API
|
||||||
from .. import utils, path_ops, variables as v, app
|
from .. import utils, path_ops, variables as v, app
|
||||||
|
from ..exceptions import PlaylistError
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playlists')
|
LOG = getLogger('PLEX.playlists')
|
||||||
|
|
|
@ -12,6 +12,8 @@ from ..watchdog.observers import Observer
|
||||||
from ..watchdog.utils.bricks import OrderedSetQueue
|
from ..watchdog.utils.bricks import OrderedSetQueue
|
||||||
|
|
||||||
from .. import path_ops, variables as v, app
|
from .. import path_ops, variables as v, app
|
||||||
|
from ..exceptions import PlaylistError
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playlists.common')
|
LOG = getLogger('PLEX.playlists.common')
|
||||||
|
|
||||||
|
@ -20,13 +22,6 @@ SIMILAR_EVENTS = (events.EVENT_TYPE_CREATED, events.EVENT_TYPE_MODIFIED)
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
class PlaylistError(Exception):
|
|
||||||
"""
|
|
||||||
The one main exception thrown if anything goes awry
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Playlist(object):
|
class Playlist(object):
|
||||||
"""
|
"""
|
||||||
Class representing a synced Playlist with info for both Kodi and Plex.
|
Class representing a synced Playlist with info for both Kodi and Plex.
|
||||||
|
|
|
@ -7,10 +7,11 @@ module
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from .common import Playlist, PlaylistError
|
from .common import Playlist
|
||||||
from ..plex_db import PlexDB
|
from ..plex_db import PlexDB
|
||||||
from ..kodi_db import kodiid_from_filename
|
from ..kodi_db import kodiid_from_filename
|
||||||
from .. import path_ops, utils, variables as v
|
from .. import path_ops, utils, variables as v
|
||||||
|
from ..exceptions import PlaylistError
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playlists.db')
|
LOG = getLogger('PLEX.playlists.db')
|
||||||
|
|
||||||
|
@ -121,7 +122,7 @@ def m3u_to_plex_ids(playlist):
|
||||||
def playlist_file_to_plex_ids(playlist):
|
def playlist_file_to_plex_ids(playlist):
|
||||||
"""
|
"""
|
||||||
Takes the playlist file located at path [unicode] and parses it.
|
Takes the playlist file located at path [unicode] and parses it.
|
||||||
Returns a list of plex_ids (str) or raises PL.PlaylistError if a single
|
Returns a list of plex_ids (str) or raises PlaylistError if a single
|
||||||
item cannot be parsed from Kodi to Plex.
|
item cannot be parsed from Kodi to Plex.
|
||||||
"""
|
"""
|
||||||
if playlist.kodi_extension == 'm3u':
|
if playlist.kodi_extension == 'm3u':
|
||||||
|
|
|
@ -7,11 +7,13 @@ from __future__ import absolute_import, division, unicode_literals
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import Playlist, PlaylistError, kodi_playlist_hash
|
from .common import Playlist, kodi_playlist_hash
|
||||||
from . import db, pms
|
from . import db, pms
|
||||||
|
|
||||||
from ..plex_api import API
|
from ..plex_api import API
|
||||||
from .. import utils, path_ops, variables as v
|
from .. import utils, path_ops, variables as v
|
||||||
|
from ..exceptions import PlaylistError
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playlists.kodi_pl')
|
LOG = getLogger('PLEX.playlists.kodi_pl')
|
||||||
REGEX_FILE_NUMBERING = re.compile(r'''_(\d\d)\.\w+$''')
|
REGEX_FILE_NUMBERING = re.compile(r'''_(\d\d)\.\w+$''')
|
||||||
|
|
|
@ -6,8 +6,9 @@ Create and delete playlists on the Plex side of things
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from .common import PlaylistError
|
|
||||||
from . import pms, db
|
from . import pms, db
|
||||||
|
from ..exceptions import PlaylistError
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playlists.plex_pl')
|
LOG = getLogger('PLEX.playlists.plex_pl')
|
||||||
# Used for updating Plex playlists due to Kodi changes - Plex playlist
|
# Used for updating Plex playlists due to Kodi changes - Plex playlist
|
||||||
|
|
|
@ -7,11 +7,11 @@ manipulate playlists
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from .common import PlaylistError
|
|
||||||
|
|
||||||
from ..plex_api import API
|
from ..plex_api import API
|
||||||
from ..downloadutils import DownloadUtils as DU
|
from ..downloadutils import DownloadUtils as DU
|
||||||
from .. import utils, app, variables as v
|
from .. import utils, app, variables as v
|
||||||
|
from ..exceptions import PlaylistError
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playlists.pms')
|
LOG = getLogger('PLEX.playlists.pms')
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import xbmc
|
||||||
from .plex_api import API
|
from .plex_api import API
|
||||||
from . import playlist_func as PL, plex_functions as PF
|
from . import playlist_func as PL, plex_functions as PF
|
||||||
from . import backgroundthread, utils, json_rpc as js, app, variables as v
|
from . import backgroundthread, utils, json_rpc as js, app, variables as v
|
||||||
|
from . import exceptions
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.playqueue')
|
LOG = getLogger('PLEX.playqueue')
|
||||||
|
@ -88,7 +89,7 @@ def init_playqueue_from_plex_children(plex_id, transient_token=None):
|
||||||
api = API(child)
|
api = API(child)
|
||||||
try:
|
try:
|
||||||
PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id)
|
PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.error('Could not add Plex item to our playlist: %s, %s',
|
LOG.error('Could not add Plex item to our playlist: %s, %s',
|
||||||
child.tag, child.attrib)
|
child.tag, child.attrib)
|
||||||
playqueue.plex_transient_token = transient_token
|
playqueue.plex_transient_token = transient_token
|
||||||
|
@ -151,7 +152,7 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||||
i + j, i)
|
i + j, i)
|
||||||
try:
|
try:
|
||||||
PL.move_playlist_item(playqueue, i + j, i)
|
PL.move_playlist_item(playqueue, i + j, i)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.error('Could not modify playqueue positions')
|
LOG.error('Could not modify playqueue positions')
|
||||||
LOG.error('This is likely caused by mixing audio and '
|
LOG.error('This is likely caused by mixing audio and '
|
||||||
'video tracks in the Kodi playqueue')
|
'video tracks in the Kodi playqueue')
|
||||||
|
@ -167,7 +168,7 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||||
PL.add_item_to_plex_playqueue(playqueue,
|
PL.add_item_to_plex_playqueue(playqueue,
|
||||||
i,
|
i,
|
||||||
kodi_item=new_item)
|
kodi_item=new_item)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
# Could not add the element
|
# Could not add the element
|
||||||
pass
|
pass
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -196,7 +197,7 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||||
LOG.debug('Detected deletion of playqueue element at pos %s', i)
|
LOG.debug('Detected deletion of playqueue element at pos %s', i)
|
||||||
try:
|
try:
|
||||||
PL.delete_playlist_item_from_PMS(playqueue, i)
|
PL.delete_playlist_item_from_PMS(playqueue, i)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.error('Could not delete PMS element from position %s', i)
|
LOG.error('Could not delete PMS element from position %s', i)
|
||||||
LOG.error('This is likely caused by mixing audio and '
|
LOG.error('This is likely caused by mixing audio and '
|
||||||
'video tracks in the Kodi playqueue')
|
'video tracks in the Kodi playqueue')
|
||||||
|
|
|
@ -222,12 +222,14 @@ class Artwork(object):
|
||||||
else:
|
else:
|
||||||
# Not supported artwork
|
# Not supported artwork
|
||||||
return artworks
|
return artworks
|
||||||
data = DU().downloadUrl(url, authenticate=False, timeout=15)
|
data = DU().downloadUrl(url,
|
||||||
try:
|
authenticate=False,
|
||||||
data.get('test')
|
timeout=15,
|
||||||
except AttributeError:
|
return_response=True)
|
||||||
LOG.error('Could not download data from FanartTV')
|
if not data.ok:
|
||||||
|
LOG.debug('Could not download data from FanartTV')
|
||||||
return artworks
|
return artworks
|
||||||
|
data = data.json()
|
||||||
|
|
||||||
fanart_tv_types = list(v.FANART_TV_TO_KODI_TYPE)
|
fanart_tv_types = list(v.FANART_TV_TO_KODI_TYPE)
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,15 @@ METADATA_PROVIDERS = (('imdb', utils.REGEX_IMDB),
|
||||||
('tvdb', utils.REGEX_TVDB),
|
('tvdb', utils.REGEX_TVDB),
|
||||||
('tmdb', utils.REGEX_TMDB),
|
('tmdb', utils.REGEX_TMDB),
|
||||||
('anidb', utils.REGEX_ANIDB))
|
('anidb', utils.REGEX_ANIDB))
|
||||||
|
|
||||||
|
|
||||||
class Base(object):
|
class Base(object):
|
||||||
"""
|
"""
|
||||||
Processes a Plex media server's XML response
|
Processes a Plex media server's XML response
|
||||||
|
|
||||||
xml: xml.etree.ElementTree element
|
xml: xml.etree.ElementTree element
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, xml):
|
def __init__(self, xml):
|
||||||
self.xml = xml
|
self.xml = xml
|
||||||
# which media part in the XML response shall we look at if several
|
# which media part in the XML response shall we look at if several
|
||||||
|
@ -254,7 +257,21 @@ class Base(object):
|
||||||
Returns the media streams directly from the PMS xml.
|
Returns the media streams directly from the PMS xml.
|
||||||
Mind to set self.mediastream and self.part before calling this method!
|
Mind to set self.mediastream and self.part before calling this method!
|
||||||
"""
|
"""
|
||||||
return self.xml[self.mediastream][self.part]
|
try:
|
||||||
|
return self.xml[self.mediastream][self.part]
|
||||||
|
except TypeError:
|
||||||
|
# Direct Paths when we don't set mediastream and part
|
||||||
|
return self.xml[0][0]
|
||||||
|
|
||||||
|
def part_id(self):
|
||||||
|
"""
|
||||||
|
Returns the unique id of the currently active part [int]
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return int(self.xml[self.mediastream][self.part].attrib['id'])
|
||||||
|
except TypeError:
|
||||||
|
# Direct Paths when we don't set mediastream and part
|
||||||
|
return int(self.xml[0][0].attrib['id'])
|
||||||
|
|
||||||
def plot(self):
|
def plot(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -320,16 +320,15 @@ class Media(object):
|
||||||
filename,
|
filename,
|
||||||
extension)
|
extension)
|
||||||
response = DU().downloadUrl(url, return_response=True)
|
response = DU().downloadUrl(url, return_response=True)
|
||||||
try:
|
if not response.ok:
|
||||||
response.status_code
|
|
||||||
except AttributeError:
|
|
||||||
LOG.error('Could not temporarily download subtitle %s', url)
|
LOG.error('Could not temporarily download subtitle %s', url)
|
||||||
|
LOG.error('HTTP status: %s, message: %s',
|
||||||
|
response.status_code, response.text)
|
||||||
return
|
return
|
||||||
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 f:
|
||||||
with open(path_ops.encode_path(path), 'wb') as f:
|
f.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,
|
||||||
omit_check=False):
|
omit_check=False):
|
||||||
|
|
|
@ -21,6 +21,7 @@ from . import playqueue as PQ
|
||||||
from . import variables as v
|
from . import variables as v
|
||||||
from . import backgroundthread
|
from . import backgroundthread
|
||||||
from . import app
|
from . import app
|
||||||
|
from . import exceptions
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ def update_playqueue_from_PMS(playqueue,
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
try:
|
try:
|
||||||
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.error('Could now download playqueue %s', playqueue_id)
|
LOG.error('Could now download playqueue %s', playqueue_id)
|
||||||
return
|
return
|
||||||
if playqueue.id == playqueue_id:
|
if playqueue.id == playqueue_id:
|
||||||
|
@ -64,7 +65,7 @@ def update_playqueue_from_PMS(playqueue,
|
||||||
# Get new metadata for the playqueue first
|
# Get new metadata for the playqueue first
|
||||||
try:
|
try:
|
||||||
PL.get_playlist_details_from_xml(playqueue, xml)
|
PL.get_playlist_details_from_xml(playqueue, xml)
|
||||||
except PL.PlaylistError:
|
except exceptions.PlaylistError:
|
||||||
LOG.error('Could not get playqueue ID %s', playqueue_id)
|
LOG.error('Could not get playqueue ID %s', playqueue_id)
|
||||||
return
|
return
|
||||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||||
|
|
|
@ -1117,3 +1117,35 @@ def playback_decision(path, media, part, playmethod, video=True, args=None):
|
||||||
return DU().downloadUrl(utils.extend_url(url, arguments),
|
return DU().downloadUrl(utils.extend_url(url, arguments),
|
||||||
headerOptions=v.STREAMING_HEADERS,
|
headerOptions=v.STREAMING_HEADERS,
|
||||||
reraise=True)
|
reraise=True)
|
||||||
|
|
||||||
|
|
||||||
|
def change_subtitle(plex_stream_id, part_id):
|
||||||
|
"""
|
||||||
|
Tell the PMS to display/burn-in the subtitle stream with id plex_stream_id
|
||||||
|
for the Part (PMS XML etree tag "Part") with unique id part_id.
|
||||||
|
- plex_stream_id = 0 will deactivate the subtitle
|
||||||
|
- We always do this for ALL parts of a video
|
||||||
|
"""
|
||||||
|
arguments = {
|
||||||
|
'subtitleStreamID': plex_stream_id,
|
||||||
|
'allParts': 1
|
||||||
|
}
|
||||||
|
url = '{server}/library/parts/%s' % part_id
|
||||||
|
return DU().downloadUrl(utils.extend_url(url, arguments),
|
||||||
|
action_type='PUT')
|
||||||
|
|
||||||
|
|
||||||
|
def change_audio_stream(plex_stream_id, part_id):
|
||||||
|
"""
|
||||||
|
Tell the PMS to display/burn-in the subtitle stream with id plex_stream_id
|
||||||
|
for the Part (PMS XML etree tag "Part") with unique id part_id.
|
||||||
|
- plex_stream_id = 0 will deactivate the subtitle
|
||||||
|
- We always do this for ALL parts of a video
|
||||||
|
"""
|
||||||
|
arguments = {
|
||||||
|
'audioStreamID': plex_stream_id,
|
||||||
|
'allParts': 1
|
||||||
|
}
|
||||||
|
url = '{server}/library/parts/%s' % part_id
|
||||||
|
return DU().downloadUrl(utils.extend_url(url, arguments),
|
||||||
|
action_type='PUT')
|
||||||
|
|
|
@ -9,6 +9,7 @@ import xml.etree.ElementTree as etree
|
||||||
from . import app
|
from . import app
|
||||||
from . import path_ops
|
from . import path_ops
|
||||||
from . import variables as v
|
from . import variables as v
|
||||||
|
from .exceptions import SubtitleError
|
||||||
|
|
||||||
LOG = getLogger('PLEX.subtitles')
|
LOG = getLogger('PLEX.subtitles')
|
||||||
|
|
||||||
|
@ -467,7 +468,3 @@ def external_subs_from_filesystem(dirname, filename):
|
||||||
class DummySub(etree.Element):
|
class DummySub(etree.Element):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(DummySub, self).__init__('Stream-subtitle-dummy')
|
super(DummySub, self).__init__('Stream-subtitle-dummy')
|
||||||
|
|
||||||
|
|
||||||
class SubtitleError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
|
@ -45,6 +45,10 @@ ADDON_PATH = try_decode(_ADDON.getAddonInfo('path'))
|
||||||
ADDON_FOLDER = try_decode(xbmc.translatePath('special://home'))
|
ADDON_FOLDER = try_decode(xbmc.translatePath('special://home'))
|
||||||
ADDON_PROFILE = try_decode(xbmc.translatePath(_ADDON.getAddonInfo('profile')))
|
ADDON_PROFILE = try_decode(xbmc.translatePath(_ADDON.getAddonInfo('profile')))
|
||||||
|
|
||||||
|
# Used e.g. for json_rpc
|
||||||
|
KODI_VIDEO_PLAYER_ID = 1
|
||||||
|
KODI_AUDIO_PLAYER_ID = 0
|
||||||
|
|
||||||
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
|
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
|
||||||
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
||||||
KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion')
|
KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion')
|
||||||
|
|
|
@ -114,6 +114,8 @@
|
||||||
<setting id="trailerNumber" type="slider" label="39000" default="3" visible="eq(-2,true)" range="1,1,15" option="int" />
|
<setting id="trailerNumber" type="slider" label="39000" default="3" visible="eq(-2,true)" range="1,1,15" option="int" />
|
||||||
<setting id="enableSkipIntro" type="bool" label="30525" default="true" /><!-- Enable skipping of intros -->
|
<setting id="enableSkipIntro" type="bool" label="30525" default="true" /><!-- Enable skipping of intros -->
|
||||||
<setting id="firstVideoStream" type="bool" label="30546" default="false" /><!-- Pick the first video if several versions are present -->
|
<setting id="firstVideoStream" type="bool" label="30546" default="false" /><!-- Pick the first video if several versions are present -->
|
||||||
|
<setting id="audioStreamPick" type="enum" label="30547" values="Plex|Kodi" default="0" /><!-- Who picks the audio stream on playback start? -->
|
||||||
|
<setting id="subtitleStreamPick" type="enum" label="30548" values="Plex|Kodi" default="0" /><!-- Who picks subtitles on playback start? -->
|
||||||
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="30527" default="false" />
|
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="30527" default="false" />
|
||||||
<setting id="resumeJumpBack" type="slider" label="30521" default="10" range="0,1,120" option="int" visible="false"/>
|
<setting id="resumeJumpBack" type="slider" label="30521" default="10" range="0,1,120" option="int" visible="false"/>
|
||||||
<setting type="sep" />
|
<setting type="sep" />
|
||||||
|
|
Loading…
Reference in a new issue