Merge branch 'develop' into translations
This commit is contained in:
commit
5f91580e76
31 changed files with 814 additions and 427 deletions
|
@ -2,6 +2,17 @@
|
||||||
|
|
||||||
Thanks a ton for contributing to PlexKodiConnect!
|
Thanks a ton for contributing to PlexKodiConnect!
|
||||||
|
|
||||||
|
## Feature requests
|
||||||
|
|
||||||
|
* Are you missing a certain functionality? Then [visit feathub.com](http://feathub.com/croneter/PlexKodiConnect)
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
* Something not working like it's supposed to? Then [open a new issue report](https://github.com/croneter/PlexKodiConnect/wiki/How-to-Report-A-Bug)
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
|
||||||
|
* Want to help translate PlexKodiConnect? Then go [visit crowdin.com](https://crowdin.com/project/plexkodiconnect/invite)
|
||||||
|
|
||||||
## Programming
|
## Programming
|
||||||
|
|
||||||
|
@ -9,7 +20,3 @@ Thanks a ton for contributing to PlexKodiConnect!
|
||||||
* Thanks if you can follow the Python style guide [PEP8](https://www.python.org/dev/peps/pep-0008/) to keep things neat and clean
|
* Thanks if you can follow the Python style guide [PEP8](https://www.python.org/dev/peps/pep-0008/) to keep things neat and clean
|
||||||
* Thanks if you add some comments to make your code more readable ;-)
|
* Thanks if you add some comments to make your code more readable ;-)
|
||||||
|
|
||||||
|
|
||||||
## Translations
|
|
||||||
|
|
||||||
* Please [only use crowdin.com](https://crowdin.com/project/plexkodiconnect/invite) to help with translations. Don't use Github pull requests.
|
|
16
README.md
16
README.md
|
@ -1,3 +1,9 @@
|
||||||
|
##Status
|
||||||
|
|
||||||
|
[![GitHub issues](https://img.shields.io/github/issues/croneter/PlexKodiConnect.svg?maxAge=60&style=flat-square)](https://github.com/croneter/PlexKodiConnect/issues)
|
||||||
|
[![GitHub pull requests](https://img.shields.io/github/issues-pr/croneter/PlexKodiConnect.svg?maxAge=60&style=flat-square)](https://github.com/croneter/PlexKodiConnect/pulls)
|
||||||
|
|
||||||
|
|
||||||
# PlexKodiConnect (PKC)
|
# PlexKodiConnect (PKC)
|
||||||
**Combine the best frontend media player Kodi with the best multimedia backend server Plex**
|
**Combine the best frontend media player Kodi with the best multimedia backend server Plex**
|
||||||
|
|
||||||
|
@ -19,7 +25,7 @@ Please help translate PlexKodiConnect into your language: [visit crowdin.com](ht
|
||||||
* [**What is currently supported?**](#what-is-currently-supported)
|
* [**What is currently supported?**](#what-is-currently-supported)
|
||||||
* [**Known Larger Issues**](#known-larger-issues)
|
* [**Known Larger Issues**](#known-larger-issues)
|
||||||
* [**Issues being worked on**](#issues-being-worked-on)
|
* [**Issues being worked on**](#issues-being-worked-on)
|
||||||
* [**Pipeline for future development**](#what-could-be-in-the-pipeline-for-future-development)
|
* [**Requests for new features**](#requests-for-new-features)
|
||||||
* [**Checkout the PKC Wiki**](#checkout-the-pkc-wiki)
|
* [**Checkout the PKC Wiki**](#checkout-the-pkc-wiki)
|
||||||
* [**Credits**](#credits)
|
* [**Credits**](#credits)
|
||||||
|
|
||||||
|
@ -77,6 +83,7 @@ PKC currently provides the following features:
|
||||||
+ German
|
+ German
|
||||||
+ Czech, thanks @Pavuucek
|
+ Czech, thanks @Pavuucek
|
||||||
+ Spanish, thanks @bartolomesoriano
|
+ Spanish, thanks @bartolomesoriano
|
||||||
|
+ Danish, thanks @FIGHT
|
||||||
+ More coming up: [you can help!](https://crowdin.com/project/plexkodiconnect/invite)
|
+ More coming up: [you can help!](https://crowdin.com/project/plexkodiconnect/invite)
|
||||||
- [Plex Watch Later / Plex It!](https://support.plex.tv/hc/en-us/sections/200211783-Plex-It-)
|
- [Plex Watch Later / Plex It!](https://support.plex.tv/hc/en-us/sections/200211783-Plex-It-)
|
||||||
- [Plex Companion](https://support.plex.tv/hc/en-us/sections/200276908-Plex-Companion): fling Plex media (or anything else) from other Plex devices to PlexKodiConnect
|
- [Plex Companion](https://support.plex.tv/hc/en-us/sections/200276908-Plex-Companion): fling Plex media (or anything else) from other Plex devices to PlexKodiConnect
|
||||||
|
@ -113,12 +120,9 @@ However, some changes to individual items are instantly detected, e.g. if you ma
|
||||||
Have a look at the [Github Issues Page](https://github.com/croneter/PlexKodiConnect/issues). Before you open your own issue, please read [How to report a bug](https://github.com/croneter/PlexKodiConnect/wiki/How-to-Report-A-Bug).
|
Have a look at the [Github Issues Page](https://github.com/croneter/PlexKodiConnect/issues). Before you open your own issue, please read [How to report a bug](https://github.com/croneter/PlexKodiConnect/wiki/How-to-Report-A-Bug).
|
||||||
|
|
||||||
|
|
||||||
### What could be in the pipeline for future development?
|
### Requests for new features
|
||||||
|
|
||||||
- Plex channels
|
[![Feature Requests](http://feathub.com/croneter/PlexKodiConnect?format=svg)](http://feathub.com/croneter/PlexKodiConnect)
|
||||||
- Movie extras (trailers already work)
|
|
||||||
- Playlists
|
|
||||||
- Music Videos
|
|
||||||
|
|
||||||
### Checkout the PKC Wiki
|
### Checkout the PKC Wiki
|
||||||
The [Wiki can be found here](https://github.com/croneter/PlexKodiConnect/wiki) and will hopefully answer all your questions. You can even edit the wiki yourself!
|
The [Wiki can be found here](https://github.com/croneter/PlexKodiConnect/wiki) and will hopefully answer all your questions. You can even edit the wiki yourself!
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="plugin.video.plexkodiconnect"
|
<addon id="plugin.video.plexkodiconnect"
|
||||||
name="PlexKodiConnect"
|
name="PlexKodiConnect"
|
||||||
version="1.5.14"
|
version="1.6.4"
|
||||||
provider-name="croneter">
|
provider-name="croneter">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.1.0"/>
|
<import addon="xbmc.python" version="2.1.0"/>
|
||||||
|
@ -27,12 +27,16 @@
|
||||||
<summary lang="cs">Úplná integrace Plexu do Kodi</summary>
|
<summary lang="cs">Úplná integrace Plexu do Kodi</summary>
|
||||||
<summary lang="de">Komplette Integration von Plex in Kodi</summary>
|
<summary lang="de">Komplette Integration von Plex in Kodi</summary>
|
||||||
<summary lang="es">Native Integration of Plex into Kodi</summary>
|
<summary lang="es">Native Integration of Plex into Kodi</summary>
|
||||||
|
<summary lang="dk">Indbygget Integration af Plex i Kodi</summary>
|
||||||
|
<summary lang="nl">Directe integratie van Plex in Kodi</summary>
|
||||||
<description lang="en">Connect Kodi to your Plex Media Server. This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases (as this plugin directly changes them). Use at your own risk!</description>
|
<description lang="en">Connect Kodi to your Plex Media Server. This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases (as this plugin directly changes them). Use at your own risk!</description>
|
||||||
<description lang="en_gb">Connect Kodi to your Plex Media Server. This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases (as this plugin directly changes them). Use at your own risk!</description>
|
<description lang="en_gb">Connect Kodi to your Plex Media Server. This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases (as this plugin directly changes them). Use at your own risk!</description>
|
||||||
<description lang="en_us">Connect Kodi to your Plex Media Server. This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases (as this plugin directly changes them). Use at your own risk!</description>
|
<description lang="en_us">Connect Kodi to your Plex Media Server. This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases (as this plugin directly changes them). Use at your own risk!</description>
|
||||||
<description lang="cs">Připojte Kodi ke svému Plex Media Serveru. Tento doplněk předpokládá, že spravujete veškerá svá videa pomocí Plexu (nikoliv pomocí Kodi). Můžete přijít o data uložená ve video a hudební databázi Kodi (tento doplněk je přímo mění). Používejte na vlastní nebezpečí!</description>
|
<description lang="cs">Připojte Kodi ke svému Plex Media Serveru. Tento doplněk předpokládá, že spravujete veškerá svá videa pomocí Plexu (nikoliv pomocí Kodi). Můžete přijít o data uložená ve video a hudební databázi Kodi (tento doplněk je přímo mění). Používejte na vlastní nebezpečí!</description>
|
||||||
<description lang="de">Verbindet Kodi mit deinem Plex Media Server. Dieses Addon geht davon aus, dass du all deine Videos mit Plex verwaltest (und keine direkt mit Kodi). Du wirst möglicherweise Daten verlieren, die bereits in der Kodi Video- und/oder Musik-Datenbank gespeichert sind (da dieses Addon beide Datenbanken direkt verändert). Verwende auf eigene Gefahr!</description>
|
<description lang="de">Verbindet Kodi mit deinem Plex Media Server. Dieses Addon geht davon aus, dass du all deine Videos mit Plex verwaltest (und keine direkt mit Kodi). Du wirst möglicherweise Daten verlieren, die bereits in der Kodi Video- und/oder Musik-Datenbank gespeichert sind (da dieses Addon beide Datenbanken direkt verändert). Verwende auf eigene Gefahr!</description>
|
||||||
<description lang="es">Connect Kodi to your Plex Media Server. This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases (as this plugin directly changes them). Use at your own risk!</description>
|
<description lang="es">Connect Kodi to your Plex Media Server. This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases (as this plugin directly changes them). Use at your own risk!</description>
|
||||||
|
<description lang="dk">Tilslut Kodi til din Plex Media Server. Dette plugin forudsætter, at du administrere alle dine videoer med Plex (og ikke med Kodi). Du kan miste data som allerede er gemt i Kodi video og musik-databaser (dette plugin ændrer direkte i dem). Brug på eget ansvar!</description>
|
||||||
|
<description lang="nl">Verbind Kodi met je Plex Media Server. Deze plugin gaat ervan uit dat je al je video's met Plex (en niet met Kodi) beheerd. Je kunt gegevens reeds opgeslagen in de databases voor video en muziek van Kodi (deze plugin wijzigt deze gegevens direct) verliezen. Gebruik op eigen risico!</description>
|
||||||
<platform>all</platform>
|
<platform>all</platform>
|
||||||
<license>GPL v2.0</license>
|
<license>GPL v2.0</license>
|
||||||
<forum>https://forums.plex.tv</forum>
|
<forum>https://forums.plex.tv</forum>
|
||||||
|
|
|
@ -1,3 +1,51 @@
|
||||||
|
version 1.6.4 (beta only)
|
||||||
|
- Amazon Alexa support! Be mindful to check the Alexa forum thread first; there are still many issues completely unrelated to PKC
|
||||||
|
- Enable skipping for Plex Companion
|
||||||
|
- Set default companion name to PlexKodiConnect
|
||||||
|
|
||||||
|
version 1.6.3
|
||||||
|
- Fix UnicodeEncodeError for non ASCII filenames in playback_starter
|
||||||
|
- Cleanup playlist/playqueue string/unicode
|
||||||
|
|
||||||
|
version 1.6.2
|
||||||
|
- Fix Plex Web Issue, thanks @AllanMar
|
||||||
|
- Fix TypeError on manually entering PMS port
|
||||||
|
- Fix KeyError
|
||||||
|
- Update Danish translation
|
||||||
|
- Update readme
|
||||||
|
|
||||||
|
version 1.6.1
|
||||||
|
- New Danish translation, thanks @Osberg
|
||||||
|
- Fix UnicodeDecodeError for non-ASCII filenames
|
||||||
|
- Better error handling for Plex Companion
|
||||||
|
- Fix ValueError for Watch Later
|
||||||
|
- Try to skip new PMS items we've already processed
|
||||||
|
- Fix TypeError
|
||||||
|
|
||||||
|
version 1.6.0
|
||||||
|
A DATABASE RESET IS ABSOLUTELY NECESSARY if you're not using beta PKC
|
||||||
|
Make previous version available for everyone. The highlights:
|
||||||
|
- New Spanish translation, thanks @bartolomesoriano
|
||||||
|
- New Czech translation, thanks @Pavuucek
|
||||||
|
- Plex Companion is completely rewired and should now handly anything you throw at it
|
||||||
|
- Early compatibility with Kodi 18 Leia
|
||||||
|
- New playback startup mechanism for plugin paths
|
||||||
|
- Code rebranding from Emby to Plex, including a plex.db database :-)
|
||||||
|
- Fixes to Kodi ratings
|
||||||
|
- Fix playstate and PMS item changes not working/not propagating anymore (caused by a change Plex made with the websocket interface)
|
||||||
|
- Improvements to the way PKC behaves if the PMS goes offline
|
||||||
|
- New setting to always transcode if the video bitrate is above a certain threshold (will not work with direct paths)
|
||||||
|
- Be smarter when deciding when to transcode
|
||||||
|
- Only sign the user out if the PMS says so
|
||||||
|
- Cache missing artwork on PKC startup
|
||||||
|
- Lots of code refactoring and code optimizations
|
||||||
|
- Tons of fixes
|
||||||
|
|
||||||
|
version 1.5.15 (beta only)
|
||||||
|
- Fix ratings for movies
|
||||||
|
- Fixes to Plex Companion
|
||||||
|
- Always run only one instance of PKC
|
||||||
|
|
||||||
version 1.5.14 (beta only)
|
version 1.5.14 (beta only)
|
||||||
- Krypton: Fix ratings for episodes and TV shows
|
- Krypton: Fix ratings for episodes and TV shows
|
||||||
- Plex Companion: Fix KeyError for Plex Web
|
- Plex Companion: Fix KeyError for Plex Web
|
||||||
|
|
|
@ -161,8 +161,7 @@ class Main():
|
||||||
modes[mode](itemid, params=argv[2])
|
modes[mode](itemid, params=argv[2])
|
||||||
elif mode == 'Plex_Node':
|
elif mode == 'Plex_Node':
|
||||||
modes[mode](params.get('id'),
|
modes[mode](params.get('id'),
|
||||||
params.get('viewOffset'),
|
params.get('viewOffset'))
|
||||||
params.get('plex_type'))
|
|
||||||
else:
|
else:
|
||||||
modes[mode]()
|
modes[mode]()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -513,4 +513,7 @@
|
||||||
<string id="39601">Could not stop the database from running. Please try again later.</string>
|
<string id="39601">Could not stop the database from running. Please try again later.</string>
|
||||||
<string id="39602">Remove all cached artwork? (recommended!)</string>
|
<string id="39602">Remove all cached artwork? (recommended!)</string>
|
||||||
<string id="39603">Reset all PlexKodiConnect Addon settings? (this is usually NOT recommended and unnecessary!)</string>
|
<string id="39603">Reset all PlexKodiConnect Addon settings? (this is usually NOT recommended and unnecessary!)</string>
|
||||||
|
|
||||||
|
<string id="39700">Amazon Alexa (Voice Recognition)</string>
|
||||||
|
<string id="39701">Alexa aktivieren</string>
|
||||||
</strings>
|
</strings>
|
|
@ -513,4 +513,7 @@
|
||||||
<string id="39601">Kodi Datenbank konnte nicht gestoppt werden. Bitte später erneut versuchen.</string>
|
<string id="39601">Kodi Datenbank konnte nicht gestoppt werden. Bitte später erneut versuchen.</string>
|
||||||
<string id="39602">Alle zwischengespeicherten Bilder löschen? (empfohlen!)</string>
|
<string id="39602">Alle zwischengespeicherten Bilder löschen? (empfohlen!)</string>
|
||||||
<string id="39603">Alle PlexKodiConnect Einstellungen zurücksetzen? (normalerweise NICHT empfohlen und nicht nötig!)</string>
|
<string id="39603">Alle PlexKodiConnect Einstellungen zurücksetzen? (normalerweise NICHT empfohlen und nicht nötig!)</string>
|
||||||
|
|
||||||
|
<string id="39700">Amazon Alexa (Spracherkennung)</string>
|
||||||
|
<string id="39701">Alexa aktivieren</string>
|
||||||
</strings>
|
</strings>
|
|
@ -1256,26 +1256,26 @@ class API():
|
||||||
favorite = False
|
favorite = False
|
||||||
try:
|
try:
|
||||||
playcount = int(item['viewCount'])
|
playcount = int(item['viewCount'])
|
||||||
except KeyError:
|
except (KeyError, ValueError):
|
||||||
playcount = None
|
playcount = None
|
||||||
played = True if playcount else False
|
played = True if playcount else False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lastPlayedDate = DateToKodi(int(item['lastViewedAt']))
|
lastPlayedDate = DateToKodi(int(item['lastViewedAt']))
|
||||||
except KeyError:
|
except (KeyError, ValueError):
|
||||||
lastPlayedDate = None
|
lastPlayedDate = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
userrating = int(float(item['userRating']))
|
userrating = int(float(item['userRating']))
|
||||||
except KeyError:
|
except (KeyError, ValueError):
|
||||||
userrating = 0
|
userrating = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rating = float(item['audienceRating'])
|
rating = float(item['audienceRating'])
|
||||||
except KeyError:
|
except (KeyError, ValueError):
|
||||||
try:
|
try:
|
||||||
rating = float(item['rating'])
|
rating = float(item['rating'])
|
||||||
except KeyError:
|
except (KeyError, ValueError):
|
||||||
rating = 0.0
|
rating = 0.0
|
||||||
|
|
||||||
resume, runtime = self.getRuntime()
|
resume, runtime = self.getRuntime()
|
||||||
|
|
|
@ -6,14 +6,16 @@ from socket import SHUT_RDWR
|
||||||
|
|
||||||
from xbmc import sleep
|
from xbmc import sleep
|
||||||
|
|
||||||
from utils import settings, ThreadMethodsAdditionalSuspend, ThreadMethods
|
from utils import settings, ThreadMethodsAdditionalSuspend, ThreadMethods, \
|
||||||
|
window
|
||||||
from plexbmchelper import listener, plexgdm, subscribers, functions, \
|
from plexbmchelper import listener, plexgdm, subscribers, functions, \
|
||||||
httppersist, plexsettings
|
httppersist, plexsettings
|
||||||
from PlexFunctions import ParseContainerKey, GetPlexMetadata
|
from PlexFunctions import ParseContainerKey, GetPlexMetadata
|
||||||
from PlexAPI import API
|
from PlexAPI import API
|
||||||
import player
|
import player
|
||||||
from entrypoint import Plex_Node
|
from entrypoint import Plex_Node
|
||||||
from variables import KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE
|
import variables as v
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -35,7 +37,7 @@ class PlexCompanion(Thread):
|
||||||
# Start GDM for server/client discovery
|
# Start GDM for server/client discovery
|
||||||
self.client = plexgdm.plexgdm()
|
self.client = plexgdm.plexgdm()
|
||||||
self.client.clientDetails(self.settings)
|
self.client.clientDetails(self.settings)
|
||||||
log.debug("Registration string is: %s "
|
log.debug("Registration string is:\n%s"
|
||||||
% self.client.getClientDetails())
|
% self.client.getClientDetails())
|
||||||
# kodi player instance
|
# kodi player instance
|
||||||
self.player = player.Player()
|
self.player = player.Player()
|
||||||
|
@ -77,20 +79,42 @@ class PlexCompanion(Thread):
|
||||||
log.debug('Processing: %s' % task)
|
log.debug('Processing: %s' % task)
|
||||||
data = task['data']
|
data = task['data']
|
||||||
|
|
||||||
if (task['action'] == 'playlist' and
|
if task['action'] == 'alexa':
|
||||||
|
# e.g. Alexa
|
||||||
|
xml = GetPlexMetadata(data['key'])
|
||||||
|
try:
|
||||||
|
xml[0].attrib
|
||||||
|
except (AttributeError, IndexError, TypeError):
|
||||||
|
log.error('Could not download Plex metadata')
|
||||||
|
return
|
||||||
|
api = API(xml[0])
|
||||||
|
if api.getType() == v.PLEX_TYPE_ALBUM:
|
||||||
|
log.debug('Plex music album detected')
|
||||||
|
self.mgr.playqueue.init_playqueue_from_plex_children(
|
||||||
|
api.getRatingKey())
|
||||||
|
else:
|
||||||
|
thread = Thread(target=Plex_Node,
|
||||||
|
args=('{server}%s' % data.get('key'),
|
||||||
|
data.get('offset'),
|
||||||
|
True,
|
||||||
|
False),)
|
||||||
|
thread.setDaemon(True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
elif (task['action'] == 'playlist' and
|
||||||
data.get('address') == 'node.plexapp.com'):
|
data.get('address') == 'node.plexapp.com'):
|
||||||
# E.g. watch later initiated by Companion
|
# E.g. watch later initiated by Companion
|
||||||
thread = Thread(target=Plex_Node,
|
thread = Thread(target=Plex_Node,
|
||||||
args=('{server}%s' % data.get('key'),
|
args=('{server}%s' % data.get('key'),
|
||||||
data.get('offset'),
|
data.get('offset'),
|
||||||
data.get('type'),
|
|
||||||
True),)
|
True),)
|
||||||
thread.setDaemon(True)
|
thread.setDaemon(True)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
elif task['action'] == 'playlist':
|
elif task['action'] == 'playlist':
|
||||||
# Get the playqueue ID
|
# Get the playqueue ID
|
||||||
try:
|
try:
|
||||||
_, ID, query = ParseContainerKey(data['containerKey'])
|
typus, ID, query = ParseContainerKey(data['containerKey'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error('Exception while processing: %s' % e)
|
log.error('Exception while processing: %s' % e)
|
||||||
import traceback
|
import traceback
|
||||||
|
@ -98,14 +122,19 @@ class PlexCompanion(Thread):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
||||||
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
|
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# E.g. Plex web does not supply the media type
|
# E.g. Plex web does not supply the media type
|
||||||
# Still need to figure out the type (video vs. music vs. pix)
|
# Still need to figure out the type (video vs. music vs. pix)
|
||||||
xml = GetPlexMetadata(data['key'])
|
xml = GetPlexMetadata(data['key'])
|
||||||
|
try:
|
||||||
|
xml[0].attrib
|
||||||
|
except (AttributeError, IndexError, TypeError):
|
||||||
|
log.error('Could not download Plex metadata')
|
||||||
|
return
|
||||||
api = API(xml[0])
|
api = API(xml[0])
|
||||||
playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
||||||
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()])
|
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()])
|
||||||
self.mgr.playqueue.update_playqueue_from_PMS(
|
self.mgr.playqueue.update_playqueue_from_PMS(
|
||||||
playqueue,
|
playqueue,
|
||||||
ID,
|
ID,
|
||||||
|
@ -126,6 +155,7 @@ class PlexCompanion(Thread):
|
||||||
jsonClass, requestMgr, self.player, self.mgr)
|
jsonClass, requestMgr, self.player, self.mgr)
|
||||||
|
|
||||||
queue = Queue.Queue(maxsize=100)
|
queue = Queue.Queue(maxsize=100)
|
||||||
|
self.queue = queue
|
||||||
|
|
||||||
if settings('plexCompanion') == 'true':
|
if settings('plexCompanion') == 'true':
|
||||||
# Start up httpd
|
# Start up httpd
|
||||||
|
@ -188,6 +218,7 @@ class PlexCompanion(Thread):
|
||||||
log.debug("Client is no longer registered. "
|
log.debug("Client is no longer registered. "
|
||||||
"Plex Companion still running on port %s"
|
"Plex Companion still running on port %s"
|
||||||
% self.settings['myport'])
|
% self.settings['myport'])
|
||||||
|
client.register_as_client()
|
||||||
# Get and set servers
|
# Get and set servers
|
||||||
if message_count % 30 == 0:
|
if message_count % 30 == 0:
|
||||||
subscriptionManager.serverlist = client.getServerList()
|
subscriptionManager.serverlist = client.getServerList()
|
||||||
|
@ -209,7 +240,7 @@ class PlexCompanion(Thread):
|
||||||
queue.task_done()
|
queue.task_done()
|
||||||
# Don't sleep
|
# Don't sleep
|
||||||
continue
|
continue
|
||||||
sleep(20)
|
sleep(50)
|
||||||
|
|
||||||
client.stop_all()
|
client.stop_all()
|
||||||
if httpd:
|
if httpd:
|
||||||
|
|
|
@ -99,7 +99,7 @@ def setKodiWebServerDetails():
|
||||||
result = loads(result)
|
result = loads(result)
|
||||||
try:
|
try:
|
||||||
xbmc_username = result['result']['value']
|
xbmc_username = result['result']['value']
|
||||||
except TypeError:
|
except (TypeError, KeyError):
|
||||||
pass
|
pass
|
||||||
web_pass = {
|
web_pass = {
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
|
|
|
@ -39,7 +39,7 @@ def getXArgsDeviceInfo(options=None):
|
||||||
'X-Plex-Product': v.ADDON_NAME,
|
'X-Plex-Product': v.ADDON_NAME,
|
||||||
'X-Plex-Version': v.ADDON_VERSION,
|
'X-Plex-Version': v.ADDON_VERSION,
|
||||||
'X-Plex-Client-Identifier': getDeviceId(),
|
'X-Plex-Client-Identifier': getDeviceId(),
|
||||||
'X-Plex-Provides': 'client,controller,player',
|
'X-Plex-Provides': 'client,controller,player,pubsub-player',
|
||||||
}
|
}
|
||||||
if window('pms_token'):
|
if window('pms_token'):
|
||||||
xargs['X-Plex-Token'] = window('pms_token')
|
xargs['X-Plex-Token'] = window('pms_token')
|
||||||
|
|
187
resources/lib/companion.py
Normal file
187
resources/lib/companion.py
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import logging
|
||||||
|
from re import compile as re_compile
|
||||||
|
|
||||||
|
from xbmc import Player
|
||||||
|
|
||||||
|
from utils import JSONRPC
|
||||||
|
from variables import ALEXA_TO_COMPANION
|
||||||
|
from playqueue import Playqueue
|
||||||
|
from PlexFunctions import GetPlexKeyNumber
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
log = logging.getLogger("PLEX."+__name__)
|
||||||
|
|
||||||
|
REGEX_PLAYQUEUES = re_compile(r'''/playQueues/(\d+)$''')
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def getPlayers():
|
||||||
|
info = JSONRPC("Player.GetActivePlayers").execute()['result'] or []
|
||||||
|
ret = {}
|
||||||
|
for player in info:
|
||||||
|
player['playerid'] = int(player['playerid'])
|
||||||
|
ret[player['type']] = player
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def getPlayerIds():
|
||||||
|
ret = []
|
||||||
|
for player in getPlayers().values():
|
||||||
|
ret.append(player['playerid'])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def getPlaylistId(typus):
|
||||||
|
"""
|
||||||
|
typus: one of the Kodi types, e.g. audio or video
|
||||||
|
|
||||||
|
Returns None if nothing was found
|
||||||
|
"""
|
||||||
|
for playlist in getPlaylists():
|
||||||
|
if playlist.get('type') == typus:
|
||||||
|
return playlist.get('playlistid')
|
||||||
|
|
||||||
|
|
||||||
|
def getPlaylists():
|
||||||
|
"""
|
||||||
|
Returns a list, e.g.
|
||||||
|
[
|
||||||
|
{u'playlistid': 0, u'type': u'audio'},
|
||||||
|
{u'playlistid': 1, u'type': u'video'},
|
||||||
|
{u'playlistid': 2, u'type': u'picture'}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
return JSONRPC('Playlist.GetPlaylists').execute()
|
||||||
|
|
||||||
|
|
||||||
|
def millisToTime(t):
|
||||||
|
millis = int(t)
|
||||||
|
seconds = millis / 1000
|
||||||
|
minutes = seconds / 60
|
||||||
|
hours = minutes / 60
|
||||||
|
seconds = seconds % 60
|
||||||
|
minutes = minutes % 60
|
||||||
|
millis = millis % 1000
|
||||||
|
return {'hours': hours,
|
||||||
|
'minutes': minutes,
|
||||||
|
'seconds': seconds,
|
||||||
|
'milliseconds': millis}
|
||||||
|
|
||||||
|
|
||||||
|
def skipTo(params):
|
||||||
|
# Does not seem to be implemented yet
|
||||||
|
playQueueItemID = params.get('playQueueItemID', 'not available')
|
||||||
|
library, plex_id = GetPlexKeyNumber(params.get('key'))
|
||||||
|
log.debug('Skipping to playQueueItemID %s, plex_id %s'
|
||||||
|
% (playQueueItemID, plex_id))
|
||||||
|
found = True
|
||||||
|
playqueues = Playqueue()
|
||||||
|
for (player, ID) in getPlayers().iteritems():
|
||||||
|
playqueue = playqueues.get_playqueue_from_type(player)
|
||||||
|
for i, item in enumerate(playqueue.items):
|
||||||
|
if item.ID == playQueueItemID or item.plex_id == plex_id:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
log.debug('Item not found to skip to')
|
||||||
|
found = False
|
||||||
|
if found:
|
||||||
|
Player().play(playqueue.kodi_pl, None, False, i)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_alexa_to_companion(dictionary):
|
||||||
|
for key in dictionary:
|
||||||
|
if key in ALEXA_TO_COMPANION:
|
||||||
|
dictionary[ALEXA_TO_COMPANION[key]] = dictionary[key]
|
||||||
|
del dictionary[key]
|
||||||
|
|
||||||
|
|
||||||
|
def process_command(request_path, params, queue=None):
|
||||||
|
"""
|
||||||
|
queue: Queue() of PlexCompanion.py
|
||||||
|
"""
|
||||||
|
if params.get('deviceName') == 'Alexa':
|
||||||
|
convert_alexa_to_companion(params)
|
||||||
|
log.debug('Received request_path: %s, params: %s' % (request_path, params))
|
||||||
|
if "/playMedia" in request_path:
|
||||||
|
# We need to tell service.py
|
||||||
|
action = 'alexa' if params.get('deviceName') == 'Alexa' else 'playlist'
|
||||||
|
queue.put({
|
||||||
|
'action': action,
|
||||||
|
'data': params
|
||||||
|
})
|
||||||
|
|
||||||
|
elif request_path == "player/playback/setParameters":
|
||||||
|
if 'volume' in params:
|
||||||
|
volume = int(params['volume'])
|
||||||
|
log.debug("Adjusting the volume to %s" % volume)
|
||||||
|
JSONRPC('Application.SetVolume').execute({"volume": volume})
|
||||||
|
|
||||||
|
elif request_path == "player/playback/play":
|
||||||
|
for playerid in getPlayerIds():
|
||||||
|
JSONRPC("Player.PlayPause").execute({"playerid": playerid,
|
||||||
|
"play": True})
|
||||||
|
|
||||||
|
elif request_path == "player/playback/pause":
|
||||||
|
for playerid in getPlayerIds():
|
||||||
|
JSONRPC("Player.PlayPause").execute({"playerid": playerid,
|
||||||
|
"play": False})
|
||||||
|
|
||||||
|
elif request_path == "player/playback/stop":
|
||||||
|
for playerid in getPlayerIds():
|
||||||
|
JSONRPC("Player.Stop").execute({"playerid": playerid})
|
||||||
|
|
||||||
|
elif request_path == "player/playback/seekTo":
|
||||||
|
for playerid in getPlayerIds():
|
||||||
|
JSONRPC("Player.Seek").execute(
|
||||||
|
{"playerid": playerid,
|
||||||
|
"value": millisToTime(params.get('offset', 0))})
|
||||||
|
|
||||||
|
elif request_path == "player/playback/stepForward":
|
||||||
|
for playerid in getPlayerIds():
|
||||||
|
JSONRPC("Player.Seek").execute({"playerid": playerid,
|
||||||
|
"value": "smallforward"})
|
||||||
|
|
||||||
|
elif request_path == "player/playback/stepBack":
|
||||||
|
for playerid in getPlayerIds():
|
||||||
|
JSONRPC("Player.Seek").execute({"playerid": playerid,
|
||||||
|
"value": "smallbackward"})
|
||||||
|
|
||||||
|
elif request_path == "player/playback/skipNext":
|
||||||
|
for playerid in getPlayerIds():
|
||||||
|
JSONRPC("Player.GoTo").execute({"playerid": playerid,
|
||||||
|
"to": "next"})
|
||||||
|
|
||||||
|
elif request_path == "player/playback/skipPrevious":
|
||||||
|
for playerid in getPlayerIds():
|
||||||
|
JSONRPC("Player.GoTo").execute({"playerid": playerid,
|
||||||
|
"to": "previous"})
|
||||||
|
|
||||||
|
elif request_path == "player/playback/skipTo":
|
||||||
|
skipTo(params)
|
||||||
|
|
||||||
|
elif request_path == "player/navigation/moveUp":
|
||||||
|
JSONRPC("Input.Up").execute()
|
||||||
|
|
||||||
|
elif request_path == "player/navigation/moveDown":
|
||||||
|
JSONRPC("Input.Down").execute()
|
||||||
|
|
||||||
|
elif request_path == "player/navigation/moveLeft":
|
||||||
|
JSONRPC("Input.Left").execute()
|
||||||
|
|
||||||
|
elif request_path == "player/navigation/moveRight":
|
||||||
|
JSONRPC("Input.Right").execute()
|
||||||
|
|
||||||
|
elif request_path == "player/navigation/select":
|
||||||
|
JSONRPC("Input.Select").execute()
|
||||||
|
|
||||||
|
elif request_path == "player/navigation/home":
|
||||||
|
JSONRPC("Input.Home").execute()
|
||||||
|
|
||||||
|
elif request_path == "player/navigation/back":
|
||||||
|
JSONRPC("Input.Back").execute()
|
||||||
|
|
||||||
|
else:
|
||||||
|
log.error('Unknown request path: %s' % request_path)
|
|
@ -14,6 +14,7 @@ from utils import window, settings, language as lang, dialog, tryDecode,\
|
||||||
tryEncode, CatchExceptions, JSONRPC
|
tryEncode, CatchExceptions, JSONRPC
|
||||||
import downloadutils
|
import downloadutils
|
||||||
import playbackutils as pbutils
|
import playbackutils as pbutils
|
||||||
|
import plexdb_functions as plexdb
|
||||||
|
|
||||||
from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \
|
from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \
|
||||||
GetMachineIdentifier
|
GetMachineIdentifier
|
||||||
|
@ -96,7 +97,7 @@ def togglePlexTV():
|
||||||
sound=False)
|
sound=False)
|
||||||
|
|
||||||
|
|
||||||
def Plex_Node(url, viewOffset, plex_type, playdirectly=False):
|
def Plex_Node(url, viewOffset, playdirectly=False, node=True):
|
||||||
"""
|
"""
|
||||||
Called only for a SINGLE element for Plex.tv watch later
|
Called only for a SINGLE element for Plex.tv watch later
|
||||||
|
|
||||||
|
@ -120,11 +121,25 @@ def Plex_Node(url, viewOffset, plex_type, playdirectly=False):
|
||||||
else:
|
else:
|
||||||
window('plex_customplaylist.seektime', value=str(viewOffset))
|
window('plex_customplaylist.seektime', value=str(viewOffset))
|
||||||
log.info('Set resume point to %s' % str(viewOffset))
|
log.info('Set resume point to %s' % str(viewOffset))
|
||||||
typus = v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type]
|
api = API(xml[0])
|
||||||
|
typus = v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()]
|
||||||
|
if node is True:
|
||||||
|
plex_id = None
|
||||||
|
kodi_id = 'plexnode'
|
||||||
|
else:
|
||||||
|
plex_id = api.getRatingKey()
|
||||||
|
kodi_id = None
|
||||||
|
with plexdb.Get_Plex_DB() as plex_db:
|
||||||
|
plexdb_item = plex_db.getItem_byId(plex_id)
|
||||||
|
try:
|
||||||
|
kodi_id = plexdb_item[0]
|
||||||
|
except TypeError:
|
||||||
|
log.info('Couldnt find item %s in Kodi db'
|
||||||
|
% api.getRatingKey())
|
||||||
playqueue = Playqueue().get_playqueue_from_type(typus)
|
playqueue = Playqueue().get_playqueue_from_type(typus)
|
||||||
result = pbutils.PlaybackUtils(xml, playqueue).play(
|
result = pbutils.PlaybackUtils(xml, playqueue).play(
|
||||||
None,
|
plex_id,
|
||||||
kodi_id='plexnode',
|
kodi_id=kodi_id,
|
||||||
plex_lib_UUID=xml.attrib.get('librarySectionUUID'))
|
plex_lib_UUID=xml.attrib.get('librarySectionUUID'))
|
||||||
if result.listitem:
|
if result.listitem:
|
||||||
listitem = convert_PKC_to_listitem(result.listitem)
|
listitem = convert_PKC_to_listitem(result.listitem)
|
||||||
|
|
|
@ -6,13 +6,12 @@ import logging
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
|
|
||||||
from utils import settings, window, language as lang
|
from utils import settings, window, language as lang, tryEncode
|
||||||
import downloadutils
|
import downloadutils
|
||||||
from userclient import UserClient
|
from userclient import UserClient
|
||||||
|
|
||||||
from PlexAPI import PlexAPI
|
from PlexAPI import PlexAPI
|
||||||
from PlexFunctions import GetMachineIdentifier, get_PMS_settings
|
from PlexFunctions import GetMachineIdentifier, get_PMS_settings
|
||||||
import variables as v
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -257,7 +256,8 @@ class InitialSetup():
|
||||||
log.warn('Not authorized even though we are signed '
|
log.warn('Not authorized even though we are signed '
|
||||||
' in to plex.tv correctly')
|
' in to plex.tv correctly')
|
||||||
self.dialog.ok(lang(29999), '%s %s'
|
self.dialog.ok(lang(29999), '%s %s'
|
||||||
% lang(39214) + server['name'])
|
% (lang(39214),
|
||||||
|
tryEncode(server['name'])))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
|
@ -316,14 +316,14 @@ class Movies(Items):
|
||||||
# Update the movie entry
|
# Update the movie entry
|
||||||
if v.KODIVERSION >= 17:
|
if v.KODIVERSION >= 17:
|
||||||
# update new ratings Kodi 17
|
# update new ratings Kodi 17
|
||||||
ratingid = self.kodi_db.get_ratingid(movieid,
|
rating_id = self.kodi_db.get_ratingid(movieid,
|
||||||
v.KODI_TYPE_MOVIE)
|
v.KODI_TYPE_MOVIE)
|
||||||
self.kodi_db.update_ratings(movieid,
|
self.kodi_db.update_ratings(movieid,
|
||||||
v.KODI_TYPE_MOVIE,
|
v.KODI_TYPE_MOVIE,
|
||||||
"default",
|
"default",
|
||||||
rating,
|
rating,
|
||||||
votecount,
|
votecount,
|
||||||
ratingid)
|
rating_id)
|
||||||
# update new uniqueid Kodi 17
|
# update new uniqueid Kodi 17
|
||||||
uniqueid = self.kodi_db.get_uniqueid(movieid,
|
uniqueid = self.kodi_db.get_uniqueid(movieid,
|
||||||
v.KODI_TYPE_MOVIE)
|
v.KODI_TYPE_MOVIE)
|
||||||
|
@ -342,10 +342,10 @@ class Movies(Items):
|
||||||
WHERE idMovie = ?
|
WHERE idMovie = ?
|
||||||
'''
|
'''
|
||||||
kodicursor.execute(query, (title, plot, shortplot, tagline,
|
kodicursor.execute(query, (title, plot, shortplot, tagline,
|
||||||
votecount, rating, writer, year, imdb, sorttitle, runtime,
|
votecount, rating_id, writer, year, imdb, sorttitle,
|
||||||
mpaa, genre, director, title, studio, trailer, country,
|
runtime, mpaa, genre, director, title, studio, trailer,
|
||||||
playurl, pathid, fileid, year, userdata['UserRating'],
|
country, playurl, pathid, fileid, year,
|
||||||
movieid))
|
userdata['UserRating'], movieid))
|
||||||
else:
|
else:
|
||||||
query = '''
|
query = '''
|
||||||
UPDATE movie
|
UPDATE movie
|
||||||
|
@ -365,7 +365,8 @@ class Movies(Items):
|
||||||
log.info("ADD movie itemid: %s - Title: %s" % (itemid, title))
|
log.info("ADD movie itemid: %s - Title: %s" % (itemid, title))
|
||||||
if v.KODIVERSION >= 17:
|
if v.KODIVERSION >= 17:
|
||||||
# add new ratings Kodi 17
|
# add new ratings Kodi 17
|
||||||
self.kodi_db.add_ratings(self.kodi_db.create_entry_rating(),
|
rating_id = self.kodi_db.create_entry_rating()
|
||||||
|
self.kodi_db.add_ratings(rating_id,
|
||||||
movieid,
|
movieid,
|
||||||
v.KODI_TYPE_MOVIE,
|
v.KODI_TYPE_MOVIE,
|
||||||
"default",
|
"default",
|
||||||
|
@ -385,9 +386,9 @@ class Movies(Items):
|
||||||
?, ?, ?, ?, ?, ?, ?)
|
?, ?, ?, ?, ?, ?, ?)
|
||||||
'''
|
'''
|
||||||
kodicursor.execute(query, (movieid, fileid, title, plot,
|
kodicursor.execute(query, (movieid, fileid, title, plot,
|
||||||
shortplot, tagline, votecount, rating, writer, year, imdb,
|
shortplot, tagline, votecount, rating_id, writer, year,
|
||||||
sorttitle, runtime, mpaa, genre, director, title, studio,
|
imdb, sorttitle, runtime, mpaa, genre, director, title,
|
||||||
trailer, country, playurl, pathid, year,
|
studio, trailer, country, playurl, pathid, year,
|
||||||
userdata['UserRating']))
|
userdata['UserRating']))
|
||||||
else:
|
else:
|
||||||
query = '''
|
query = '''
|
||||||
|
|
|
@ -321,6 +321,11 @@ class LibrarySync(Thread):
|
||||||
def __init__(self, callback=None):
|
def __init__(self, callback=None):
|
||||||
self.mgr = callback
|
self.mgr = callback
|
||||||
|
|
||||||
|
# Dict of items we just processed in order to prevent a reprocessing
|
||||||
|
# caused by websocket
|
||||||
|
self.just_processed = {}
|
||||||
|
# How long do we wait until we start re-processing? (in seconds)
|
||||||
|
self.ignore_just_processed = 10*60
|
||||||
self.itemsToProcess = []
|
self.itemsToProcess = []
|
||||||
self.sessionKeys = []
|
self.sessionKeys = []
|
||||||
self.fanartqueue = Queue.Queue()
|
self.fanartqueue = Queue.Queue()
|
||||||
|
@ -532,6 +537,9 @@ class LibrarySync(Thread):
|
||||||
# True: we're syncing only the delta, e.g. different checksum
|
# True: we're syncing only the delta, e.g. different checksum
|
||||||
self.compare = not repair
|
self.compare = not repair
|
||||||
|
|
||||||
|
# Empty our list of item's we've just processed in the past
|
||||||
|
self.just_processed = {}
|
||||||
|
|
||||||
self.new_items_only = True
|
self.new_items_only = True
|
||||||
# This will also update playstates and userratings!
|
# This will also update playstates and userratings!
|
||||||
log.info('Running fullsync for NEW PMS items with repair=%s' % repair)
|
log.info('Running fullsync for NEW PMS items with repair=%s' % repair)
|
||||||
|
@ -884,6 +892,7 @@ class LibrarySync(Thread):
|
||||||
self.allPlexElementsId APPENDED(!!) dict
|
self.allPlexElementsId APPENDED(!!) dict
|
||||||
= {itemid: checksum}
|
= {itemid: checksum}
|
||||||
"""
|
"""
|
||||||
|
now = getUnixTimestamp()
|
||||||
if self.new_items_only is True:
|
if self.new_items_only is True:
|
||||||
# Only process Plex items that Kodi does not already have in lib
|
# Only process Plex items that Kodi does not already have in lib
|
||||||
for item in xml:
|
for item in xml:
|
||||||
|
@ -903,6 +912,7 @@ class LibrarySync(Thread):
|
||||||
'title': item.attrib.get('title', 'Missing Title'),
|
'title': item.attrib.get('title', 'Missing Title'),
|
||||||
'mediaType': item.attrib.get('type')
|
'mediaType': item.attrib.get('type')
|
||||||
})
|
})
|
||||||
|
self.just_processed[itemId] = now
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.compare:
|
if self.compare:
|
||||||
|
@ -928,6 +938,7 @@ class LibrarySync(Thread):
|
||||||
'title': item.attrib.get('title', 'Missing Title'),
|
'title': item.attrib.get('title', 'Missing Title'),
|
||||||
'mediaType': item.attrib.get('type')
|
'mediaType': item.attrib.get('type')
|
||||||
})
|
})
|
||||||
|
self.just_processed[itemId] = now
|
||||||
else:
|
else:
|
||||||
# Initial or repair sync: get all Plex movies
|
# Initial or repair sync: get all Plex movies
|
||||||
for item in xml:
|
for item in xml:
|
||||||
|
@ -946,6 +957,7 @@ class LibrarySync(Thread):
|
||||||
'title': item.attrib.get('title', 'Missing Title'),
|
'title': item.attrib.get('title', 'Missing Title'),
|
||||||
'mediaType': item.attrib.get('type')
|
'mediaType': item.attrib.get('type')
|
||||||
})
|
})
|
||||||
|
self.just_processed[itemId] = now
|
||||||
|
|
||||||
def GetAndProcessXMLs(self, itemType):
|
def GetAndProcessXMLs(self, itemType):
|
||||||
"""
|
"""
|
||||||
|
@ -1450,6 +1462,8 @@ class LibrarySync(Thread):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
successful = self.process_newitems(item)
|
successful = self.process_newitems(item)
|
||||||
|
if successful:
|
||||||
|
self.just_processed[str(item['ratingKey'])] = now
|
||||||
if successful and settings('FanartTV') == 'true':
|
if successful and settings('FanartTV') == 'true':
|
||||||
plex_type = v.PLEX_TYPE_FROM_WEBSOCKET[item['type']]
|
plex_type = v.PLEX_TYPE_FROM_WEBSOCKET[item['type']]
|
||||||
if plex_type in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW):
|
if plex_type in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW):
|
||||||
|
@ -1534,6 +1548,7 @@ class LibrarySync(Thread):
|
||||||
PMS is messing with the library items, e.g. new or changed. Put in our
|
PMS is messing with the library items, e.g. new or changed. Put in our
|
||||||
"processing queue" for later
|
"processing queue" for later
|
||||||
"""
|
"""
|
||||||
|
now = getUnixTimestamp()
|
||||||
for item in data:
|
for item in data:
|
||||||
if 'tv.plex' in item.get('identifier', ''):
|
if 'tv.plex' in item.get('identifier', ''):
|
||||||
# Ommit Plex DVR messages - the Plex IDs are not corresponding
|
# Ommit Plex DVR messages - the Plex IDs are not corresponding
|
||||||
|
@ -1548,6 +1563,14 @@ class LibrarySync(Thread):
|
||||||
if plex_id == '0':
|
if plex_id == '0':
|
||||||
log.error('Received malformed PMS message: %s' % item)
|
log.error('Received malformed PMS message: %s' % item)
|
||||||
continue
|
continue
|
||||||
|
try:
|
||||||
|
if (now - self.just_processed[plex_id] <
|
||||||
|
self.ignore_just_processed and state != 9):
|
||||||
|
log.debug('We just processed %s: ignoring' % plex_id)
|
||||||
|
continue
|
||||||
|
except KeyError:
|
||||||
|
# Item has NOT just been processed
|
||||||
|
pass
|
||||||
# Have we already added this element?
|
# Have we already added this element?
|
||||||
for existingItem in self.itemsToProcess:
|
for existingItem in self.itemsToProcess:
|
||||||
if existingItem['ratingKey'] == plex_id:
|
if existingItem['ratingKey'] == plex_id:
|
||||||
|
|
|
@ -63,7 +63,7 @@ class MyFormatter(logging.Formatter):
|
||||||
|
|
||||||
# Replace the original format with one customized by logging level
|
# Replace the original format with one customized by logging level
|
||||||
if record.levelno in (logging.DEBUG, logging.ERROR):
|
if record.levelno in (logging.DEBUG, logging.ERROR):
|
||||||
self._fmt = '%(name)s -> %(levelname)s:: %(message)s'
|
self._fmt = '%(name)s -> %(levelname)s: %(message)s'
|
||||||
|
|
||||||
# Call the original formatter class to do the grunt work
|
# Call the original formatter class to do the grunt work
|
||||||
result = logging.Formatter.format(self, record)
|
result = logging.Formatter.format(self, record)
|
||||||
|
|
|
@ -138,7 +138,10 @@ class PlaybackUtils():
|
||||||
plex_lib_UUID,
|
plex_lib_UUID,
|
||||||
mediatype=api.getType(),
|
mediatype=api.getType(),
|
||||||
trailers=trailers)
|
trailers=trailers)
|
||||||
|
try:
|
||||||
get_playlist_details_from_xml(playqueue, xml=xml)
|
get_playlist_details_from_xml(playqueue, xml=xml)
|
||||||
|
except KeyError:
|
||||||
|
return
|
||||||
|
|
||||||
if (not homeScreen and not seektime and sizePlaylist < 2 and
|
if (not homeScreen and not seektime and sizePlaylist < 2 and
|
||||||
window('plex_customplaylist') != "true" and
|
window('plex_customplaylist') != "true" and
|
||||||
|
@ -287,16 +290,19 @@ class PlaybackUtils():
|
||||||
self.currentPosition = 0
|
self.currentPosition = 0
|
||||||
for item in self.xml:
|
for item in self.xml:
|
||||||
api = API(item)
|
api = API(item)
|
||||||
|
successful = True
|
||||||
if api.getType() == v.PLEX_TYPE_CLIP:
|
if api.getType() == v.PLEX_TYPE_CLIP:
|
||||||
self.add_trailer(item)
|
self.add_trailer(item)
|
||||||
else:
|
else:
|
||||||
with Get_Plex_DB() as plex_db:
|
with Get_Plex_DB() as plex_db:
|
||||||
db_item = plex_db.getItem_byId(api.getRatingKey())
|
db_item = plex_db.getItem_byId(api.getRatingKey())
|
||||||
if db_item is not None:
|
if db_item is not None:
|
||||||
if add_item_to_kodi_playlist(self.playqueue,
|
successful = add_item_to_kodi_playlist(
|
||||||
|
self.playqueue,
|
||||||
self.currentPosition,
|
self.currentPosition,
|
||||||
kodi_id=db_item[0],
|
kodi_id=db_item[0],
|
||||||
kodi_type=db_item[4]) is True:
|
kodi_type=db_item[4])
|
||||||
|
if successful is True:
|
||||||
self.currentPosition += 1
|
self.currentPosition += 1
|
||||||
if len(item[0]) > 1:
|
if len(item[0]) > 1:
|
||||||
self.add_part(item,
|
self.add_part(item,
|
||||||
|
@ -306,6 +312,7 @@ class PlaybackUtils():
|
||||||
else:
|
else:
|
||||||
# Item not in Kodi DB
|
# Item not in Kodi DB
|
||||||
self.add_trailer(item)
|
self.add_trailer(item)
|
||||||
|
if successful is True:
|
||||||
self.playqueue.items[self.currentPosition - 1].ID = item.get(
|
self.playqueue.items[self.currentPosition - 1].ID = item.get(
|
||||||
'%sItemID' % self.playqueue.kind)
|
'%sItemID' % self.playqueue.kind)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ from urllib import quote
|
||||||
|
|
||||||
import plexdb_functions as plexdb
|
import plexdb_functions as plexdb
|
||||||
from downloadutils import DownloadUtils as DU
|
from downloadutils import DownloadUtils as DU
|
||||||
from utils import JSONRPC, tryEncode
|
from utils import JSONRPC, tryEncode, tryDecode
|
||||||
from PlexAPI import API
|
from PlexAPI import API
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -36,7 +36,11 @@ class Playlist_Object_Baseclase(object):
|
||||||
answ += "items: %s, " % self.items
|
answ += "items: %s, " % self.items
|
||||||
for key in self.__dict__:
|
for key in self.__dict__:
|
||||||
if key not in ("ID", 'items'):
|
if key not in ("ID", 'items'):
|
||||||
answ += '%s: %s, ' % (key, getattr(self, key))
|
if type(getattr(self, key)) in (str, unicode):
|
||||||
|
answ += '%s: %s, ' % (key, tryEncode(getattr(self, key)))
|
||||||
|
else:
|
||||||
|
# e.g. int
|
||||||
|
answ += '%s: %s, ' % (key, str(getattr(self, key)))
|
||||||
return answ[:-2] + ">"
|
return answ[:-2] + ">"
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
@ -73,14 +77,18 @@ class Playlist_Item(object):
|
||||||
plex_UUID = None # Plex librarySectionUUID
|
plex_UUID = None # Plex librarySectionUUID
|
||||||
kodi_id = None # Kodi unique kodi id (unique only within type!)
|
kodi_id = None # Kodi unique kodi id (unique only within type!)
|
||||||
kodi_type = None # Kodi type: 'movie'
|
kodi_type = None # Kodi type: 'movie'
|
||||||
file = None # Path to the item's file
|
file = None # Path to the item's file. STRING!!
|
||||||
uri = None # Weird Plex uri path involving plex_UUID
|
uri = None # Weird Plex uri path involving plex_UUID. STRING!
|
||||||
guid = None # Weird Plex guid
|
guid = None # Weird Plex guid
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
answ = "<%s: " % (self.__class__.__name__)
|
answ = "<%s: " % (self.__class__.__name__)
|
||||||
for key in self.__dict__:
|
for key in self.__dict__:
|
||||||
answ += '%s: %s, ' % (key, getattr(self, key))
|
if type(getattr(self, key)) in (str, unicode):
|
||||||
|
answ += '%s: %s, ' % (key, tryEncode(getattr(self, key)))
|
||||||
|
else:
|
||||||
|
# e.g. int
|
||||||
|
answ += '%s: %s, ' % (key, str(getattr(self, key)))
|
||||||
return answ[:-2] + ">"
|
return answ[:-2] + ">"
|
||||||
|
|
||||||
|
|
||||||
|
@ -110,6 +118,7 @@ def playlist_item_from_kodi(kodi_item):
|
||||||
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
|
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
|
||||||
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
||||||
(item.plex_UUID, item.plex_id))
|
(item.plex_UUID, item.plex_id))
|
||||||
|
log.debug('Made playlist item from Kodi: %s' % item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,6 +137,10 @@ def playlist_item_from_plex(plex_id):
|
||||||
item.kodi_type = plex_dbitem[4]
|
item.kodi_type = plex_dbitem[4]
|
||||||
except:
|
except:
|
||||||
raise KeyError('Could not find plex_id %s in database' % plex_id)
|
raise KeyError('Could not find plex_id %s in database' % plex_id)
|
||||||
|
item.plex_UUID = plex_id
|
||||||
|
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
||||||
|
(item.plex_UUID, plex_id))
|
||||||
|
log.debug('Made playlist item from plex: %s' % item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
@ -209,15 +222,14 @@ def update_playlist_from_PMS(playlist, playlist_id=None, xml=None):
|
||||||
"""
|
"""
|
||||||
if xml is None:
|
if xml is None:
|
||||||
xml = get_PMS_playlist(playlist, playlist_id)
|
xml = get_PMS_playlist(playlist, playlist_id)
|
||||||
try:
|
|
||||||
xml.attrib['%sVersion' % playlist.kind]
|
|
||||||
except:
|
|
||||||
log.error('Could not process Plex playlist')
|
|
||||||
return
|
|
||||||
# Clear our existing playlist and the associated Kodi playlist
|
# Clear our existing playlist and the associated Kodi playlist
|
||||||
playlist.clear()
|
playlist.clear()
|
||||||
# Set new values
|
# Set new values
|
||||||
|
try:
|
||||||
get_playlist_details_from_xml(playlist, xml)
|
get_playlist_details_from_xml(playlist, xml)
|
||||||
|
except KeyError:
|
||||||
|
log.error('Could not update playlist from PMS')
|
||||||
|
return
|
||||||
for plex_item in xml:
|
for plex_item in xml:
|
||||||
playlist_item = add_to_Kodi_playlist(playlist, plex_item)
|
playlist_item = add_to_Kodi_playlist(playlist, plex_item)
|
||||||
if playlist_item is not None:
|
if playlist_item is not None:
|
||||||
|
@ -231,6 +243,7 @@ def init_Plex_playlist(playlist, plex_id=None, kodi_item=None):
|
||||||
WILL ALSO UPDATE OUR PLAYLISTS
|
WILL ALSO UPDATE OUR PLAYLISTS
|
||||||
"""
|
"""
|
||||||
log.debug('Initializing the playlist %s on the Plex side' % playlist)
|
log.debug('Initializing the playlist %s on the Plex side' % playlist)
|
||||||
|
try:
|
||||||
if plex_id:
|
if plex_id:
|
||||||
item = playlist_item_from_plex(plex_id)
|
item = playlist_item_from_plex(plex_id)
|
||||||
else:
|
else:
|
||||||
|
@ -244,6 +257,9 @@ def init_Plex_playlist(playlist, plex_id=None, kodi_item=None):
|
||||||
action_type="POST",
|
action_type="POST",
|
||||||
parameters=params)
|
parameters=params)
|
||||||
get_playlist_details_from_xml(playlist, xml)
|
get_playlist_details_from_xml(playlist, xml)
|
||||||
|
except KeyError:
|
||||||
|
log.error('Could not init Plex playlist')
|
||||||
|
return
|
||||||
item.ID = xml[-1].attrib['%sItemID' % playlist.kind]
|
item.ID = xml[-1].attrib['%sItemID' % playlist.kind]
|
||||||
playlist.items.append(item)
|
playlist.items.append(item)
|
||||||
log.debug('Initialized the playlist on the Plex side: %s' % playlist)
|
log.debug('Initialized the playlist on the Plex side: %s' % playlist)
|
||||||
|
@ -255,6 +271,8 @@ def add_listitem_to_playlist(playlist, pos, listitem, kodi_id=None,
|
||||||
Adds a listitem to both the Kodi and Plex playlist at position pos [int].
|
Adds a listitem to both the Kodi and Plex playlist at position pos [int].
|
||||||
|
|
||||||
If file is not None, file will overrule kodi_id!
|
If file is not None, file will overrule kodi_id!
|
||||||
|
|
||||||
|
file: str!!
|
||||||
"""
|
"""
|
||||||
log.debug('add_listitem_to_playlist at position %s. Playlist before add: '
|
log.debug('add_listitem_to_playlist at position %s. Playlist before add: '
|
||||||
'%s' % (pos, playlist))
|
'%s' % (pos, playlist))
|
||||||
|
@ -282,6 +300,8 @@ def add_item_to_playlist(playlist, pos, kodi_id=None, kodi_type=None,
|
||||||
plex_id=None, file=None):
|
plex_id=None, file=None):
|
||||||
"""
|
"""
|
||||||
Adds an item to BOTH the Kodi and Plex playlist at position pos [int]
|
Adds an item to BOTH the Kodi and Plex playlist at position pos [int]
|
||||||
|
|
||||||
|
file: str!
|
||||||
"""
|
"""
|
||||||
log.debug('add_item_to_playlist. Playlist before adding: %s' % playlist)
|
log.debug('add_item_to_playlist. Playlist before adding: %s' % playlist)
|
||||||
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
|
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
|
||||||
|
@ -305,7 +325,11 @@ def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None):
|
||||||
log.debug('Adding new item plex_id: %s, kodi_item: %s on the Plex side at '
|
log.debug('Adding new item plex_id: %s, kodi_item: %s on the Plex side at '
|
||||||
'position %s for %s' % (plex_id, kodi_item, pos, playlist))
|
'position %s for %s' % (plex_id, kodi_item, pos, playlist))
|
||||||
if plex_id:
|
if plex_id:
|
||||||
|
try:
|
||||||
item = playlist_item_from_plex(plex_id)
|
item = playlist_item_from_plex(plex_id)
|
||||||
|
except KeyError:
|
||||||
|
log.error('Could not add new item to the PMS playlist')
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
item = playlist_item_from_kodi(kodi_item)
|
item = playlist_item_from_kodi(kodi_item)
|
||||||
url = "{server}/%ss/%s?uri=%s" % (playlist.kind, playlist.ID, item.uri)
|
url = "{server}/%ss/%s?uri=%s" % (playlist.kind, playlist.ID, item.uri)
|
||||||
|
@ -342,6 +366,8 @@ def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
|
||||||
Adds an item to the KODI playlist only. WILL ALSO UPDATE OUR PLAYLISTS
|
Adds an item to the KODI playlist only. WILL ALSO UPDATE OUR PLAYLISTS
|
||||||
|
|
||||||
Returns False if unsuccessful
|
Returns False if unsuccessful
|
||||||
|
|
||||||
|
file: str!
|
||||||
"""
|
"""
|
||||||
log.debug('Adding new item kodi_id: %s, kodi_type: %s, file: %s to Kodi '
|
log.debug('Adding new item kodi_id: %s, kodi_type: %s, file: %s to Kodi '
|
||||||
'only at position %s for %s'
|
'only at position %s for %s'
|
||||||
|
@ -418,11 +444,9 @@ def refresh_playlist_from_PMS(playlist):
|
||||||
"""
|
"""
|
||||||
xml = get_PMS_playlist(playlist)
|
xml = get_PMS_playlist(playlist)
|
||||||
try:
|
try:
|
||||||
xml.attrib['%sVersion' % playlist.kind]
|
|
||||||
except:
|
|
||||||
log.error('Could not download Plex playlist.')
|
|
||||||
return
|
|
||||||
get_playlist_details_from_xml(playlist, xml)
|
get_playlist_details_from_xml(playlist, xml)
|
||||||
|
except KeyError:
|
||||||
|
log.error('Could not refresh playlist from PMS')
|
||||||
|
|
||||||
|
|
||||||
def delete_playlist_item_from_PMS(playlist, pos):
|
def delete_playlist_item_from_PMS(playlist, pos):
|
||||||
|
@ -469,8 +493,9 @@ def get_kodi_playqueues():
|
||||||
try:
|
try:
|
||||||
queues = queues['result']
|
queues = queues['result']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeyError('Could not get Kodi playqueues. JSON Result was: %s'
|
log.error('Could not get Kodi playqueues. JSON Result was: %s'
|
||||||
% queues)
|
% queues)
|
||||||
|
queues = []
|
||||||
return queues
|
return queues
|
||||||
|
|
||||||
|
|
||||||
|
@ -490,7 +515,7 @@ def add_to_Kodi_playlist(playlist, xml_video_element):
|
||||||
if item.kodi_id:
|
if item.kodi_id:
|
||||||
params['item'] = {'%sid' % item.kodi_type: item.kodi_id}
|
params['item'] = {'%sid' % item.kodi_type: item.kodi_id}
|
||||||
else:
|
else:
|
||||||
params['item'] = {'file': tryEncode(item.file)}
|
params['item'] = {'file': item.file}
|
||||||
reply = JSONRPC('Playlist.Add').execute(params)
|
reply = JSONRPC('Playlist.Add').execute(params)
|
||||||
if reply.get('error') is not None:
|
if reply.get('error') is not None:
|
||||||
log.error('Could not add item %s to Kodi playlist. Error: %s'
|
log.error('Could not add item %s to Kodi playlist. Error: %s'
|
||||||
|
@ -506,6 +531,8 @@ def add_listitem_to_Kodi_playlist(playlist, pos, listitem, file,
|
||||||
Adds an xbmc listitem to the Kodi playlist.xml_video_element
|
Adds an xbmc listitem to the Kodi playlist.xml_video_element
|
||||||
|
|
||||||
WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS
|
WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS
|
||||||
|
|
||||||
|
file: string!
|
||||||
"""
|
"""
|
||||||
log.debug('Insert listitem at position %s for Kodi only for %s'
|
log.debug('Insert listitem at position %s for Kodi only for %s'
|
||||||
% (pos, playlist))
|
% (pos, playlist))
|
||||||
|
|
|
@ -7,8 +7,10 @@ from xbmc import sleep, Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO
|
||||||
|
|
||||||
from utils import window, ThreadMethods, ThreadMethodsAdditionalSuspend
|
from utils import window, ThreadMethods, ThreadMethodsAdditionalSuspend
|
||||||
import playlist_func as PL
|
import playlist_func as PL
|
||||||
from PlexFunctions import ConvertPlexToKodiTime
|
from PlexFunctions import ConvertPlexToKodiTime, GetAllPlexChildren
|
||||||
|
from PlexAPI import API
|
||||||
from playbackutils import PlaybackUtils
|
from playbackutils import PlaybackUtils
|
||||||
|
import variables as v
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
log = logging.getLogger("PLEX."+__name__)
|
log = logging.getLogger("PLEX."+__name__)
|
||||||
|
@ -31,6 +33,8 @@ class Playqueue(Thread):
|
||||||
def __init__(self, callback=None):
|
def __init__(self, callback=None):
|
||||||
self.__dict__ = self.__shared_state
|
self.__dict__ = self.__shared_state
|
||||||
if self.playqueues is not None:
|
if self.playqueues is not None:
|
||||||
|
log.debug('Playqueue thread has already been initialized')
|
||||||
|
Thread.__init__(self)
|
||||||
return
|
return
|
||||||
self.mgr = callback
|
self.mgr = callback
|
||||||
|
|
||||||
|
@ -69,6 +73,25 @@ class Playqueue(Thread):
|
||||||
raise ValueError('Wrong playlist type passed in: %s' % typus)
|
raise ValueError('Wrong playlist type passed in: %s' % typus)
|
||||||
return playqueue
|
return playqueue
|
||||||
|
|
||||||
|
def init_playqueue_from_plex_children(self, plex_id):
|
||||||
|
"""
|
||||||
|
Init a new playqueue e.g. from an album. Alexa does this
|
||||||
|
"""
|
||||||
|
xml = GetAllPlexChildren(plex_id)
|
||||||
|
try:
|
||||||
|
xml[0].attrib
|
||||||
|
except (TypeError, IndexError, AttributeError):
|
||||||
|
log.error('Could not download the PMS xml for %s' % plex_id)
|
||||||
|
return
|
||||||
|
playqueue = self.get_playqueue_from_type(
|
||||||
|
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']])
|
||||||
|
playqueue.clear()
|
||||||
|
for i, child in enumerate(xml):
|
||||||
|
api = API(child)
|
||||||
|
PL.add_item_to_playlist(playqueue, i, plex_id=api.getRatingKey())
|
||||||
|
log.debug('Firing up Kodi player')
|
||||||
|
Player().play(playqueue.kodi_pl, None, False, 0)
|
||||||
|
|
||||||
def update_playqueue_from_PMS(self,
|
def update_playqueue_from_PMS(self,
|
||||||
playqueue,
|
playqueue,
|
||||||
playqueue_id=None,
|
playqueue_id=None,
|
||||||
|
@ -85,11 +108,12 @@ class Playqueue(Thread):
|
||||||
'%s, repeat %s' % (playqueue_id, offset, repeat))
|
'%s, repeat %s' % (playqueue_id, offset, repeat))
|
||||||
with lock:
|
with lock:
|
||||||
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
||||||
if xml is None:
|
playqueue.clear()
|
||||||
|
try:
|
||||||
|
PL.get_playlist_details_from_xml(playqueue, xml)
|
||||||
|
except KeyError:
|
||||||
log.error('Could not get playqueue ID %s' % playqueue_id)
|
log.error('Could not get playqueue ID %s' % playqueue_id)
|
||||||
return
|
return
|
||||||
playqueue.clear()
|
|
||||||
PL.get_playlist_details_from_xml(playqueue, xml)
|
|
||||||
PlaybackUtils(xml, playqueue).play_all()
|
PlaybackUtils(xml, playqueue).play_all()
|
||||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||||
window('plex_customplaylist', value="true")
|
window('plex_customplaylist', value="true")
|
||||||
|
|
|
@ -9,6 +9,7 @@ import xbmcgui
|
||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
|
|
||||||
from utils import window, settings, tryEncode, language as lang
|
from utils import window, settings, tryEncode, language as lang
|
||||||
|
import variables as v
|
||||||
|
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
|
|
||||||
|
@ -160,11 +161,11 @@ class PlayUtils():
|
||||||
- video bitrate above specified settings bitrate
|
- video bitrate above specified settings bitrate
|
||||||
if the corresponding file settings are set to 'true'
|
if the corresponding file settings are set to 'true'
|
||||||
"""
|
"""
|
||||||
videoCodec = self.API.getVideoCodec()
|
if self.API.getType() in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_SONG):
|
||||||
log.info("videoCodec: %s" % videoCodec)
|
|
||||||
if self.API.getType() in ('clip', 'track'):
|
|
||||||
log.info('Plex clip or music track, not transcoding')
|
log.info('Plex clip or music track, not transcoding')
|
||||||
return False
|
return False
|
||||||
|
videoCodec = self.API.getVideoCodec()
|
||||||
|
log.info("videoCodec: %s" % videoCodec)
|
||||||
if window('plex_forcetranscode') == 'true':
|
if window('plex_forcetranscode') == 'true':
|
||||||
log.info('User chose to force-transcode')
|
log.info('User chose to force-transcode')
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -57,7 +57,7 @@ def plex_type(xbmc_type):
|
||||||
|
|
||||||
|
|
||||||
def getXMLHeader():
|
def getXMLHeader():
|
||||||
return '<?xml version="1.0" encoding="utf-8" ?>\r\n'
|
return '<?xml version="1.0" encoding="utf-8" ?>\n'
|
||||||
|
|
||||||
|
|
||||||
def getOKMsg():
|
def getOKMsg():
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import logging
|
import logging
|
||||||
import re
|
from re import sub
|
||||||
from SocketServer import ThreadingMixIn
|
from SocketServer import ThreadingMixIn
|
||||||
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
||||||
from urlparse import urlparse, parse_qs
|
from urlparse import urlparse, parse_qs
|
||||||
|
|
||||||
from xbmc import sleep
|
from xbmc import sleep
|
||||||
|
from companion import process_command
|
||||||
|
from utils import window
|
||||||
|
|
||||||
from functions import *
|
from functions import *
|
||||||
|
|
||||||
|
@ -19,7 +21,6 @@ log = logging.getLogger("PLEX."+__name__)
|
||||||
|
|
||||||
class MyHandler(BaseHTTPRequestHandler):
|
class MyHandler(BaseHTTPRequestHandler):
|
||||||
protocol_version = 'HTTP/1.1'
|
protocol_version = 'HTTP/1.1'
|
||||||
regex = re.compile(r'''/playQueues/(\d+)$''')
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
|
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
|
||||||
|
@ -58,7 +59,7 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
'x-plex-version, x-plex-platform-version, x-plex-username, '
|
'x-plex-version, x-plex-platform-version, x-plex-username, '
|
||||||
'x-plex-client-identifier, x-plex-target-client-identifier, '
|
'x-plex-client-identifier, x-plex-target-client-identifier, '
|
||||||
'x-plex-device-name, x-plex-platform, x-plex-product, accept, '
|
'x-plex-device-name, x-plex-platform, x-plex-product, accept, '
|
||||||
'x-plex-device')
|
'x-plex-device, x-plex-device-screen-resolution')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.close()
|
self.wfile.close()
|
||||||
|
|
||||||
|
@ -83,11 +84,10 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
subMgr = self.server.subscriptionManager
|
subMgr = self.server.subscriptionManager
|
||||||
js = self.server.jsonClass
|
js = self.server.jsonClass
|
||||||
settings = self.server.settings
|
settings = self.server.settings
|
||||||
queue = self.server.queue
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
request_path = self.path[1:]
|
request_path = self.path[1:]
|
||||||
request_path = re.sub(r"\?.*", "", request_path)
|
request_path = sub(r"\?.*", "", request_path)
|
||||||
url = urlparse(self.path)
|
url = urlparse(self.path)
|
||||||
paramarrays = parse_qs(url.query)
|
paramarrays = parse_qs(url.query)
|
||||||
params = {}
|
params = {}
|
||||||
|
@ -101,10 +101,10 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
params.get('commandID', False))
|
params.get('commandID', False))
|
||||||
if request_path == "version":
|
if request_path == "version":
|
||||||
self.response(
|
self.response(
|
||||||
"PlexKodiConnect Plex Companion: Running\r\nVersion: %s"
|
"PlexKodiConnect Plex Companion: Running\nVersion: %s"
|
||||||
% settings['version'])
|
% settings['version'])
|
||||||
elif request_path == "verify":
|
elif request_path == "verify":
|
||||||
self.response("XBMC JSON connection test:\r\n" +
|
self.response("XBMC JSON connection test:\n" +
|
||||||
js.jsonrpc("ping"))
|
js.jsonrpc("ping"))
|
||||||
elif "resources" == request_path:
|
elif "resources" == request_path:
|
||||||
resp = ('%s'
|
resp = ('%s'
|
||||||
|
@ -145,7 +145,7 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
sleep(950)
|
sleep(950)
|
||||||
commandID = params.get('commandID', 0)
|
commandID = params.get('commandID', 0)
|
||||||
self.response(
|
self.response(
|
||||||
re.sub(r"INSERTCOMMANDID",
|
sub(r"INSERTCOMMANDID",
|
||||||
str(commandID),
|
str(commandID),
|
||||||
subMgr.msg(js.getPlayers())),
|
subMgr.msg(js.getPlayers())),
|
||||||
{
|
{
|
||||||
|
@ -160,121 +160,11 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
uuid = self.headers.get('X-Plex-Client-Identifier', False) \
|
uuid = self.headers.get('X-Plex-Client-Identifier', False) \
|
||||||
or self.client_address[0]
|
or self.client_address[0]
|
||||||
subMgr.removeSubscriber(uuid)
|
subMgr.removeSubscriber(uuid)
|
||||||
elif request_path == "player/playback/setParameters":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
if 'volume' in params:
|
|
||||||
volume = int(params['volume'])
|
|
||||||
log.debug("adjusting the volume to %s%%" % volume)
|
|
||||||
js.jsonrpc("Application.SetVolume",
|
|
||||||
{"volume": volume})
|
|
||||||
elif "/playMedia" in request_path:
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
offset = params.get('viewOffset', params.get('offset', "0"))
|
|
||||||
protocol = params.get('protocol', "http")
|
|
||||||
address = params.get('address', self.client_address[0])
|
|
||||||
server = self.getServerByHost(address)
|
|
||||||
port = params.get('port', server.get('port', '32400'))
|
|
||||||
try:
|
|
||||||
containerKey = urlparse(params.get('containerKey')).path
|
|
||||||
except:
|
|
||||||
containerKey = ''
|
|
||||||
try:
|
|
||||||
playQueueID = self.regex.findall(containerKey)[0]
|
|
||||||
except IndexError:
|
|
||||||
playQueueID = ''
|
|
||||||
# We need to tell service.py
|
|
||||||
queue.put({
|
|
||||||
'action': 'playlist',
|
|
||||||
'data': params
|
|
||||||
})
|
|
||||||
subMgr.lastkey = params['key']
|
|
||||||
subMgr.containerKey = containerKey
|
|
||||||
subMgr.playQueueID = playQueueID
|
|
||||||
subMgr.server = server.get('server', 'localhost')
|
|
||||||
subMgr.port = port
|
|
||||||
subMgr.protocol = protocol
|
|
||||||
subMgr.notify()
|
|
||||||
elif request_path == "player/playback/play":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
for playerid in js.getPlayerIds():
|
|
||||||
js.jsonrpc("Player.PlayPause",
|
|
||||||
{"playerid": playerid, "play": True})
|
|
||||||
subMgr.notify()
|
|
||||||
elif request_path == "player/playback/pause":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
for playerid in js.getPlayerIds():
|
|
||||||
js.jsonrpc("Player.PlayPause",
|
|
||||||
{"playerid": playerid, "play": False})
|
|
||||||
subMgr.notify()
|
|
||||||
elif request_path == "player/playback/stop":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
for playerid in js.getPlayerIds():
|
|
||||||
js.jsonrpc("Player.Stop", {"playerid": playerid})
|
|
||||||
subMgr.notify()
|
|
||||||
elif request_path == "player/playback/seekTo":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
for playerid in js.getPlayerIds():
|
|
||||||
js.jsonrpc("Player.Seek",
|
|
||||||
{"playerid": playerid,
|
|
||||||
"value": millisToTime(
|
|
||||||
params.get('offset', 0))})
|
|
||||||
subMgr.notify()
|
|
||||||
elif request_path == "player/playback/stepForward":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
for playerid in js.getPlayerIds():
|
|
||||||
js.jsonrpc("Player.Seek",
|
|
||||||
{"playerid": playerid,
|
|
||||||
"value": "smallforward"})
|
|
||||||
subMgr.notify()
|
|
||||||
elif request_path == "player/playback/stepBack":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
for playerid in js.getPlayerIds():
|
|
||||||
js.jsonrpc("Player.Seek",
|
|
||||||
{"playerid": playerid,
|
|
||||||
"value": "smallbackward"})
|
|
||||||
subMgr.notify()
|
|
||||||
elif request_path == "player/playback/skipNext":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
for playerid in js.getPlayerIds():
|
|
||||||
js.jsonrpc("Player.GoTo",
|
|
||||||
{"playerid": playerid,
|
|
||||||
"to": "next"})
|
|
||||||
subMgr.notify()
|
|
||||||
elif request_path == "player/playback/skipPrevious":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
for playerid in js.getPlayerIds():
|
|
||||||
js.jsonrpc("Player.GoTo",
|
|
||||||
{"playerid": playerid,
|
|
||||||
"to": "previous"})
|
|
||||||
subMgr.notify()
|
|
||||||
elif request_path == "player/playback/skipTo":
|
|
||||||
js.skipTo(params.get('key').rsplit('/', 1)[1],
|
|
||||||
params.get('type'))
|
|
||||||
subMgr.notify()
|
|
||||||
elif request_path == "player/navigation/moveUp":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
js.jsonrpc("Input.Up")
|
|
||||||
elif request_path == "player/navigation/moveDown":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
js.jsonrpc("Input.Down")
|
|
||||||
elif request_path == "player/navigation/moveLeft":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
js.jsonrpc("Input.Left")
|
|
||||||
elif request_path == "player/navigation/moveRight":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
js.jsonrpc("Input.Right")
|
|
||||||
elif request_path == "player/navigation/select":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
js.jsonrpc("Input.Select")
|
|
||||||
elif request_path == "player/navigation/home":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
js.jsonrpc("Input.Home")
|
|
||||||
elif request_path == "player/navigation/back":
|
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
|
||||||
js.jsonrpc("Input.Back")
|
|
||||||
else:
|
else:
|
||||||
log.error('Unknown request path: %s' % request_path)
|
# Throw it to companion.py
|
||||||
|
process_command(request_path, params, self.server.queue)
|
||||||
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
|
subMgr.notify()
|
||||||
except:
|
except:
|
||||||
log.error('Error encountered. Traceback:')
|
log.error('Error encountered. Traceback:')
|
||||||
import traceback
|
import traceback
|
||||||
|
|
|
@ -57,23 +57,22 @@ class plexgdm:
|
||||||
self._discovery_is_running = False
|
self._discovery_is_running = False
|
||||||
self._registration_is_running = False
|
self._registration_is_running = False
|
||||||
|
|
||||||
self.discovery_complete = False
|
|
||||||
self.client_registered = False
|
self.client_registered = False
|
||||||
self.download = downloadutils.DownloadUtils().downloadUrl
|
self.download = downloadutils.DownloadUtils().downloadUrl
|
||||||
|
|
||||||
def clientDetails(self, options):
|
def clientDetails(self, options):
|
||||||
self.client_data = (
|
self.client_data = (
|
||||||
"Content-Type: plex/media-player\r\n"
|
"Content-Type: plex/media-player\n"
|
||||||
"Resource-Identifier: %s\r\n"
|
"Resource-Identifier: %s\n"
|
||||||
"Name: %s\r\n"
|
"Name: %s\n"
|
||||||
"Port: %s\r\n"
|
"Port: %s\n"
|
||||||
"Product: %s\r\n"
|
"Product: %s\n"
|
||||||
"Version: %s\r\n"
|
"Version: %s\n"
|
||||||
"Protocol: plex\r\n"
|
"Protocol: plex\n"
|
||||||
"Protocol-Version: 1\r\n"
|
"Protocol-Version: 1\n"
|
||||||
"Protocol-Capabilities: timeline,playback,navigation,"
|
"Protocol-Capabilities: timeline,playback,navigation,"
|
||||||
"playqueues\r\n"
|
"playqueues\n"
|
||||||
"Device-Class: HTPC"
|
"Device-Class: HTPC\n"
|
||||||
) % (
|
) % (
|
||||||
options['uuid'],
|
options['uuid'],
|
||||||
options['client_name'],
|
options['client_name'],
|
||||||
|
@ -86,10 +85,25 @@ class plexgdm:
|
||||||
def getClientDetails(self):
|
def getClientDetails(self):
|
||||||
return self.client_data
|
return self.client_data
|
||||||
|
|
||||||
|
def register_as_client(self):
|
||||||
|
"""
|
||||||
|
Registers PKC's Plex Companion to the PMS
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
log.debug("Sending registration data: HELLO %s\n%s"
|
||||||
|
% (self.client_header, self.client_data))
|
||||||
|
self.update_sock.sendto("HELLO %s\n%s"
|
||||||
|
% (self.client_header, self.client_data),
|
||||||
|
self.client_register_group)
|
||||||
|
log.debug('(Re-)registering PKC Plex Companion successful')
|
||||||
|
except:
|
||||||
|
log.error("Unable to send registration message")
|
||||||
|
|
||||||
def client_update(self):
|
def client_update(self):
|
||||||
update_sock = socket.socket(socket.AF_INET,
|
self.update_sock = socket.socket(socket.AF_INET,
|
||||||
socket.SOCK_DGRAM,
|
socket.SOCK_DGRAM,
|
||||||
socket.IPPROTO_UDP)
|
socket.IPPROTO_UDP)
|
||||||
|
update_sock = self.update_sock
|
||||||
|
|
||||||
# Set socket reuse, may not work on all OSs.
|
# Set socket reuse, may not work on all OSs.
|
||||||
try:
|
try:
|
||||||
|
@ -129,16 +143,9 @@ class plexgdm:
|
||||||
self._multicast_address) +
|
self._multicast_address) +
|
||||||
socket.inet_aton('0.0.0.0'))
|
socket.inet_aton('0.0.0.0'))
|
||||||
update_sock.setblocking(0)
|
update_sock.setblocking(0)
|
||||||
log.debug("Sending registration data: HELLO %s\r\n%s"
|
|
||||||
% (self.client_header, self.client_data))
|
|
||||||
|
|
||||||
# Send initial client registration
|
# Send initial client registration
|
||||||
try:
|
self.register_as_client()
|
||||||
update_sock.sendto("HELLO %s\r\n%s"
|
|
||||||
% (self.client_header, self.client_data),
|
|
||||||
self.client_register_group)
|
|
||||||
except:
|
|
||||||
log.error("Unable to send registration message")
|
|
||||||
|
|
||||||
# Now, listen format client discovery reguests and respond.
|
# Now, listen format client discovery reguests and respond.
|
||||||
while self._registration_is_running:
|
while self._registration_is_running:
|
||||||
|
@ -153,7 +160,7 @@ class plexgdm:
|
||||||
log.debug("Detected client discovery request from %s. "
|
log.debug("Detected client discovery request from %s. "
|
||||||
" Replying" % str(addr))
|
" Replying" % str(addr))
|
||||||
try:
|
try:
|
||||||
update_sock.sendto("HTTP/1.0 200 OK\r\n%s"
|
update_sock.sendto("HTTP/1.0 200 OK\n%s"
|
||||||
% self.client_data,
|
% self.client_data,
|
||||||
addr)
|
addr)
|
||||||
except:
|
except:
|
||||||
|
@ -165,10 +172,10 @@ class plexgdm:
|
||||||
log.info("Client Update loop stopped")
|
log.info("Client Update loop stopped")
|
||||||
# When we are finished, then send a final goodbye message to
|
# When we are finished, then send a final goodbye message to
|
||||||
# deregister cleanly.
|
# deregister cleanly.
|
||||||
log.debug("Sending registration data: BYE %s\r\n%s"
|
log.debug("Sending registration data: BYE %s\n%s"
|
||||||
% (self.client_header, self.client_data))
|
% (self.client_header, self.client_data))
|
||||||
try:
|
try:
|
||||||
update_sock.sendto("BYE %s\r\n%s"
|
update_sock.sendto("BYE %s\n%s"
|
||||||
% (self.client_header, self.client_data),
|
% (self.client_header, self.client_data),
|
||||||
self.client_register_group)
|
self.client_register_group)
|
||||||
except:
|
except:
|
||||||
|
@ -176,12 +183,12 @@ class plexgdm:
|
||||||
self.client_registered = False
|
self.client_registered = False
|
||||||
|
|
||||||
def check_client_registration(self):
|
def check_client_registration(self):
|
||||||
|
if not self.client_registered:
|
||||||
if self.client_registered and self.discovery_complete:
|
log.debug('Client has not been marked as registered')
|
||||||
|
return False
|
||||||
if not self.server_list:
|
if not self.server_list:
|
||||||
log.info("Server list is empty. Unable to check")
|
log.info("Server list is empty. Unable to check")
|
||||||
return False
|
return False
|
||||||
try:
|
|
||||||
for server in self.server_list:
|
for server in self.server_list:
|
||||||
if server['uuid'] == window('plex_machineIdentifier'):
|
if server['uuid'] == window('plex_machineIdentifier'):
|
||||||
media_server = server['server']
|
media_server = server['server']
|
||||||
|
@ -194,23 +201,23 @@ class plexgdm:
|
||||||
|
|
||||||
log.debug("Checking server [%s] on port [%s]"
|
log.debug("Checking server [%s] on port [%s]"
|
||||||
% (media_server, media_port))
|
% (media_server, media_port))
|
||||||
client_result = self.download(
|
xml = self.download(
|
||||||
'%s://%s:%s/clients' % (scheme, media_server, media_port))
|
'%s://%s:%s/clients' % (scheme, media_server, media_port))
|
||||||
|
try:
|
||||||
|
xml[0].attrib
|
||||||
|
except (TypeError, IndexError, AttributeError):
|
||||||
|
log.error('Could not download clients for %s' % media_server)
|
||||||
|
return False
|
||||||
registered = False
|
registered = False
|
||||||
for client in client_result:
|
for client in xml:
|
||||||
if (client.attrib.get('machineIdentifier') ==
|
if (client.attrib.get('machineIdentifier') ==
|
||||||
self.client_id):
|
self.client_id):
|
||||||
registered = True
|
registered = True
|
||||||
if registered:
|
if registered:
|
||||||
log.debug("Client registration successful. "
|
|
||||||
"Client data is: %s" % client_result)
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
log.info("Client registration not found. "
|
log.info("Client registration not found. "
|
||||||
"Client data is: %s" % client_result)
|
"Client data is: %s" % xml)
|
||||||
except:
|
|
||||||
log.error("Unable to check status")
|
|
||||||
pass
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def getServerList(self):
|
def getServerList(self):
|
||||||
|
|
|
@ -26,7 +26,7 @@ def getSettings():
|
||||||
options['gdm_debug'] = settings('companionGDMDebugging')
|
options['gdm_debug'] = settings('companionGDMDebugging')
|
||||||
options['gdm_debug'] = True if options['gdm_debug'] == 'true' else False
|
options['gdm_debug'] = True if options['gdm_debug'] == 'true' else False
|
||||||
|
|
||||||
options['client_name'] = settings('deviceName')
|
options['client_name'] = v.DEVICENAME
|
||||||
|
|
||||||
# XBMC web server options
|
# XBMC web server options
|
||||||
options['webserver_enabled'] = (getGUI('webserver') == "true")
|
options['webserver_enabled'] = (getGUI('webserver') == "true")
|
||||||
|
|
|
@ -71,7 +71,7 @@ class SubscriptionManager:
|
||||||
msg += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio())
|
msg += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio())
|
||||||
msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo())
|
msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo())
|
||||||
msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
|
msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
|
||||||
msg += "\r\n</MediaContainer>"
|
msg += "\n</MediaContainer>"
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def getTimelineXML(self, playerid, ptype):
|
def getTimelineXML(self, playerid, ptype):
|
||||||
|
@ -84,7 +84,7 @@ class SubscriptionManager:
|
||||||
else:
|
else:
|
||||||
state = "stopped"
|
state = "stopped"
|
||||||
time = 0
|
time = 0
|
||||||
ret = "\r\n"+' <Timeline state="%s" time="%s" type="%s"' % (state, time, ptype)
|
ret = "\n"+' <Timeline state="%s" time="%s" type="%s"' % (state, time, ptype)
|
||||||
if playerid is None:
|
if playerid is None:
|
||||||
ret += ' seekRange="0-0"'
|
ret += ' seekRange="0-0"'
|
||||||
ret += ' />'
|
ret += ' />'
|
||||||
|
@ -312,7 +312,7 @@ class Subscriber:
|
||||||
else:
|
else:
|
||||||
self.navlocationsent = True
|
self.navlocationsent = True
|
||||||
msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg)
|
msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg)
|
||||||
log.debug("sending xml to subscriber %s: %s" % (self.tostr(), msg))
|
log.debug("sending xml to subscriber %s:\n%s" % (self.tostr(), msg))
|
||||||
url = self.protocol + '://' + self.host + ':' + self.port \
|
url = self.protocol + '://' + self.host + ':' + self.port \
|
||||||
+ "/:/timeline"
|
+ "/:/timeline"
|
||||||
t = threading.Thread(target=self.threadedSend, args=(url, msg))
|
t = threading.Thread(target=self.threadedSend, args=(url, msg))
|
||||||
|
|
|
@ -133,8 +133,7 @@ def dialog(typus, *args, **kwargs):
|
||||||
'{ipaddress}': xbmcgui.INPUT_IPADDRESS,
|
'{ipaddress}': xbmcgui.INPUT_IPADDRESS,
|
||||||
'{password}': xbmcgui.INPUT_PASSWORD
|
'{password}': xbmcgui.INPUT_PASSWORD
|
||||||
}
|
}
|
||||||
for key, value in types.iteritems():
|
kwargs['type'] = types[kwargs['type']]
|
||||||
kwargs['type'] = kwargs['type'].replace(key, value)
|
|
||||||
if "heading" in kwargs:
|
if "heading" in kwargs:
|
||||||
kwargs['heading'] = kwargs['heading'].replace("{plex}",
|
kwargs['heading'] = kwargs['heading'].replace("{plex}",
|
||||||
language(29999))
|
language(29999))
|
||||||
|
|
|
@ -46,13 +46,18 @@ elif xbmc.getCondVisibility('system.platform.android'):
|
||||||
else:
|
else:
|
||||||
PLATFORM = "Unknown"
|
PLATFORM = "Unknown"
|
||||||
|
|
||||||
if _ADDON.getSetting('deviceNameOpt') == "false":
|
|
||||||
# Use Kodi's deviceName
|
|
||||||
DEVICENAME = tryDecode(xbmc.getInfoLabel('System.FriendlyName'))
|
|
||||||
else:
|
|
||||||
DEVICENAME = tryDecode(_ADDON.getSetting('deviceName'))
|
DEVICENAME = tryDecode(_ADDON.getSetting('deviceName'))
|
||||||
DEVICENAME = DEVICENAME.replace("\"", "_")
|
DEVICENAME = DEVICENAME.replace(":", "")
|
||||||
DEVICENAME = DEVICENAME.replace("/", "_")
|
DEVICENAME = DEVICENAME.replace("/", "-")
|
||||||
|
DEVICENAME = DEVICENAME.replace("\\", "-")
|
||||||
|
DEVICENAME = DEVICENAME.replace("<", "")
|
||||||
|
DEVICENAME = DEVICENAME.replace(">", "")
|
||||||
|
DEVICENAME = DEVICENAME.replace("*", "")
|
||||||
|
DEVICENAME = DEVICENAME.replace("?", "")
|
||||||
|
DEVICENAME = DEVICENAME.replace('|', "")
|
||||||
|
DEVICENAME = DEVICENAME.replace('(', "")
|
||||||
|
DEVICENAME = DEVICENAME.replace(')', "")
|
||||||
|
DEVICENAME = DEVICENAME.strip()
|
||||||
|
|
||||||
# Database paths
|
# Database paths
|
||||||
_DB_VIDEO_VERSION = {
|
_DB_VIDEO_VERSION = {
|
||||||
|
@ -248,3 +253,16 @@ KODI_SUPPORTED_IMAGES = (
|
||||||
'.pcx',
|
'.pcx',
|
||||||
'.tga'
|
'.tga'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Translation table from Alexa websocket commands to Plex Companion
|
||||||
|
ALEXA_TO_COMPANION = {
|
||||||
|
'queryKey': 'key',
|
||||||
|
'queryOffset': 'offset',
|
||||||
|
'queryMachineIdentifier': 'machineIdentifier',
|
||||||
|
'queryProtocol': 'protocol',
|
||||||
|
'queryAddress': 'address',
|
||||||
|
'queryPort': 'port',
|
||||||
|
'queryContainerKey': 'containerKey',
|
||||||
|
'queryToken': 'token',
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import logging
|
import logging
|
||||||
import websocket
|
import websocket
|
||||||
from json import loads
|
from json import loads
|
||||||
|
import xml.etree.ElementTree as etree
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from Queue import Queue
|
from Queue import Queue
|
||||||
from ssl import CERT_NONE
|
from ssl import CERT_NONE
|
||||||
|
@ -12,6 +13,7 @@ from xbmc import sleep
|
||||||
|
|
||||||
from utils import window, settings, ThreadMethodsAdditionalSuspend, \
|
from utils import window, settings, ThreadMethodsAdditionalSuspend, \
|
||||||
ThreadMethods
|
ThreadMethods
|
||||||
|
from companion import process_command
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -29,10 +31,151 @@ class WebSocket(Thread):
|
||||||
if callback is not None:
|
if callback is not None:
|
||||||
self.mgr = callback
|
self.mgr = callback
|
||||||
self.ws = None
|
self.ws = None
|
||||||
# Communication with librarysync
|
|
||||||
self.queue = Queue()
|
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
|
|
||||||
|
def process(self, opcode, message):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def receive(self, ws):
|
||||||
|
# Not connected yet
|
||||||
|
if ws is None:
|
||||||
|
raise websocket.WebSocketConnectionClosedException
|
||||||
|
|
||||||
|
frame = ws.recv_frame()
|
||||||
|
|
||||||
|
if not frame:
|
||||||
|
raise websocket.WebSocketException("Not a valid frame %s" % frame)
|
||||||
|
elif frame.opcode in self.opcode_data:
|
||||||
|
return frame.opcode, frame.data
|
||||||
|
elif frame.opcode == websocket.ABNF.OPCODE_CLOSE:
|
||||||
|
ws.send_close()
|
||||||
|
return frame.opcode, None
|
||||||
|
elif frame.opcode == websocket.ABNF.OPCODE_PING:
|
||||||
|
ws.pong("Hi!")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def getUri(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
log.info("----===## Starting %s ##===----" % self.__class__.__name__)
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
handshake_counter = 0
|
||||||
|
threadStopped = self.threadStopped
|
||||||
|
threadSuspended = self.threadSuspended
|
||||||
|
while not threadStopped():
|
||||||
|
# In the event the server goes offline
|
||||||
|
while threadSuspended():
|
||||||
|
# Set in service.py
|
||||||
|
if self.ws is not None:
|
||||||
|
try:
|
||||||
|
self.ws.shutdown()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.ws = None
|
||||||
|
if threadStopped():
|
||||||
|
# Abort was requested while waiting. We should exit
|
||||||
|
log.info("##===---- %s Stopped ----===##"
|
||||||
|
% self.__class__.__name__)
|
||||||
|
return
|
||||||
|
sleep(1000)
|
||||||
|
try:
|
||||||
|
self.process(*self.receive(self.ws))
|
||||||
|
except websocket.WebSocketTimeoutException:
|
||||||
|
# No worries if read timed out
|
||||||
|
pass
|
||||||
|
except websocket.WebSocketConnectionClosedException:
|
||||||
|
log.info("Connection closed, (re)connecting")
|
||||||
|
uri, sslopt = self.getUri()
|
||||||
|
try:
|
||||||
|
# Low timeout - let's us shut this thread down!
|
||||||
|
self.ws = websocket.create_connection(
|
||||||
|
uri,
|
||||||
|
timeout=1,
|
||||||
|
sslopt=sslopt,
|
||||||
|
enable_multithread=True)
|
||||||
|
except IOError:
|
||||||
|
# Server is probably offline
|
||||||
|
log.info("Error connecting")
|
||||||
|
self.ws = None
|
||||||
|
counter += 1
|
||||||
|
if counter > 3:
|
||||||
|
counter = 0
|
||||||
|
self.IOError_response()
|
||||||
|
sleep(1000)
|
||||||
|
except websocket.WebSocketTimeoutException:
|
||||||
|
log.info("timeout while connecting, trying again")
|
||||||
|
self.ws = None
|
||||||
|
sleep(1000)
|
||||||
|
except websocket.WebSocketException as e:
|
||||||
|
log.info('WebSocketException: %s' % e)
|
||||||
|
if 'Handshake Status 401' in e.args:
|
||||||
|
handshake_counter += 1
|
||||||
|
if handshake_counter >= 5:
|
||||||
|
log.info('Error in handshake detected. Stopping '
|
||||||
|
'%s now' % self.__class__.__name__)
|
||||||
|
break
|
||||||
|
self.ws = None
|
||||||
|
sleep(1000)
|
||||||
|
except Exception as e:
|
||||||
|
log.error("Unknown exception encountered in connecting: %s"
|
||||||
|
% e)
|
||||||
|
import traceback
|
||||||
|
log.error("Traceback:\n%s" % traceback.format_exc())
|
||||||
|
self.ws = None
|
||||||
|
sleep(1000)
|
||||||
|
else:
|
||||||
|
counter = 0
|
||||||
|
handshake_counter = 0
|
||||||
|
except Exception as e:
|
||||||
|
log.error("Unknown exception encountered: %s" % e)
|
||||||
|
import traceback
|
||||||
|
log.error("Traceback:\n%s" % traceback.format_exc())
|
||||||
|
try:
|
||||||
|
self.ws.shutdown()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.ws = None
|
||||||
|
log.info("##===---- %s Stopped ----===##" % self.__class__.__name__)
|
||||||
|
|
||||||
|
def stopThread(self):
|
||||||
|
"""
|
||||||
|
Overwrite this method from ThreadMethods to close websockets
|
||||||
|
"""
|
||||||
|
log.info("Stopping %s thread." % self.__class__.__name__)
|
||||||
|
self._threadStopped = True
|
||||||
|
try:
|
||||||
|
self.ws.shutdown()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PMS_Websocket(WebSocket):
|
||||||
|
"""
|
||||||
|
Websocket connection with the PMS for Plex Companion
|
||||||
|
"""
|
||||||
|
# Communication with librarysync
|
||||||
|
queue = Queue()
|
||||||
|
|
||||||
|
def getUri(self):
|
||||||
|
server = window('pms_server')
|
||||||
|
# Need to use plex.tv token, if any. NOT user token
|
||||||
|
token = window('plex_token')
|
||||||
|
# Get the appropriate prefix for the websocket
|
||||||
|
if server.startswith('https'):
|
||||||
|
server = "wss%s" % server[5:]
|
||||||
|
else:
|
||||||
|
server = "ws%s" % server[4:]
|
||||||
|
uri = "%s/:/websockets/notifications" % server
|
||||||
|
if token:
|
||||||
|
uri += '?X-Plex-Token=%s' % token
|
||||||
|
sslopt = {}
|
||||||
|
if settings('sslverify') == "false":
|
||||||
|
sslopt["cert_reqs"] = CERT_NONE
|
||||||
|
log.debug("Uri: %s, sslopt: %s" % (uri, sslopt))
|
||||||
|
return uri, sslopt
|
||||||
|
|
||||||
def process(self, opcode, message):
|
def process(self, opcode, message):
|
||||||
if opcode not in self.opcode_data:
|
if opcode not in self.opcode_data:
|
||||||
return False
|
return False
|
||||||
|
@ -62,131 +205,58 @@ class WebSocket(Thread):
|
||||||
self.queue.put(message)
|
self.queue.put(message)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def receive(self, ws):
|
def IOError_response(self):
|
||||||
# Not connected yet
|
|
||||||
if ws is None:
|
|
||||||
raise websocket.WebSocketConnectionClosedException
|
|
||||||
|
|
||||||
frame = ws.recv_frame()
|
|
||||||
|
|
||||||
if not frame:
|
|
||||||
raise websocket.WebSocketException("Not a valid frame %s" % frame)
|
|
||||||
elif frame.opcode in self.opcode_data:
|
|
||||||
return frame.opcode, frame.data
|
|
||||||
elif frame.opcode == websocket.ABNF.OPCODE_CLOSE:
|
|
||||||
ws.send_close()
|
|
||||||
return frame.opcode, None
|
|
||||||
elif frame.opcode == websocket.ABNF.OPCODE_PING:
|
|
||||||
ws.pong("Hi!")
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
def getUri(self):
|
|
||||||
server = window('pms_server')
|
|
||||||
# Need to use plex.tv token, if any. NOT user token
|
|
||||||
token = window('plex_token')
|
|
||||||
# Get the appropriate prefix for the websocket
|
|
||||||
if server.startswith('https'):
|
|
||||||
server = "wss%s" % server[5:]
|
|
||||||
else:
|
|
||||||
server = "ws%s" % server[4:]
|
|
||||||
uri = "%s/:/websockets/notifications" % server
|
|
||||||
if token:
|
|
||||||
uri += '?X-Plex-Token=%s' % token
|
|
||||||
sslopt = {}
|
|
||||||
if settings('sslverify') == "false":
|
|
||||||
sslopt["cert_reqs"] = CERT_NONE
|
|
||||||
log.debug("Uri: %s, sslopt: %s" % (uri, sslopt))
|
|
||||||
return uri, sslopt
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
log.info("----===## Starting WebSocketClient ##===----")
|
|
||||||
|
|
||||||
counter = 0
|
|
||||||
handshake_counter = 0
|
|
||||||
threadStopped = self.threadStopped
|
|
||||||
threadSuspended = self.threadSuspended
|
|
||||||
while not threadStopped():
|
|
||||||
# In the event the server goes offline
|
|
||||||
while threadSuspended():
|
|
||||||
# Set in service.py
|
|
||||||
if self.ws is not None:
|
|
||||||
try:
|
|
||||||
self.ws.shutdown()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
self.ws = None
|
|
||||||
if threadStopped():
|
|
||||||
# Abort was requested while waiting. We should exit
|
|
||||||
log.info("##===---- WebSocketClient Stopped ----===##")
|
|
||||||
return
|
|
||||||
sleep(1000)
|
|
||||||
try:
|
|
||||||
self.process(*self.receive(self.ws))
|
|
||||||
except websocket.WebSocketTimeoutException:
|
|
||||||
# No worries if read timed out
|
|
||||||
pass
|
|
||||||
except websocket.WebSocketConnectionClosedException:
|
|
||||||
log.info("Connection closed, (re)connecting")
|
|
||||||
uri, sslopt = self.getUri()
|
|
||||||
try:
|
|
||||||
# Low timeout - let's us shut this thread down!
|
|
||||||
self.ws = websocket.create_connection(
|
|
||||||
uri,
|
|
||||||
timeout=1,
|
|
||||||
sslopt=sslopt,
|
|
||||||
enable_multithread=True)
|
|
||||||
except IOError:
|
|
||||||
# Server is probably offline
|
|
||||||
log.info("Error connecting")
|
|
||||||
self.ws = None
|
|
||||||
counter += 1
|
|
||||||
if counter > 3:
|
|
||||||
log.warn("Repeatedly could not connect to PMS, "
|
log.warn("Repeatedly could not connect to PMS, "
|
||||||
"declaring the connection dead")
|
"declaring the connection dead")
|
||||||
window('plex_online', value='false')
|
window('plex_online', value='false')
|
||||||
counter = 0
|
|
||||||
sleep(1000)
|
|
||||||
except websocket.WebSocketTimeoutException:
|
class Alexa_Websocket(WebSocket):
|
||||||
log.info("timeout while connecting, trying again")
|
"""
|
||||||
self.ws = None
|
Websocket connection to talk to Amazon Alexa
|
||||||
sleep(1000)
|
"""
|
||||||
except websocket.WebSocketException as e:
|
def getUri(self):
|
||||||
log.info('WebSocketException: %s' % e)
|
self.plex_client_Id = window('plex_client_Id')
|
||||||
if 'Handshake Status 401' in e.args:
|
uri = ('wss://pubsub.plex.tv/sub/websockets/%s/%s?X-Plex-Token=%s'
|
||||||
handshake_counter += 1
|
% (window('currUserId'),
|
||||||
if handshake_counter >= 5:
|
self.plex_client_Id,
|
||||||
log.info('Error in handshake detected. Stopping '
|
window('plex_token')))
|
||||||
'WebSocketClient now')
|
sslopt = {}
|
||||||
break
|
log.debug("Uri: %s, sslopt: %s" % (uri, sslopt))
|
||||||
self.ws = None
|
return uri, sslopt
|
||||||
sleep(1000)
|
|
||||||
except Exception as e:
|
def process(self, opcode, message):
|
||||||
log.error("Unknown exception encountered in connecting: %s"
|
if opcode not in self.opcode_data:
|
||||||
% e)
|
return False
|
||||||
import traceback
|
log.debug('Received the following message from Alexa:')
|
||||||
log.error("Traceback:\n%s" % traceback.format_exc())
|
log.debug(message)
|
||||||
self.ws = None
|
try:
|
||||||
sleep(1000)
|
message = etree.fromstring(message)
|
||||||
|
except Exception as ex:
|
||||||
|
log.error('Error decoding message from Alexa: %s' % ex)
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
if message.attrib['command'] == 'processRemoteControlCommand':
|
||||||
|
message = message[0]
|
||||||
else:
|
else:
|
||||||
counter = 0
|
log.error('Unknown Alexa message received')
|
||||||
handshake_counter = 0
|
return False
|
||||||
except Exception as e:
|
|
||||||
log.error("Unknown exception encountered: %s" % e)
|
|
||||||
try:
|
|
||||||
self.ws.shutdown()
|
|
||||||
except:
|
except:
|
||||||
pass
|
log.error('Could not parse Alexa message')
|
||||||
self.ws = None
|
return False
|
||||||
|
process_command(message.attrib['path'][1:],
|
||||||
|
message.attrib,
|
||||||
|
queue=self.mgr.plexCompanion.queue)
|
||||||
|
return True
|
||||||
|
|
||||||
log.info("##===---- WebSocketClient Stopped ----===##")
|
def IOError_response(self):
|
||||||
|
|
||||||
def stopThread(self):
|
|
||||||
"""
|
|
||||||
Overwrite this method from ThreadMethods to close websockets
|
|
||||||
"""
|
|
||||||
log.info("Stopping websocket client thread.")
|
|
||||||
self._threadStopped = True
|
|
||||||
try:
|
|
||||||
self.ws.shutdown()
|
|
||||||
except:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def threadSuspended(self):
|
||||||
|
"""
|
||||||
|
Overwrite to ignore library sync stuff and allow to check for
|
||||||
|
plex_restricteduser
|
||||||
|
"""
|
||||||
|
return (self._threadSuspended or
|
||||||
|
window('plex_restricteduser') == 'true' or
|
||||||
|
not window('plex_token'))
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="Plex">
|
<category label="Plex">
|
||||||
<setting id="enableContext" type="bool" label="30413" default="true" />
|
|
||||||
<setting id="skipContextMenu" type="bool" label="30520" default="false" visible="eq(-1,true)" subsetting="true" />
|
|
||||||
<setting type="lsep" label="plex.tv"/>
|
<setting type="lsep" label="plex.tv"/>
|
||||||
<setting id="plex_status" label="39071" type="text" default="Not logged in to plex.tv" enable="false" /><!-- Current plex.tv status: -->
|
<setting id="plex_status" label="39071" type="text" default="Not logged in to plex.tv" enable="false" /><!-- Current plex.tv status: -->
|
||||||
<setting id="plexLogin" label="Plex user:" type="text" default="" enable="false" />
|
<setting id="plexLogin" label="Plex user:" type="text" default="" enable="false" />
|
||||||
<setting type="sep" text=""/>
|
|
||||||
|
|
||||||
<setting id="myplexlogin" label="39025" type="bool" default="true" /> <!-- Log into plex.tv on startup -->
|
<setting id="myplexlogin" label="39025" type="bool" default="true" /> <!-- Log into plex.tv on startup -->
|
||||||
<setting label="39209" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=togglePlexTV)" option="close" />
|
<setting label="39209" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=togglePlexTV)" option="close" />
|
||||||
<setting id="plexhome" label="Plex home in use" type="bool" default="" visible="false" />
|
<setting id="plexhome" label="Plex home in use" type="bool" default="" visible="false" />
|
||||||
|
@ -36,11 +32,14 @@
|
||||||
|
|
||||||
<setting type="lsep" label="39008" />
|
<setting type="lsep" label="39008" />
|
||||||
<setting id="plexCompanion" label="39004" type="bool" default="true" />
|
<setting id="plexCompanion" label="39004" type="bool" default="true" />
|
||||||
<setting id="deviceNameOpt" label="30504" type="bool" default="false" subsetting="true" visible="eq(-1,true)" />
|
<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="PlexKodiConnect" subsetting="true" />
|
||||||
<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="Kodi" subsetting="true" />
|
<setting id="companionPort" label="39005" type="number" default="3005" option="int" visible="eq(-2,true)" subsetting="true" />
|
||||||
<setting id="companionPort" label="39005" type="number" default="3005" option="int" visible="eq(-3,true)" subsetting="true" />
|
<setting id="companionUpdatePort" label="39078" type="number" default="32412" option="int" visible="eq(-3,true)" subsetting="true" />
|
||||||
<setting id="companionUpdatePort" label="39078" type="number" default="32412" option="int" visible="eq(-4,true)" subsetting="true" />
|
<setting type="lsep" label="39700" />
|
||||||
|
<setting id="enable_alexa" label="39701" type="bool" default="true"/>
|
||||||
|
<setting type="lsep" label="" />
|
||||||
|
<setting id="enableContext" type="bool" label="30413" default="true" />
|
||||||
|
<setting id="skipContextMenu" type="bool" label="30520" default="false" visible="eq(-1,true)" subsetting="true" />
|
||||||
<setting id="plex_restricteduser" type="bool" default="false" visible="false"/>
|
<setting id="plex_restricteduser" type="bool" default="false" visible="false"/>
|
||||||
<setting id="plex_allows_mediaDeletion" type="bool" default="true" visible="false"/>
|
<setting id="plex_allows_mediaDeletion" type="bool" default="true" visible="false"/>
|
||||||
<setting id="companion_show_gdm_port_warning" type="bool" default="true" visible="false"/>
|
<setting id="companion_show_gdm_port_warning" type="bool" default="true" visible="false"/>
|
||||||
|
|
28
service.py
28
service.py
|
@ -36,7 +36,7 @@ import initialsetup
|
||||||
from kodimonitor import KodiMonitor
|
from kodimonitor import KodiMonitor
|
||||||
from librarysync import LibrarySync
|
from librarysync import LibrarySync
|
||||||
import videonodes
|
import videonodes
|
||||||
from websocket_client import WebSocket
|
from websocket_client import PMS_Websocket, Alexa_Websocket
|
||||||
import downloadutils
|
import downloadutils
|
||||||
from playqueue import Playqueue
|
from playqueue import Playqueue
|
||||||
|
|
||||||
|
@ -70,6 +70,7 @@ class Service():
|
||||||
|
|
||||||
user_running = False
|
user_running = False
|
||||||
ws_running = False
|
ws_running = False
|
||||||
|
alexa_running = False
|
||||||
library_running = False
|
library_running = False
|
||||||
plexCompanion_running = False
|
plexCompanion_running = False
|
||||||
playqueue_running = False
|
playqueue_running = False
|
||||||
|
@ -148,7 +149,8 @@ class Service():
|
||||||
|
|
||||||
# Initialize important threads, handing over self for callback purposes
|
# Initialize important threads, handing over self for callback purposes
|
||||||
self.user = UserClient(self)
|
self.user = UserClient(self)
|
||||||
self.ws = WebSocket(self)
|
self.ws = PMS_Websocket(self)
|
||||||
|
self.alexa = Alexa_Websocket(self)
|
||||||
self.library = LibrarySync(self)
|
self.library = LibrarySync(self)
|
||||||
self.plexCompanion = PlexCompanion(self)
|
self.plexCompanion = PlexCompanion(self)
|
||||||
self.playqueue = Playqueue(self)
|
self.playqueue = Playqueue(self)
|
||||||
|
@ -201,6 +203,11 @@ class Service():
|
||||||
if not self.ws_running:
|
if not self.ws_running:
|
||||||
self.ws_running = True
|
self.ws_running = True
|
||||||
self.ws.start()
|
self.ws.start()
|
||||||
|
# Start the Alexa thread
|
||||||
|
if (not self.alexa_running and
|
||||||
|
settings('enable_alexa') == 'true'):
|
||||||
|
self.alexa_running = True
|
||||||
|
self.alexa.start()
|
||||||
# Start the syncing thread
|
# Start the syncing thread
|
||||||
if not self.library_running:
|
if not self.library_running:
|
||||||
self.library_running = True
|
self.library_running = True
|
||||||
|
@ -326,6 +333,10 @@ class Service():
|
||||||
self.ws.stopThread()
|
self.ws.stopThread()
|
||||||
except:
|
except:
|
||||||
log.warn('Websocket client already shut down')
|
log.warn('Websocket client already shut down')
|
||||||
|
try:
|
||||||
|
self.alexa.stopThread()
|
||||||
|
except:
|
||||||
|
log.warn('Websocket client already shut down')
|
||||||
try:
|
try:
|
||||||
self.user.stopThread()
|
self.user.stopThread()
|
||||||
except:
|
except:
|
||||||
|
@ -334,14 +345,23 @@ class Service():
|
||||||
downloadutils.DownloadUtils().stopSession()
|
downloadutils.DownloadUtils().stopSession()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
window('plex_service_started', clear=True)
|
||||||
log.warn("======== STOP %s ========" % v.ADDON_NAME)
|
log.warn("======== STOP %s ========" % v.ADDON_NAME)
|
||||||
|
|
||||||
|
# Safety net - Kody starts PKC twice upon first installation!
|
||||||
|
if window('plex_service_started') == 'true':
|
||||||
|
exit = True
|
||||||
|
else:
|
||||||
|
window('plex_service_started', value='true')
|
||||||
|
exit = False
|
||||||
|
|
||||||
# Delay option
|
# Delay option
|
||||||
delay = int(settings('startupDelay'))
|
delay = int(settings('startupDelay'))
|
||||||
|
|
||||||
log.warn("Delaying Plex startup by: %s sec..." % delay)
|
log.warn("Delaying Plex startup by: %s sec..." % delay)
|
||||||
if delay and Monitor().waitForAbort(delay):
|
if exit:
|
||||||
|
log.error('PKC service.py already started - exiting this instance')
|
||||||
|
elif delay and Monitor().waitForAbort(delay):
|
||||||
# Start the service
|
# Start the service
|
||||||
log.warn("Abort requested while waiting. PKC not started.")
|
log.warn("Abort requested while waiting. PKC not started.")
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in a new issue