Merge Master

This commit is contained in:
tomkat83 2016-12-20 16:13:19 +01:00
parent db02a001a8
commit ad8b7c7d90
21 changed files with 473 additions and 410 deletions

View file

@ -1,52 +1,65 @@
# 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**
PKC combines the best of Kodi - ultra smooth navigation, beautiful and highly customizable user interfaces and playback of any file under the sun, and the Plex Media Server to manage all your media without lifting a finger. PKC combines the best of Kodi - ultra smooth navigation, beautiful and highly customizable user interfaces and playback of any file under the sun - and the Plex Media Server.
Have a look at [some screenshots](https://github.com/croneter/PlexKodiConnect/wiki/Some-PKC-Screenshots) to see what's possible. Have a look at [some screenshots](https://github.com/croneter/PlexKodiConnect/wiki/Some-PKC-Screenshots) to see what's possible.
### Content
* [**Warning**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#warning)
* [**What does PKC do and how is it different from the official 'Plex for Kodi'**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#what-does-pkc-do-and-how-is-it-different-from-the-official-plex-for-kod)
* [**Download and Installation**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#download-and-installation)
* [**Important notes**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#important-notes)
* [**Donations**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#donations)
* [**What is currently supported?**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#what-is-currently-supported)
* [**Known Larger Issues**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#known-larger-issues)
* [**Issues being worked on**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#issues-being-worked-on)
* [**Pipeline for future development**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#what-could-be-in-the-pipeline-for-future-development)
* [**Checkout the PKC Wiki**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#checkout-the-pkc-wiki)
* [**Credits**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#credits)
### Warning ### Warning
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! Use at your own risk! 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. Don't worry if you want Plex to manage all your media (like you should ;-)).
### What does PKC do and how is it different from the official ['Plex for Kodi'](https://www.plex.tv/apps/computer/kodi/)?
With other Plex addons for Kodi such as the official [Plex for Kodi](https://www.plex.tv/apps/computer/kodi/) or [PlexBMC](https://forums.plex.tv/discussion/106593/plexbmc-xbmc-add-on-to-connect-to-plex-media-server) there are a couple of issues:
- Other Kodi addons such as NextAired, remote apps and others won't work
- You can only use special Kodi skins
- Slow speed: when browsing data has to be retrieved from the server. Especially on slower devices this can take too much time and you will notice artwork being loaded slowly while you browse the library
- All kinds of workarounds are needed to get the best experience on Kodi clients
PKC synchronizes your media from your Plex server to the native Kodi database. Because PKC uses the native Kodi database, the above limitations are gone!
- Use any Kodi skin you want!
- You can browse your media at full speed, images are cached
- All other Kodi addons will be able to "see" your media, thinking it's normal Kodi stuff
Some people argue that PKC is 'hacky' because of the way it directly accesses the Kodi database. See [here for a more thorough discussion](https://github.com/croneter/PlexKodiConnect/wiki/Is-PKC-'hacky'%3F).
### Download and Installation ### Download and Installation
[ ![Download](https://api.bintray.com/packages/croneter/PlexKodiConnect/PlexKodiConnect/images/download.svg) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip) [ ![Download](https://api.bintray.com/packages/croneter/PlexKodiConnect/PlexKodiConnect/images/download.svg) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip)
The easiest way to install PKC is via our PlexKodiConnect Kodi repository (we cannot use the official Kodi repository as PKC messes with Kodi's databases). See the [installation guideline on how to do this](https://github.com/croneter/PlexKodiConnect/wiki/Installation). Install PKC via the PlexKodiConnect Kodi repository (we cannot use the official Kodi repository as PKC messes with Kodi's databases). See the [installation guideline on how to do this](https://github.com/croneter/PlexKodiConnect/wiki/Installation).
**Possibly UNSTABLE BETA version:** [ ![Download](https://api.bintray.com/packages/croneter/PlexKodiConnect_BETA/PlexKodiConnect_BETA/images/download.svg) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip) **Possibly UNSTABLE BETA version:** [ ![Download](https://api.bintray.com/packages/croneter/PlexKodiConnect_BETA/PlexKodiConnect_BETA/images/download.svg) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip)
### Important Notes
1. If you are using a **low CPU device like a Raspberry Pi or a CuBox**, PKC might be instable or crash during initial sync. Lower the number of threads in the [PKC settings under Sync Options](https://github.com/croneter/PlexKodiConnect/wiki/PKC-settings#sync-options): `Limit artwork cache threads: 5`
Don't forget to reboot Kodi after that.
2. **Compatibility**:
* PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
* PKC is not (and will never be) compatible with the **MySQL database replacement** in Kodi. In fact, PKC replaces the MySQL functionality because it acts as a "man in the middle" for your entire media library.
* If **another plugin is not working** like it's supposed to, try to use [PKC direct paths](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths)
3. If you post logs, your **Plex tokens** might be included. Be sure to double and triple check for tokens before posting any logs anywhere by searching for `token`
### Donations ### Donations
I'm not in any way affiliated with Plex. Thank you very much for a small donation via ko-fi.com and PayPal if you appreciate PKC. I'm not in any way affiliated with Plex. Thank you very much for a small donation via ko-fi.com and PayPal if you appreciate PKC.
**Full disclaimer:** I will see your name and address on my PayPal account. Rest assured that I will not share this with anyone. **Full disclaimer:** I will see your name and address on my PayPal account. Rest assured that I will not share this with anyone.
[ ![Download](https://az743702.vo.msecnd.net/cdn/kofi1.png?v=a|alt=Buy Me a Coffee)](https://ko-fi.com/A8182EB) [ ![Download](https://az743702.vo.msecnd.net/cdn/kofi1.png?v=a|alt=Buy Me a Coffee)](https://ko-fi.com/A8182EB)
### IMPORTANT NOTES
1. If you are using a **low CPU device like a Raspberry Pi or a CuBox**, PKC might be instable or crash during initial sync. Lower the number of threads in the [PKC settings under Sync Options](https://github.com/croneter/PlexKodiConnect/wiki/PKC-settings#sync-options): `Limit artwork cache threads: 5`
Don't forget to reboot Kodi after that.
2. If you post logs, your **Plex tokens** might be included. Be sure to double and triple check for tokens before posting any logs anywhere by searching for `token`
3. **Compatibility**: PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
### Checkout the PKC Wiki
The [Wiki can be found here](https://github.com/croneter/PlexKodiConnect/wiki) and will hopefully answer all your questions.
### What does PKC do?
With other addons for Kodi there are a couple of issues:
- 3rd party addons such as NextAired, remote apps etc. won't work
- Slow speed: when browsing the data has to be retrieved from the server. Especially on slower devices this can take too much time
- You can only use special Kodi skins
- All kinds of workarounds are needed to get the best experience on Kodi clients
PKC synchronizes your media from your Plex server to the native Kodi database. Because PKC uses the native Kodi database, the above limitations are gone!
- You can browse your media full speed, images are cached
- All other Kodi addons will be able to "see" your media, thinking it's normal Kodi stuff
- Use any Kodi skin you want!
### What is currently supported? ### What is currently supported?
PKC currently provides the following features: PKC currently provides the following features:
@ -71,14 +84,14 @@ PKC currently provides the following features:
+ Extra fanart backgrounds + Extra fanart backgrounds
- Automatically group movies into [movie sets](http://kodi.wiki/view/movie_sets) - Automatically group movies into [movie sets](http://kodi.wiki/view/movie_sets)
- Direct play from network paths (e.g. "\\\\server\\Plex\\movie.mkv") instead of streaming from slow HTTP (e.g. "192.168.1.1:32400"). You have to setup all your Plex libraries to point to such network paths. Do have a look at [the wiki here](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths) - Direct play from network paths (e.g. "\\\\server\\Plex\\movie.mkv") instead of streaming from slow HTTP (e.g. "192.168.1.1:32400"). You have to setup all your Plex libraries to point to such network paths. Do have a look at [the wiki here](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths)
- Delete PMS items from the Kodi context menu
### Known Larger Issues ### Known Larger Issues
Solutions are unlikely due to the nature of these issues Solutions are unlikely due to the nature of these issues
- A Plex Media Server "bug" leads to frequent and slow syncs, see [here for more info](https://github.com/croneter/PlexKodiConnect/issues/135)
- *Plex Music when using Addon paths instead of Native Direct Paths:* Kodi tries to scan every(!) single Plex song on startup. This leads to errors in the Kodi log file and potentially even crashes. See the [Github issue](https://github.com/croneter/PlexKodiConnect/issues/14) for more details - *Plex Music when using Addon paths instead of Native Direct Paths:* Kodi tries to scan every(!) single Plex song on startup. This leads to errors in the Kodi log file and potentially even crashes. See the [Github issue](https://github.com/croneter/PlexKodiConnect/issues/14) for more details
- *Plex Music when using Addon paths instead of Native Direct Paths:* You must have a static IP address for your Plex media server if you plan to use Plex Music features - *Plex Music when using Addon paths instead of Native Direct Paths:* You must have a static IP address for your Plex media server if you plan to use Plex Music features
- If something on the PMS has changed, this change is synced to Kodi. Hence if you rescan your entire library, a long PlexKodiConnect re-sync is triggered. You can [change your PMS settings to avoid that](https://github.com/croneter/PlexKodiConnect/wiki/Configure-PKC-on-the-First-Run#deactivate-frequent-updates)
- External Plex subtitles (in separate files, e.g. mymovie.srt) can be used, but it is impossible to label them correctly and tell what language they are in. However, this is not the case if you use direct paths - External Plex subtitles (in separate files, e.g. mymovie.srt) can be used, but it is impossible to label them correctly and tell what language they are in. However, this is not the case if you use direct paths
*Background Sync:* *Background Sync:*
@ -91,21 +104,18 @@ However, some changes to individual items are instantly detected, e.g. if you ma
### Issues being worked on ### Issues being worked on
Have a look at the [Github Issues Page](https://github.com/croneter/PlexKodiConnect/issues). 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? ### What could be in the pipeline for future development?
- Plex channels
- Movie extras (trailers already work)
- Playlists - Playlists
- Music Videos - Music Videos
- Deleting PMS items from Kodi
- TV Shows Theme Music (ultra-low prio)
### Important note about MySQL database in Kodi
The addon is not (and will not be) compatible with the MySQL database replacement in Kodi. In fact, PlexKodiConnect takes over the point of having a MySQL database because it acts as a "man in the middle" for your entire media library.
### 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!
### Credits ### Credits

View file

@ -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.4.8" version="1.5.1"
provider-name="croneter"> provider-name="croneter">
<requires> <requires>
<import addon="xbmc.python" version="2.1.0"/> <import addon="xbmc.python" version="2.1.0"/>

View file

@ -1,3 +1,37 @@
version 1.5.1 (beta only)
- Fix playstate and PMS item changes not working/not propagating anymore (caused by a change Plex made with the websocket interface). UPGRADE YOUR PMS!!
- 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
- Improvements to PMS on/offline notifications
- Note to PLEASE read the Wiki if one is using several Plex libraries (shows on first PKC install only)
- Get rid of low powered device option (always use low powered option)
- Don't show a notification when searching for PMS
- Combine h265 und HEVC into one setting
- Less traffic when PKC is checking whether a PMS is still offline
- Improve logging
version 1.5.0
Out for everyone:
- reatly speed up the database sync. Please report if you experience any issues!
- Only show database sync progress for NEW PMS items
- Speed up the pathname verifications
- Update readme to reflect the advent of the official Plex for Kodi
- Fix for not getting tv show additional fanart
- Fix for fanart url containing spaces
- Fix library AttributeError
- Catch websocket handshake errors correctly
version 1.4.10 (beta only)
- Fix library AttributeError
version 1.4.9 (beta only)
- Greatly speed up the database sync. Please report if you experience any issues!
- Only show database sync progress for NEW PMS items
- Speed up the pathname verifications
- Update readme to reflect the advent of the official Plex for Kodi
version 1.4.8 (beta only) version 1.4.8 (beta only)
- Fix for not getting tv show additional fanart - Fix for not getting tv show additional fanart
- Fix for fanart url containing spaces - Fix for fanart url containing spaces

View file

@ -127,6 +127,8 @@
<string id="30141">Enable Background Image (Requires Restart)</string> <string id="30141">Enable Background Image (Requires Restart)</string>
<string id="30142">Services</string> <string id="30142">Services</string>
<string id="30143">Always transcode if video bitrate is above</string>
<string id="30150">Skin does not support setting views</string> <string id="30150">Skin does not support setting views</string>
<string id="30151">Select item action (Requires Restart)</string> <string id="30151">Select item action (Requires Restart)</string>
@ -294,7 +296,7 @@
<string id="30519">Ask to play trailers</string> <string id="30519">Ask to play trailers</string>
<string id="30520">Skip Plex delete confirmation for the context menu (use at your own risk)</string> <string id="30520">Skip Plex delete confirmation for the context menu (use at your own risk)</string>
<string id="30521">Jump back on resume (in seconds)</string> <string id="30521">Jump back on resume (in seconds)</string>
<string id="30522">Force transcode H265</string> <string id="30522">Force transcode h265/HEVC</string>
<string id="30523">Music metadata options (not compatible with direct stream)</string> <string id="30523">Music metadata options (not compatible with direct stream)</string>
<string id="30524">Import music song rating directly from files</string> <string id="30524">Import music song rating directly from files</string>
<string id="30525">Convert music song rating to Emby rating</string> <string id="30525">Convert music song rating to Emby rating</string>
@ -423,7 +425,6 @@
<string id="39062">Sync when screensaver is deactivated</string> <string id="39062">Sync when screensaver is deactivated</string>
<string id="39063">Force Transcode Hi10P</string> <string id="39063">Force Transcode Hi10P</string>
<string id="39064">Recently Added: Also show already watched episodes</string> <string id="39064">Recently Added: Also show already watched episodes</string>
<string id="39065">Force Transcode HEVC</string>
<string id="39066">Recently Added: Also show already watched movies (Refresh Plex playlist/nodes!)</string> <string id="39066">Recently Added: Also show already watched movies (Refresh Plex playlist/nodes!)</string>
<string id="39067">Your current Plex Media Server:</string> <string id="39067">Your current Plex Media Server:</string>
<string id="39068">[COLOR yellow]Manually enter Plex Media Server address[/COLOR]</string> <string id="39068">[COLOR yellow]Manually enter Plex Media Server address[/COLOR]</string>
@ -434,6 +435,7 @@
<string id="39073">Appearance Tweaks</string> <string id="39073">Appearance Tweaks</string>
<string id="39074">TV Shows</string> <string id="39074">TV Shows</string>
<string id="39075">Always use default Plex subtitle if possible</string> <string id="39075">Always use default Plex subtitle if possible</string>
<string id="39076">If you use several Plex libraries of one kind, e.g. "Kids Movies" and "Parents Movies", be sure to check the Wiki: https://goo.gl/JFtQV9</string>
<!-- Plex Entrypoint.py --> <!-- Plex Entrypoint.py -->
<string id="39200">Log-out Plex Home User </string> <string id="39200">Log-out Plex Home User </string>

View file

@ -18,7 +18,7 @@
<string id="30509">Plex Musik-Bibliotheken aktivieren</string> <string id="30509">Plex Musik-Bibliotheken aktivieren</string>
<string id="30518">Plex Trailer aktivieren (Plexpass benötigt)</string> <string id="30518">Plex Trailer aktivieren (Plexpass benötigt)</string>
<string id="30519">Nachfragen, ob Trailer gespielt werden sollen</string> <string id="30519">Nachfragen, ob Trailer gespielt werden sollen</string>
<string id="30522">H265 Codec Transkodierung erzwingen</string> <string id="30522">h265/HEVC Codec Transkodierung erzwingen</string>
<string id="30517">Netzwerk Credentials eingeben</string> <string id="30517">Netzwerk Credentials eingeben</string>
<string id="30529">PlexKodiConnect Start Verzögerung (in Sekunden)</string> <string id="30529">PlexKodiConnect Start Verzögerung (in Sekunden)</string>
<string id="30527">Extras ignorieren, wenn Nächste Episode gespielt wird</string> <string id="30527">Extras ignorieren, wenn Nächste Episode gespielt wird</string>
@ -151,7 +151,8 @@
<string id="30140"> - Themen-Musik in Schleife abspielen</string> <string id="30140"> - Themen-Musik in Schleife abspielen</string>
<string id="30141">Laden im Hintergrund aktivieren (Erfordert Neustart)</string> <string id="30141">Laden im Hintergrund aktivieren (Erfordert Neustart)</string>
<string id="30142">Dienste</string> <string id="30142">Dienste</string>
<string id="30143">Info-Loader aktivieren (Erfordert Neustart)</string> <string id="30143">Immer transkodieren falls Bitrate höher als</string>
<string id="30144">Menü-Loader aktivieren (Erfordert Neustart)</string> <string id="30144">Menü-Loader aktivieren (Erfordert Neustart)</string>
<string id="30145">WebSocket Fernbedienung aktivieren (Erfordert Neustart)</string> <string id="30145">WebSocket Fernbedienung aktivieren (Erfordert Neustart)</string>
<string id="30146">'Laufende Medien'-Loader aktivieren (Erfordert Neustart)</string> <string id="30146">'Laufende Medien'-Loader aktivieren (Erfordert Neustart)</string>
@ -373,7 +374,6 @@
<string id="39062">Sync wenn Bildschirmschoner deaktiviert wird</string> <string id="39062">Sync wenn Bildschirmschoner deaktiviert wird</string>
<string id="39063">Hi10p Codec Transkodierung erzwingen</string> <string id="39063">Hi10p Codec Transkodierung erzwingen</string>
<string id="39064">"Zuletzt hinzugefügt": gesehene Folgen anzeigen</string> <string id="39064">"Zuletzt hinzugefügt": gesehene Folgen anzeigen</string>
<string id="39065">HEVC Codec Transkodierung erzwingen</string>
<string id="39066">"Zuletzt hinzugefügt": gesehene Filme anzeigen (Plex Playlisten und Nodes zurücksetzen!)</string> <string id="39066">"Zuletzt hinzugefügt": gesehene Filme anzeigen (Plex Playlisten und Nodes zurücksetzen!)</string>
<string id="39067">Aktueller Plex Media Server:</string> <string id="39067">Aktueller Plex Media Server:</string>
<string id="39068">[COLOR yellow]Plex Media Server Adresse manuell eingeben[/COLOR]</string> <string id="39068">[COLOR yellow]Plex Media Server Adresse manuell eingeben[/COLOR]</string>
@ -384,6 +384,7 @@
<string id="39073">Tweaks Aussehen</string> <string id="39073">Tweaks Aussehen</string>
<string id="39074">TV Serien</string> <string id="39074">TV Serien</string>
<string id="39075">Falls möglich, Plex Standard-Untertitel anzeigen</string> <string id="39075">Falls möglich, Plex Standard-Untertitel anzeigen</string>
<string id="39076">Falls du mehrere Plex Bibliotheken einer Art nutzt, z.B. "Filme Kinder" und "Filme Eltern", lies unbedingt das Wiki unter https://goo.gl/JFtQV9</string>
<!-- Plex Entrypoint.py --> <!-- Plex Entrypoint.py -->
<string id="39200">Plex Home Benutzer abmelden: </string> <string id="39200">Plex Home Benutzer abmelden: </string>

View file

@ -49,7 +49,8 @@ import clientinfo
import downloadutils import downloadutils
from utils import window, settings, language as lang, tryDecode, tryEncode, \ from utils import window, settings, language as lang, tryDecode, tryEncode, \
DateToKodi, KODILANGUAGE DateToKodi, KODILANGUAGE
from PlexFunctions import PLEX_TO_KODI_TIMEFACTOR, PMSHttpsEnabled from PlexFunctions import PLEX_TO_KODI_TIMEFACTOR, PMSHttpsEnabled, \
REMAP_TYPE_FROM_PLEXTYPE
import embydb_functions as embydb import embydb_functions as embydb
############################################################################### ###############################################################################
@ -288,18 +289,18 @@ class PlexAPI():
url = 'https://plex.tv/api/home/users' url = 'https://plex.tv/api/home/users'
else: else:
url = url + '/library/onDeck' url = url + '/library/onDeck'
log.info("Checking connection to server %s with verifySSL=%s" log.debug("Checking connection to server %s with verifySSL=%s"
% (url, verifySSL)) % (url, verifySSL))
# Check up to 3 times before giving up # Check up to 3 times before giving up
count = 0 count = 0
while count < 3: while count < 1:
answer = self.doUtils(url, answer = self.doUtils(url,
authenticate=False, authenticate=False,
headerOptions=headerOptions, headerOptions=headerOptions,
verifySSL=verifySSL, verifySSL=verifySSL,
timeout=4) timeout=4)
if answer is None: if answer is None:
log.info("Could not connect to %s" % url) log.debug("Could not connect to %s" % url)
count += 1 count += 1
xbmc.sleep(500) xbmc.sleep(500)
continue continue
@ -316,7 +317,7 @@ class PlexAPI():
# We could connect but maybe were not authenticated. No worries # We could connect but maybe were not authenticated. No worries
log.debug("Checking connection successfull. Answer: %s" % answer) log.debug("Checking connection successfull. Answer: %s" % answer)
return answer return answer
log.info('Failed to connect to %s too many times. PMS is dead' % url) log.debug('Failed to connect to %s too many times. PMS is dead' % url)
return False return False
def GetgPMSKeylist(self): def GetgPMSKeylist(self):
@ -510,13 +511,6 @@ class PlexAPI():
self.g_PMS dict set self.g_PMS dict set
""" """
self.g_PMS = {} self.g_PMS = {}
# "Searching for Plex Server"
xbmcgui.Dialog().notification(
heading=addonName,
message=lang(39055),
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png",
time=4000,
sound=False)
# Look first for local PMS in the LAN # Look first for local PMS in the LAN
pmsList = self.PlexGDM() pmsList = self.PlexGDM()
@ -2515,29 +2509,17 @@ class API():
""" """
if path is None: if path is None:
return None return None
types = { typus = REMAP_TYPE_FROM_PLEXTYPE[typus]
'movie': 'movie', if window('remapSMB') == 'true':
'show': 'tv', path = path.replace(window('remapSMB%sOrg' % typus),
'season': 'tv', window('remapSMB%sNew' % typus),
'episode': 'tv',
'artist': 'music',
'album': 'music',
'song': 'music',
'track': 'music',
'clip': 'clip',
'photo': 'photo'
}
typus = types[typus]
if settings('remapSMB') == 'true':
path = path.replace(settings('remapSMB%sOrg' % typus),
settings('remapSMB%sNew' % typus),
1) 1)
# There might be backslashes left over: # There might be backslashes left over:
path = path.replace('\\', '/') path = path.replace('\\', '/')
elif settings('replaceSMB') == 'true': elif window('replaceSMB') == 'true':
if path.startswith('\\\\'): if path.startswith('\\\\'):
path = 'smb:' + path.replace('\\', '/') path = 'smb:' + path.replace('\\', '/')
if settings('plex_pathverified') == 'true' and forceCheck is False: if window('plex_pathverified') == 'true' and forceCheck is False:
return path return path
# exist() needs a / or \ at the end to work for directories # exist() needs a / or \ at the end to work for directories
@ -2558,12 +2540,12 @@ class API():
if self.askToValidate(path): if self.askToValidate(path):
window('plex_shouldStop', value="true") window('plex_shouldStop', value="true")
path = None path = None
settings('plex_pathverified', value='true') window('plex_pathverified', value='true')
else: else:
path = None path = None
elif forceCheck is False: elif forceCheck is False:
if settings('plex_pathverified') != 'true': if window('plex_pathverified') != 'true':
settings('plex_pathverified', value='true') window('plex_pathverified', value='true')
return path return path
def askToValidate(self, url): def askToValidate(self, url):

View file

@ -72,6 +72,20 @@ KODIAUDIOVIDEO_FROM_MEDIA_TYPE = {
} }
REMAP_TYPE_FROM_PLEXTYPE = {
'movie': 'movie',
'show': 'tv',
'season': 'tv',
'episode': 'tv',
'artist': 'music',
'album': 'music',
'song': 'music',
'track': 'music',
'clip': 'clip',
'photo': 'photo'
}
def ConvertPlexToKodiTime(plexTime): def ConvertPlexToKodiTime(plexTime):
""" """
Converts Plextime to Koditime. Returns an int (in seconds). Converts Plextime to Koditime. Returns an int (in seconds).

View file

@ -131,13 +131,9 @@ class Image_Cache_Thread(Thread):
xbmc_host = 'localhost' xbmc_host = 'localhost'
xbmc_port, xbmc_username, xbmc_password = setKodiWebServerDetails() xbmc_port, xbmc_username, xbmc_password = setKodiWebServerDetails()
sleep_between = 50 sleep_between = 50
if settings('low_powered_device') == 'true': # Potentially issues with limited number of threads
# Low CPU, potentially issues with limited number of threads # Hence let Kodi wait till download is successful
# Hence let Kodi wait till download is successful timeout = (35.1, 35.1)
timeout = (35.1, 35.1)
else:
# High CPU, no issue with limited number of threads
timeout = (0.01, 0.01)
def __init__(self, queue): def __init__(self, queue):
self.queue = queue self.queue = queue

View file

@ -215,12 +215,12 @@ class DownloadUtils():
# THE EXCEPTIONS # THE EXCEPTIONS
except requests.exceptions.ConnectionError as e: except requests.exceptions.ConnectionError as e:
# Connection error # Connection error
log.warn("Server unreachable at: %s" % url) log.debug("Server unreachable at: %s" % url)
log.warn(e) log.debug(e)
except requests.exceptions.ConnectTimeout as e: except requests.exceptions.Timeout as e:
log.warn("Server timeout at: %s" % url) log.debug("Server timeout at: %s" % url)
log.warn(e) log.debug(e)
except requests.exceptions.HTTPError as e: except requests.exceptions.HTTPError as e:
log.warn('HTTP Error at %s' % url) log.warn('HTTP Error at %s' % url)

View file

@ -242,14 +242,6 @@ class InitialSetup():
log.warn('The PMS you have used before with a unique ' log.warn('The PMS you have used before with a unique '
'machineIdentifier of %s and name %s is ' 'machineIdentifier of %s and name %s is '
'offline' % (self.serverid, name)) 'offline' % (self.serverid, name))
# "PMS xyz offline"
if settings('show_pms_offline') == 'true':
self.dialog.notification(addonName,
'%s %s'
% (name, lang(39213)),
xbmcgui.NOTIFICATION_ERROR,
7000,
False)
return return
chk = self._checkServerCon(server) chk = self._checkServerCon(server)
if chk == 504 and httpsUpdated is False: if chk == 504 and httpsUpdated is False:
@ -441,15 +433,6 @@ class InitialSetup():
if settings('InstallQuestionsAnswered') == 'true': if settings('InstallQuestionsAnswered') == 'true':
return return
# Is your Kodi installed on a low-powered device like a Raspberry Pi?
# If yes, then we will reduce the strain on Kodi to prevent it from
# crashing.
if dialog.yesno(heading=addonName, line1=lang(39072)):
settings('low_powered_device', value="true")
settings('syncThreadNumber', value="1")
else:
settings('low_powered_device', value="false")
# Additional settings where the user needs to choose # Additional settings where the user needs to choose
# Direct paths (\\NAS\mymovie.mkv) or addon (http)? # Direct paths (\\NAS\mymovie.mkv) or addon (http)?
goToSettings = False goToSettings = False
@ -496,6 +479,9 @@ class InitialSetup():
log.debug("User opted to use FanArtTV") log.debug("User opted to use FanArtTV")
settings('FanartTV', value="true") settings('FanartTV', value="true")
# If you use several Plex libraries of one kind, e.g. "Kids Movies" and
# "Parents Movies", be sure to check https://goo.gl/JFtQV9
dialog.ok(heading=addonName, line1=lang(39076))
# Make sure that we only ask these questions upon first installation # Make sure that we only ask these questions upon first installation
settings('InstallQuestionsAnswered', value='true') settings('InstallQuestionsAnswered', value='true')

View file

@ -7,12 +7,11 @@ from urllib import urlencode
from ntpath import dirname from ntpath import dirname
from datetime import datetime from datetime import datetime
import xbmc
import xbmcgui import xbmcgui
import artwork import artwork
from utils import tryEncode, tryDecode, settings, window, kodiSQL, \ from utils import tryEncode, tryDecode, settings, window, kodiSQL, \
CatchExceptions CatchExceptions, KODIVERSION
import embydb_functions as embydb import embydb_functions as embydb
import kodidb_functions as kodidb import kodidb_functions as kodidb
@ -36,7 +35,6 @@ class Items(object):
""" """
def __init__(self): def __init__(self):
self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
self.directpath = window('useDirectPaths') == 'true' self.directpath = window('useDirectPaths') == 'true'
self.artwork = artwork.Artwork() self.artwork = artwork.Artwork()
@ -435,7 +433,7 @@ class Movies(Items):
% (itemid, title)) % (itemid, title))
# Update the movie entry # Update the movie entry
if self.kodiversion > 16: if KODIVERSION > 16:
query = ' '.join(( query = ' '.join((
"UPDATE movie", "UPDATE movie",
"SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?," "SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?,"
@ -466,7 +464,7 @@ class Movies(Items):
##### OR ADD THE MOVIE ##### ##### OR ADD THE MOVIE #####
else: else:
log.info("ADD movie itemid: %s - Title: %s" % (itemid, title)) log.info("ADD movie itemid: %s - Title: %s" % (itemid, title))
if self.kodiversion > 16: if KODIVERSION > 16:
query = ( query = (
''' '''
INSERT INTO movie( idMovie, idFile, c00, c01, c02, c03, INSERT INTO movie( idMovie, idFile, c00, c01, c02, c03,
@ -985,7 +983,7 @@ class TVShows(Items):
log.info("UPDATE episode itemid: %s" % (itemid)) log.info("UPDATE episode itemid: %s" % (itemid))
# Update the movie entry # Update the movie entry
if self.kodiversion in (16, 17): if KODIVERSION in (16, 17):
# Kodi Jarvis, Krypton # Kodi Jarvis, Krypton
query = ' '.join(( query = ' '.join((
"UPDATE episode", "UPDATE episode",
@ -1018,7 +1016,7 @@ class TVShows(Items):
else: else:
log.info("ADD episode itemid: %s - Title: %s" % (itemid, title)) log.info("ADD episode itemid: %s - Title: %s" % (itemid, title))
# Create the episode entry # Create the episode entry
if self.kodiversion in (16, 17): if KODIVERSION in (16, 17):
# Kodi Jarvis, Krypton # Kodi Jarvis, Krypton
query = ( query = (
''' '''
@ -1318,7 +1316,7 @@ class Music(Items):
itemid, artistid, artisttype, "artist", checksum=checksum) itemid, artistid, artisttype, "artist", checksum=checksum)
# Process the artist # Process the artist
if self.kodiversion in (16, 17): if KODIVERSION in (16, 17):
query = ' '.join(( query = ' '.join((
"UPDATE artist", "UPDATE artist",
@ -1411,7 +1409,7 @@ class Music(Items):
itemid, albumid, "MusicAlbum", "album", checksum=checksum) itemid, albumid, "MusicAlbum", "album", checksum=checksum)
# Process the album info # Process the album info
if self.kodiversion == 17: if KODIVERSION == 17:
# Kodi Krypton # Kodi Krypton
query = ' '.join(( query = ' '.join((
@ -1424,7 +1422,7 @@ class Music(Items):
kodicursor.execute(query, (artistname, year, genre, bio, thumb, kodicursor.execute(query, (artistname, year, genre, bio, thumb,
rating, lastScraped, "album", studio, rating, lastScraped, "album", studio,
albumid)) albumid))
elif self.kodiversion == 16: elif KODIVERSION == 16:
# Kodi Jarvis # Kodi Jarvis
query = ' '.join(( query = ' '.join((
@ -1437,7 +1435,7 @@ class Music(Items):
kodicursor.execute(query, (artistname, year, genre, bio, thumb, kodicursor.execute(query, (artistname, year, genre, bio, thumb,
rating, lastScraped, "album", studio, rating, lastScraped, "album", studio,
albumid)) albumid))
elif self.kodiversion == 15: elif KODIVERSION == 15:
# Kodi Isengard # Kodi Isengard
query = ' '.join(( query = ' '.join((
@ -1679,7 +1677,7 @@ class Music(Items):
log.info("Failed to add album. Creating singles.") log.info("Failed to add album. Creating singles.")
kodicursor.execute("select coalesce(max(idAlbum),0) from album") kodicursor.execute("select coalesce(max(idAlbum),0) from album")
albumid = kodicursor.fetchone()[0] + 1 albumid = kodicursor.fetchone()[0] + 1
if self.kodiversion == 16: if KODIVERSION == 16:
# Kodi Jarvis # Kodi Jarvis
query = ( query = (
''' '''
@ -1689,7 +1687,7 @@ class Music(Items):
''' '''
) )
kodicursor.execute(query, (albumid, genre, year, "single")) kodicursor.execute(query, (albumid, genre, year, "single"))
elif self.kodiversion == 15: elif KODIVERSION == 15:
# Kodi Isengard # Kodi Isengard
query = ( query = (
''' '''
@ -1767,7 +1765,7 @@ class Music(Items):
artist_edb = emby_db.getItem_byId(artist_eid) artist_edb = emby_db.getItem_byId(artist_eid)
artistid = artist_edb[0] artistid = artist_edb[0]
finally: finally:
if self.kodiversion >= 17: if KODIVERSION >= 17:
# Kodi Krypton # Kodi Krypton
query = ( query = (
''' '''
@ -1842,11 +1840,11 @@ class Music(Items):
result = kodicursor.fetchone() result = kodicursor.fetchone()
if result and result[0] != album_artists: if result and result[0] != album_artists:
# Field is empty # Field is empty
if self.kodiversion in (16, 17): if KODIVERSION in (16, 17):
# Kodi Jarvis, Krypton # Kodi Jarvis, Krypton
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
kodicursor.execute(query, (album_artists, albumid)) kodicursor.execute(query, (album_artists, albumid))
elif self.kodiversion == 15: elif KODIVERSION == 15:
# Kodi Isengard # Kodi Isengard
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
kodicursor.execute(query, (album_artists, albumid)) kodicursor.execute(query, (album_artists, albumid))

View file

@ -3,13 +3,10 @@
############################################################################### ###############################################################################
import logging import logging
import xbmc
from ntpath import dirname from ntpath import dirname
import artwork import artwork
import clientinfo from utils import kodiSQL, KODIVERSION
from utils import settings, kodiSQL
############################################################################### ###############################################################################
@ -43,13 +40,8 @@ class GetKodiDB():
class Kodidb_Functions(): class Kodidb_Functions():
kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
def __init__(self, cursor): def __init__(self, cursor):
self.cursor = cursor self.cursor = cursor
self.clientInfo = clientinfo.ClientInfo()
self.artwork = artwork.Artwork() self.artwork = artwork.Artwork()
def pathHack(self): def pathHack(self):
@ -212,8 +204,7 @@ class Kodidb_Functions():
self.cursor.execute(query, (pathid, filename,)) self.cursor.execute(query, (pathid, filename,))
def addCountries(self, kodiid, countries, mediatype): def addCountries(self, kodiid, countries, mediatype):
if KODIVERSION > 14:
if self.kodiversion in (15, 16, 17):
# Kodi Isengard, Jarvis, Krypton # Kodi Isengard, Jarvis, Krypton
for country in countries: for country in countries:
query = ' '.join(( query = ' '.join((
@ -284,85 +275,74 @@ class Kodidb_Functions():
) )
self.cursor.execute(query, (idCountry, kodiid)) self.cursor.execute(query, (idCountry, kodiid))
def addPeople(self, kodiid, people, mediatype): def _getactorid(self, name):
"""
Crucial für sync speed!
"""
query = ' '.join((
"SELECT actor_id",
"FROM actor",
"WHERE name = ?",
"LIMIT 1"
))
self.cursor.execute(query, (name,))
try:
actorid = self.cursor.fetchone()[0]
except TypeError:
# Cast entry does not exists
self.cursor.execute("select coalesce(max(actor_id),0) from actor")
actorid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO actor(actor_id, name) VALUES (?, ?)"
self.cursor.execute(query, (actorid, name))
return actorid
def _addPerson(self, role, person_type, actorid, kodiid, mediatype,
castorder):
if "Actor" == person_type:
query = '''
INSERT OR REPLACE INTO actor_link(
actor_id, media_id, media_type, role, cast_order)
VALUES (?, ?, ?, ?, ?)
'''
self.cursor.execute(query, (actorid, kodiid, mediatype, role,
castorder))
castorder += 1
elif "Director" == person_type:
query = '''
INSERT OR REPLACE INTO director_link(
actor_id, media_id, media_type)
VALUES (?, ?, ?)
'''
self.cursor.execute(query, (actorid, kodiid, mediatype))
elif person_type == "Writer":
query = '''
INSERT OR REPLACE INTO writer_link(
actor_id, media_id, media_type)
VALUES (?, ?, ?)
'''
self.cursor.execute(query, (actorid, kodiid, mediatype))
elif "Artist" == person_type:
query = '''
INSERT OR REPLACE INTO actor_link(
actor_id, media_id, media_type)
VALUES (?, ?, ?)
'''
self.cursor.execute(query, (actorid, kodiid, mediatype))
return castorder
def addPeople(self, kodiid, people, mediatype):
castorder = 1 castorder = 1
for person in people: for person in people:
name = person['Name']
person_type = person['Type']
thumb = person['imageurl']
# Kodi Isengard, Jarvis, Krypton # Kodi Isengard, Jarvis, Krypton
if self.kodiversion in (15, 16, 17): if KODIVERSION > 14:
query = ' '.join(( actorid = self._getactorid(person['Name'])
# Link person to content
"SELECT actor_id", castorder = self._addPerson(person.get('Role'),
"FROM actor", person['Type'],
"WHERE name = ?", actorid,
"COLLATE NOCASE" kodiid,
)) mediatype,
self.cursor.execute(query, (name,)) castorder)
try:
actorid = self.cursor.fetchone()[0]
except TypeError:
# Cast entry does not exists
self.cursor.execute("select coalesce(max(actor_id),0) from actor")
actorid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO actor(actor_id, name) values(?, ?)"
self.cursor.execute(query, (actorid, name))
log.debug("Add people to media, processing: %s" % name)
finally:
# Link person to content
if "Actor" in person_type:
role = person.get('Role')
query = (
'''
INSERT OR REPLACE INTO actor_link(
actor_id, media_id, media_type, role, cast_order)
VALUES (?, ?, ?, ?, ?)
'''
)
self.cursor.execute(query, (actorid, kodiid, mediatype, role, castorder))
castorder += 1
elif "Director" in person_type:
query = (
'''
INSERT OR REPLACE INTO director_link(
actor_id, media_id, media_type)
VALUES (?, ?, ?)
'''
)
self.cursor.execute(query, (actorid, kodiid, mediatype))
elif person_type in ("Writing", "Writer"):
query = (
'''
INSERT OR REPLACE INTO writer_link(
actor_id, media_id, media_type)
VALUES (?, ?, ?)
'''
)
self.cursor.execute(query, (actorid, kodiid, mediatype))
elif "Artist" in person_type:
query = (
'''
INSERT OR REPLACE INTO actor_link(
actor_id, media_id, media_type)
VALUES (?, ?, ?)
'''
)
self.cursor.execute(query, (actorid, kodiid, mediatype))
# Kodi Helix # Kodi Helix
else: else:
query = ' '.join(( query = ' '.join((
@ -372,23 +352,19 @@ class Kodidb_Functions():
"WHERE strActor = ?", "WHERE strActor = ?",
"COLLATE NOCASE" "COLLATE NOCASE"
)) ))
self.cursor.execute(query, (name,)) self.cursor.execute(query, (person['Name'],))
try: try:
actorid = self.cursor.fetchone()[0] actorid = self.cursor.fetchone()[0]
except TypeError: except TypeError:
# Cast entry does not exists # Cast entry does not exists
self.cursor.execute("select coalesce(max(idActor),0) from actors") self.cursor.execute("select coalesce(max(idActor),0) from actors")
actorid = self.cursor.fetchone()[0] + 1 actorid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO actors(idActor, strActor) values(?, ?)" query = "INSERT INTO actors(idActor, strActor) values(?, ?)"
self.cursor.execute(query, (actorid, name)) self.cursor.execute(query, (actorid, person['Name']))
log.debug("Add people to media, processing: %s" % name)
finally: finally:
# Link person to content # Link person to content
if "Actor" in person_type: if "Actor" == person['Type']:
role = person.get('Role') role = person.get('Role')
if "movie" in mediatype: if "movie" in mediatype:
@ -418,12 +394,13 @@ class Kodidb_Functions():
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
''' '''
) )
else: return # Item is invalid else:
# Item is invalid
return
self.cursor.execute(query, (actorid, kodiid, role, castorder)) self.cursor.execute(query, (actorid, kodiid, role, castorder))
castorder += 1 castorder += 1
elif "Director" in person_type: elif "Director" == person['Type']:
if "movie" in mediatype: if "movie" in mediatype:
query = ( query = (
''' '''
@ -465,7 +442,7 @@ class Kodidb_Functions():
self.cursor.execute(query, (actorid, kodiid)) self.cursor.execute(query, (actorid, kodiid))
elif person_type in ("Writing", "Writer"): elif person['Type'] == "Writer":
if "movie" in mediatype: if "movie" in mediatype:
query = ( query = (
''' '''
@ -484,29 +461,25 @@ class Kodidb_Functions():
VALUES (?, ?) VALUES (?, ?)
''' '''
) )
else: return # Item is invalid else:
# Item is invalid
return
self.cursor.execute(query, (actorid, kodiid)) self.cursor.execute(query, (actorid, kodiid))
elif "Artist" == person['Type']:
elif "Artist" in person_type:
query = ( query = (
''' '''
INSERT OR REPLACE INTO artistlinkmusicvideo( INSERT OR REPLACE INTO artistlinkmusicvideo(
idArtist, idMVideo) idArtist, idMVideo)
VALUES (?, ?) VALUES (?, ?)
''' '''
) )
self.cursor.execute(query, (actorid, kodiid)) self.cursor.execute(query, (actorid, kodiid))
# Add person image to art table # Add person image to art table
if thumb: if person['imageurl']:
arttype = person_type.lower() self.artwork.addOrUpdateArt(person['imageurl'], actorid,
person['Type'].lower(), "thumb",
if "writing" in arttype: self.cursor)
arttype = "writer"
self.artwork.addOrUpdateArt(thumb, actorid, arttype, "thumb", self.cursor)
def existingArt(self, kodiId, mediaType, refresh=False): def existingArt(self, kodiId, mediaType, refresh=False):
""" """
@ -554,7 +527,7 @@ class Kodidb_Functions():
# Kodi Isengard, Jarvis, Krypton # Kodi Isengard, Jarvis, Krypton
if self.kodiversion in (15, 16, 17): if KODIVERSION > 14:
# Delete current genres for clean slate # Delete current genres for clean slate
query = ' '.join(( query = ' '.join((
@ -667,10 +640,8 @@ class Kodidb_Functions():
self.cursor.execute(query, (idGenre, kodiid)) self.cursor.execute(query, (idGenre, kodiid))
def addStudios(self, kodiid, studios, mediatype): def addStudios(self, kodiid, studios, mediatype):
for studio in studios: for studio in studios:
if KODIVERSION > 14:
if self.kodiversion in (15, 16, 17):
# Kodi Isengard, Jarvis, Krypton # Kodi Isengard, Jarvis, Krypton
query = ' '.join(( query = ' '.join((
@ -989,9 +960,8 @@ class Kodidb_Functions():
"DVDPlayer", 1)) "DVDPlayer", 1))
def addTags(self, kodiid, tags, mediatype): def addTags(self, kodiid, tags, mediatype):
# First, delete any existing tags associated to the id # First, delete any existing tags associated to the id
if self.kodiversion in (15, 16, 17): if KODIVERSION > 14:
# Kodi Isengard, Jarvis, Krypton # Kodi Isengard, Jarvis, Krypton
query = ' '.join(( query = ' '.join((
@ -1016,8 +986,7 @@ class Kodidb_Functions():
self.addTag(kodiid, tag, mediatype) self.addTag(kodiid, tag, mediatype)
def addTag(self, kodiid, tag, mediatype): def addTag(self, kodiid, tag, mediatype):
if KODIVERSION > 14:
if self.kodiversion in (15, 16, 17):
# Kodi Isengard, Jarvis, Krypton # Kodi Isengard, Jarvis, Krypton
query = ' '.join(( query = ' '.join((
@ -1077,9 +1046,8 @@ class Kodidb_Functions():
self.cursor.execute(query, (tag_id, kodiid, mediatype)) self.cursor.execute(query, (tag_id, kodiid, mediatype))
def createTag(self, name): def createTag(self, name):
# This will create and return the tag_id # This will create and return the tag_id
if self.kodiversion in (15, 16, 17): if KODIVERSION > 14:
# Kodi Isengard, Jarvis, Krypton # Kodi Isengard, Jarvis, Krypton
query = ' '.join(( query = ' '.join((
@ -1123,10 +1091,7 @@ class Kodidb_Functions():
return tag_id return tag_id
def updateTag(self, oldtag, newtag, kodiid, mediatype): def updateTag(self, oldtag, newtag, kodiid, mediatype):
if KODIVERSION > 14:
log.debug("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid))
if self.kodiversion in (15, 16, 17):
# Kodi Isengard, Jarvis, Krypton # Kodi Isengard, Jarvis, Krypton
try: try:
query = ' '.join(( query = ' '.join((
@ -1174,8 +1139,7 @@ class Kodidb_Functions():
self.cursor.execute(query, (kodiid, mediatype, oldtag,)) self.cursor.execute(query, (kodiid, mediatype, oldtag,))
def removeTag(self, kodiid, tagname, mediatype): def removeTag(self, kodiid, tagname, mediatype):
if KODIVERSION > 14:
if self.kodiversion in (15, 16, 17):
# Kodi Isengard, Jarvis, Krypton # Kodi Isengard, Jarvis, Krypton
query = ' '.join(( query = ' '.join((
@ -1349,7 +1313,7 @@ class Kodidb_Functions():
# Create the album # Create the album
self.cursor.execute("select coalesce(max(idAlbum),0) from album") self.cursor.execute("select coalesce(max(idAlbum),0) from album")
albumid = self.cursor.fetchone()[0] + 1 albumid = self.cursor.fetchone()[0] + 1
if self.kodiversion in (15, 16, 17): if KODIVERSION > 14:
query = ( query = (
''' '''
INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType) INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType)

View file

@ -13,7 +13,7 @@ import embydb_functions as embydb
import kodidb_functions as kodidb import kodidb_functions as kodidb
import playbackutils as pbutils import playbackutils as pbutils
from utils import window, settings, CatchExceptions, tryDecode, tryEncode from utils import window, settings, CatchExceptions, tryDecode, tryEncode
from PlexFunctions import scrobble from PlexFunctions import scrobble, REMAP_TYPE_FROM_PLEXTYPE
from playlist import Playlist from playlist import Playlist
############################################################################### ###############################################################################
@ -51,8 +51,17 @@ class KodiMonitor(xbmc.Monitor):
items = { items = {
'logLevel': 'plex_logLevel', 'logLevel': 'plex_logLevel',
'enableContext': 'plex_context', 'enableContext': 'plex_context',
'plex_restricteduser': 'plex_restricteduser' 'plex_restricteduser': 'plex_restricteduser',
'dbSyncIndicator': 'dbSyncIndicator',
'remapSMB': 'remapSMB',
'replaceSMB': 'replaceSMB',
} }
# Path replacement
for typus in REMAP_TYPE_FROM_PLEXTYPE.values():
for arg in ('Org', 'New'):
key = 'remapSMB%s%s' % (typus, arg)
items[key] = key
# Reset the window variables from the settings variables
for settings_value, window_value in items.iteritems(): for settings_value, window_value in items.iteritems():
if window(window_value) != settings(settings_value): if window(window_value) != settings(settings_value):
log.debug('PKC settings changed: %s is now %s' log.debug('PKC settings changed: %s is now %s'

View file

@ -15,7 +15,7 @@ from utils import window, settings, getUnixTimestamp, kodiSQL, sourcesXML,\
ThreadMethods, ThreadMethodsAdditionalStop, LogTime, getScreensaver,\ ThreadMethods, ThreadMethodsAdditionalStop, LogTime, getScreensaver,\
setScreensaver, playlistXSP, language as lang, DateToKodi, reset,\ setScreensaver, playlistXSP, language as lang, DateToKodi, reset,\
advancedSettingsXML, getKodiVideoDBPath, tryDecode, deletePlaylists,\ advancedSettingsXML, getKodiVideoDBPath, tryDecode, deletePlaylists,\
deleteNodes, ThreadMethodsAdditionalSuspend deleteNodes, ThreadMethodsAdditionalSuspend, create_actor_db_index
import clientinfo import clientinfo
import downloadutils import downloadutils
import itemtypes import itemtypes
@ -386,12 +386,19 @@ class LibrarySync(Thread):
self.syncThreadNumber = int(settings('syncThreadNumber')) self.syncThreadNumber = int(settings('syncThreadNumber'))
self.installSyncDone = settings('SyncInstallRunDone') == 'true' self.installSyncDone = settings('SyncInstallRunDone') == 'true'
self.showDbSync = settings('dbSyncIndicator') == 'true' window('dbSyncIndicator', value=settings('dbSyncIndicator'))
self.enableMusic = settings('enableMusic') == "true" self.enableMusic = settings('enableMusic') == "true"
self.enableBackgroundSync = settings( self.enableBackgroundSync = settings(
'enableBackgroundSync') == "true" 'enableBackgroundSync') == "true"
self.limitindex = int(settings('limitindex')) self.limitindex = int(settings('limitindex'))
# Init for replacing paths
window('remapSMB', value=settings('remapSMB'))
window('replaceSMB', value=settings('replaceSMB'))
for typus in PF.REMAP_TYPE_FROM_PLEXTYPE.values():
for arg in ('Org', 'New'):
key = 'remapSMB%s%s' % (typus, arg)
window(key, value=settings(key))
# Just in case a time sync goes wrong # Just in case a time sync goes wrong
self.timeoffset = int(settings('kodiplextimeoffset')) self.timeoffset = int(settings('kodiplextimeoffset'))
window('kodiplextimeoffset', value=str(self.timeoffset)) window('kodiplextimeoffset', value=str(self.timeoffset))
@ -407,7 +414,7 @@ class LibrarySync(Thread):
forced: always show popup, even if user setting to off forced: always show popup, even if user setting to off
""" """
if not self.showDbSync: if settings('dbSyncIndicator') != 'true':
if not forced: if not forced:
return return
if icon == "plex": if icon == "plex":
@ -551,6 +558,9 @@ class LibrarySync(Thread):
# content sync: movies, tvshows, musicvideos, music # content sync: movies, tvshows, musicvideos, music
embyconn.close() embyconn.close()
# Create an index for actors to speed up sync
create_actor_db_index()
@LogTime @LogTime
def fullSync(self, repair=False): def fullSync(self, repair=False):
""" """
@ -560,18 +570,32 @@ 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
self.new_items_only = True
log.info('Running fullsync for NEW PMS items with rapair=%s' % repair)
if self._fullSync() is False:
return False
self.new_items_only = False
log.info('Running fullsync for CHANGED PMS items with repair=%s'
% repair)
if self._fullSync() is False:
return False
return True
def _fullSync(self):
xbmc.executebuiltin('InhibitIdleShutdown(true)') xbmc.executebuiltin('InhibitIdleShutdown(true)')
screensaver = getScreensaver() screensaver = getScreensaver()
setScreensaver(value="") setScreensaver(value="")
# Add sources if self.new_items_only is True:
sourcesXML() # Only do the following once for new items
# Add sources
sourcesXML()
# Set views. Abort if unsuccessful # Set views. Abort if unsuccessful
if not self.maintainViews(): if not self.maintainViews():
xbmc.executebuiltin('InhibitIdleShutdown(false)') xbmc.executebuiltin('InhibitIdleShutdown(false)')
setScreensaver(value=screensaver) setScreensaver(value=screensaver)
return False return False
process = { process = {
'movies': self.PlexMovies, 'movies': self.PlexMovies,
@ -583,6 +607,8 @@ class LibrarySync(Thread):
# Do the processing # Do the processing
for itemtype in process: for itemtype in process:
if self.threadStopped(): if self.threadStopped():
xbmc.executebuiltin('InhibitIdleShutdown(false)')
setScreensaver(value=screensaver)
return False return False
if not process[itemtype](): if not process[itemtype]():
xbmc.executebuiltin('InhibitIdleShutdown(false)') xbmc.executebuiltin('InhibitIdleShutdown(false)')
@ -862,14 +888,34 @@ class LibrarySync(Thread):
self.allPlexElementsId APPENDED(!!) dict self.allPlexElementsId APPENDED(!!) dict
= {itemid: checksum} = {itemid: checksum}
""" """
if self.new_items_only is True:
# Only process Plex items that Kodi does not already have in lib
for item in xml:
itemId = item.attrib.get('ratingKey')
if not itemId:
# Skipping items 'title=All episodes' without a 'ratingKey'
continue
self.allPlexElementsId[itemId] = ("K%s%s" %
(itemId, item.attrib.get('updatedAt', '')))
if itemId not in self.allKodiElementsId:
self.updatelist.append({
'itemId': itemId,
'itemType': itemType,
'method': method,
'viewName': viewName,
'viewId': viewId,
'title': item.attrib.get('title', 'Missing Title'),
'mediaType': item.attrib.get('type')
})
return
if self.compare: if self.compare:
# Only process the delta - new or changed items # Only process the delta - new or changed items
for item in xml: for item in xml:
itemId = item.attrib.get('ratingKey') itemId = item.attrib.get('ratingKey')
# Skipping items 'title=All episodes' without a 'ratingKey'
if not itemId: if not itemId:
# Skipping items 'title=All episodes' without a 'ratingKey'
continue continue
title = item.attrib.get('title', 'Missing Title Name')
plex_checksum = ("K%s%s" plex_checksum = ("K%s%s"
% (itemId, item.attrib.get('updatedAt', ''))) % (itemId, item.attrib.get('updatedAt', '')))
self.allPlexElementsId[itemId] = plex_checksum self.allPlexElementsId[itemId] = plex_checksum
@ -883,31 +929,29 @@ class LibrarySync(Thread):
'method': method, 'method': method,
'viewName': viewName, 'viewName': viewName,
'viewId': viewId, 'viewId': viewId,
'title': title, 'title': item.attrib.get('title', 'Missing Title'),
'mediaType': item.attrib.get('type') 'mediaType': item.attrib.get('type')
}) })
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:
itemId = item.attrib.get('ratingKey') itemId = item.attrib.get('ratingKey')
# Skipping items 'title=All episodes' without a 'ratingKey'
if not itemId: if not itemId:
# Skipping items 'title=All episodes' without a 'ratingKey'
continue continue
title = item.attrib.get('title', 'Missing Title Name') self.allPlexElementsId[itemId] = ("K%s%s"
plex_checksum = ("K%s%s" % (itemId, item.attrib.get('updatedAt', '')))
% (itemId, item.attrib.get('updatedAt', '')))
self.allPlexElementsId[itemId] = plex_checksum
self.updatelist.append({ self.updatelist.append({
'itemId': itemId, 'itemId': itemId,
'itemType': itemType, 'itemType': itemType,
'method': method, 'method': method,
'viewName': viewName, 'viewName': viewName,
'viewId': viewId, 'viewId': viewId,
'title': title, 'title': item.attrib.get('title', 'Missing Title'),
'mediaType': item.attrib.get('type') 'mediaType': item.attrib.get('type')
}) })
def GetAndProcessXMLs(self, itemType, showProgress=True): def GetAndProcessXMLs(self, itemType):
""" """
Downloads all XMLs for itemType (e.g. Movies, TV-Shows). Processes them Downloads all XMLs for itemType (e.g. Movies, TV-Shows). Processes them
by then calling itemtypes.<itemType>() by then calling itemtypes.<itemType>()
@ -959,19 +1003,18 @@ class LibrarySync(Thread):
thread.start() thread.start()
threads.append(thread) threads.append(thread)
log.info("Processing thread spawned") log.info("Processing thread spawned")
# Start one thread to show sync progress # Start one thread to show sync progress ONLY for new PMS items
if showProgress: if self.new_items_only is True and window('dbSyncIndicator') == 'true':
if self.showDbSync: dialog = xbmcgui.DialogProgressBG()
dialog = xbmcgui.DialogProgressBG() thread = ThreadedShowSyncInfo(
thread = ThreadedShowSyncInfo( dialog,
dialog, [getMetadataLock, processMetadataLock],
[getMetadataLock, processMetadataLock], itemNumber,
itemNumber, itemType)
itemType) thread.setDaemon(True)
thread.setDaemon(True) thread.start()
thread.start() threads.append(thread)
threads.append(thread) log.info("Kodi Infobox thread spawned")
log.info("Kodi Infobox thread spawned")
# Wait until finished # Wait until finished
getMetadataQueue.join() getMetadataQueue.join()
@ -1349,9 +1392,9 @@ class LibrarySync(Thread):
""" """
typus = message.get('type') typus = message.get('type')
if typus == 'playing': if typus == 'playing':
self.process_playing(message['_children']) self.process_playing(message['PlaySessionStateNotification'])
elif typus == 'timeline': elif typus == 'timeline':
self.process_timeline(message['_children']) self.process_timeline(message['TimelineEntry'])
def multi_delete(self, liste, deleteListe): def multi_delete(self, liste, deleteListe):
""" """

View file

@ -59,8 +59,8 @@ class PlayUtils():
playurl = tryEncode(self.API.getTranscodeVideoPath( playurl = tryEncode(self.API.getTranscodeVideoPath(
'Transcode', 'Transcode',
quality={ quality={
'maxVideoBitrate': self.getBitrate(), 'maxVideoBitrate': self.get_bitrate(),
'videoResolution': self.getResolution(), 'videoResolution': self.get_resolution(),
'videoQuality': '100' 'videoQuality': '100'
})) }))
# Set playmethod property # Set playmethod property
@ -157,34 +157,45 @@ class PlayUtils():
- HEVC codec - HEVC codec
- window variable 'plex_forcetranscode' set to 'true' - window variable 'plex_forcetranscode' set to 'true'
(excepting trailers etc.) (excepting trailers etc.)
- 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() videoCodec = self.API.getVideoCodec()
log.info("videoCodec: %s" % videoCodec) log.info("videoCodec: %s" % videoCodec)
if self.API.getType() in ('clip', 'track'):
log.info('Plex clip or music track, not transcoding')
return False
if window('plex_forcetranscode') == 'true':
log.info('User chose to force-transcode')
return True
if (settings('transcodeHi10P') == 'true' and if (settings('transcodeHi10P') == 'true' and
videoCodec['bitDepth'] == '10'): videoCodec['bitDepth'] == '10'):
log.info('Option to transcode 10bit video content enabled.') log.info('Option to transcode 10bit video content enabled.')
return True return True
codec = videoCodec['videocodec'] codec = videoCodec['videocodec']
if (settings('transcodeHEVC') == 'true' and codec == 'hevc'):
log.info('Option to transcode HEVC video codec enabled.')
return True
if codec is None: if codec is None:
# e.g. trailers. Avoids TypeError with "'h265' in codec" # e.g. trailers. Avoids TypeError with "'h265' in codec"
log.info('No codec from PMS, not transcoding.') log.info('No codec from PMS, not transcoding.')
return False return False
if window('plex_forcetranscode') == 'true': try:
log.info('User chose to force-transcode') bitrate = int(videoCodec['bitrate'])
except (TypeError, ValueError):
log.info('No video bitrate from PMS, not transcoding.')
return False
if bitrate > self.get_max_bitrate():
log.info('Video bitrate of %s is higher than the maximal video'
'bitrate of %s that the user chose. Transcoding'
% (bitrate, self.get_max_bitrate()))
return True return True
try: try:
resolution = int(videoCodec['resolution']) resolution = int(videoCodec['resolution'])
except (TypeError, ValueError): except (TypeError, ValueError):
log.info('No video resolution from PMS, not transcoding.') log.info('No video resolution from PMS, not transcoding.')
return False return False
if 'h265' in codec: if 'h265' in codec or 'hevc' in codec:
if resolution >= self.getH265(): if resolution >= self.getH265():
log.info("Option to transcode h265 enabled. Resolution of " log.info("Option to transcode h265/HEVC enabled. Resolution "
"the media: %s, transcoding limit resolution: %s" "of the media: %s, transcoding limit resolution: %s"
% (str(resolution), str(self.getH265()))) % (str(resolution), str(self.getH265())))
return True return True
return False return False
@ -200,32 +211,47 @@ class PlayUtils():
return False return False
if self.mustTranscode(): if self.mustTranscode():
return False return False
# Verify the bitrate
if not self.isNetworkSufficient():
log.info("The network speed is insufficient to direct stream "
"file. Transcoding")
return False
return True return True
def isNetworkSufficient(self): def get_max_bitrate(self):
"""
Returns True if the network is sufficient (set in file settings)
"""
try:
sourceBitrate = int(self.API.getDataFromPartOrMedia('bitrate'))
except:
log.info('Could not detect source bitrate. It is assumed to be'
'sufficient')
return True
settings = self.getBitrate()
log.info("The add-on settings bitrate is: %s, the video bitrate"
"required is: %s" % (settings, sourceBitrate))
if settings < sourceBitrate:
return False
return True
def getBitrate(self):
# get the addon video quality # get the addon video quality
videoQuality = settings('maxVideoQualities')
bitrate = {
'0': 320,
'1': 720,
'2': 1500,
'3': 2000,
'4': 3000,
'5': 4000,
'6': 8000,
'7': 10000,
'8': 12000,
'9': 20000,
'10': 40000,
'11': 99999999 # deactivated
}
# max bit rate supported by server (max signed 32bit integer)
return bitrate.get(videoQuality, 2147483)
def getH265(self):
"""
Returns the user settings for transcoding h265: boundary resolutions
of 480, 720 or 1080 as an int
OR 9999999 (int) if user chose not to transcode
"""
H265 = {
'0': 99999999,
'1': 480,
'2': 720,
'3': 1080
}
return H265[settings('transcodeH265')]
def get_bitrate(self):
"""
Get the desired transcoding bitrate from the settings
"""
videoQuality = settings('transcoderVideoQualities') videoQuality = settings('transcoderVideoQualities')
bitrate = { bitrate = {
'0': 320, '0': 320,
@ -243,22 +269,10 @@ class PlayUtils():
# max bit rate supported by server (max signed 32bit integer) # max bit rate supported by server (max signed 32bit integer)
return bitrate.get(videoQuality, 2147483) return bitrate.get(videoQuality, 2147483)
def getH265(self): def get_resolution(self):
""" """
Returns the user settings for transcoding h265: boundary resolutions Get the desired transcoding resolutions from the settings
of 480, 720 or 1080 as an int
OR 9999999 (int) if user chose not to transcode
""" """
H265 = {
'0': 9999999,
'1': 480,
'2': 720,
'3': 1080
}
return H265[settings('transcodeH265')]
def getResolution(self):
chosen = settings('transcoderVideoQualities') chosen = settings('transcoderVideoQualities')
res = { res = {
'0': '420x420', '0': '420x420',

View file

@ -91,7 +91,7 @@ class UserClient(threading.Thread):
if self.machineIdentifier is None: if self.machineIdentifier is None:
self.machineIdentifier = '' self.machineIdentifier = ''
settings('plex_machineIdentifier', value=self.machineIdentifier) settings('plex_machineIdentifier', value=self.machineIdentifier)
log.info('Returning active server: %s' % server) log.debug('Returning active server: %s' % server)
return server return server
def getSSLverify(self): def getSSLverify(self):
@ -104,7 +104,7 @@ class UserClient(threading.Thread):
else settings('sslcert') else settings('sslcert')
def setUserPref(self): def setUserPref(self):
log.info('Setting user preferences') log.debug('Setting user preferences')
# Only try to get user avatar if there is a token # Only try to get user avatar if there is a token
if self.currToken: if self.currToken:
url = PlexAPI.PlexAPI().GetUserArtworkURL(self.currUser) url = PlexAPI.PlexAPI().GetUserArtworkURL(self.currUser)
@ -138,7 +138,7 @@ class UserClient(threading.Thread):
lang(33007)) lang(33007))
def loadCurrUser(self, username, userId, usertoken, authenticated=False): def loadCurrUser(self, username, userId, usertoken, authenticated=False):
log.info('Loading current user') log.debug('Loading current user')
doUtils = self.doUtils doUtils = self.doUtils
self.currUserId = userId self.currUserId = userId
@ -148,16 +148,16 @@ class UserClient(threading.Thread):
self.sslcert = self.getSSL() self.sslcert = self.getSSL()
if authenticated is False: if authenticated is False:
log.info('Testing validity of current token') log.debug('Testing validity of current token')
res = PlexAPI.PlexAPI().CheckConnection(self.currServer, res = PlexAPI.PlexAPI().CheckConnection(self.currServer,
token=self.currToken, token=self.currToken,
verifySSL=self.ssl) verifySSL=self.ssl)
if res is False: if res is False:
log.error('Answer from PMS is not as expected. Retrying') # PMS probably offline
return False return False
elif res == 401: elif res == 401:
log.warn('Token is no longer valid') log.error('Token is no longer valid')
return False return 401
elif res >= 400: elif res >= 400:
log.error('Answer from PMS is not as expected. Retrying') log.error('Answer from PMS is not as expected. Retrying')
return False return False
@ -190,23 +190,10 @@ class UserClient(threading.Thread):
settings('username', value=username) settings('username', value=username)
settings('userid', value=userId) settings('userid', value=userId)
settings('accessToken', value=usertoken) settings('accessToken', value=usertoken)
dialog = xbmcgui.Dialog()
if settings('connectMsg') == "true":
if username:
dialog.notification(
heading=addonName,
message="Welcome " + username,
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png")
else:
dialog.notification(
heading=addonName,
message="Welcome",
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png")
return True return True
def authenticate(self): def authenticate(self):
log.info('Authenticating user') log.debug('Authenticating user')
dialog = xbmcgui.Dialog() dialog = xbmcgui.Dialog()
# Give attempts at entering password / selecting user # Give attempts at entering password / selecting user
@ -243,19 +230,22 @@ class UserClient(threading.Thread):
enforceLogin = settings('enforceUserLogin') enforceLogin = settings('enforceUserLogin')
# Found a user in the settings, try to authenticate # Found a user in the settings, try to authenticate
if username and enforceLogin == 'false': if username and enforceLogin == 'false':
log.info('Trying to authenticate with old settings') log.debug('Trying to authenticate with old settings')
if self.loadCurrUser(username, answ = self.loadCurrUser(username,
userId, userId,
usertoken, usertoken,
authenticated=False): authenticated=False)
if answ is True:
# SUCCESS: loaded a user from the settings # SUCCESS: loaded a user from the settings
return True return True
else: elif answ == 401:
# Failed to use the settings - delete them! log.error("User token no longer valid. Sign user out")
log.info("Failed to use settings credentials. Deleting them")
settings('username', value='') settings('username', value='')
settings('userid', value='') settings('userid', value='')
settings('accessToken', value='') settings('accessToken', value='')
else:
log.debug("Could not yet authenticate user")
return False
plx = PlexAPI.PlexAPI() plx = PlexAPI.PlexAPI()
@ -288,7 +278,7 @@ class UserClient(threading.Thread):
return False return False
def resetClient(self): def resetClient(self):
log.info("Reset UserClient authentication.") log.debug("Reset UserClient authentication.")
self.doUtils.stopSession() self.doUtils.stopSession()
window('plex_authenticated', clear=True) window('plex_authenticated', clear=True)
@ -365,7 +355,7 @@ class UserClient(threading.Thread):
# Or retried too many times # Or retried too many times
if server and status != "Stop": if server and status != "Stop":
# Only if there's information found to login # Only if there's information found to login
log.info("Server found: %s" % server) log.debug("Server found: %s" % server)
self.auth = True self.auth = True
# Minimize CPU load # Minimize CPU load

View file

@ -29,6 +29,7 @@ WINDOW = xbmcgui.Window(10000)
ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect') ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1) KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
############################################################################### ###############################################################################
# Main methods # Main methods
@ -227,6 +228,25 @@ def getKodiVideoDBPath():
% dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], ""))) % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")))
return dbPath return dbPath
def create_actor_db_index():
"""
Index the "actors" because we got a TON - speed up SELECT and WHEN
"""
conn = kodiSQL('video')
cursor = conn.cursor()
try:
cursor.execute("""
CREATE UNIQUE INDEX index_name
ON actor (name);
""")
except sqlite3.OperationalError:
# Index already exists
pass
conn.commit()
conn.close()
def getKodiMusicDBPath(): def getKodiMusicDBPath():
dbVersion = { dbVersion = {
@ -402,7 +422,7 @@ def profiling(sortby="cumulative"):
s = StringIO.StringIO() s = StringIO.StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats(sortby) ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats() ps.print_stats()
log.debug(s.getvalue()) log.info(s.getvalue())
return result return result
@ -835,7 +855,8 @@ def LogTime(func):
starttotal = datetime.now() starttotal = datetime.now()
result = func(*args, **kwargs) result = func(*args, **kwargs)
elapsedtotal = datetime.now() - starttotal elapsedtotal = datetime.now() - starttotal
log.debug('It took %s to run the function.' % (elapsedtotal)) log.info('It took %s to run the function %s'
% (elapsedtotal, func.__name__))
return result return result
return wrapper return wrapper

View file

@ -10,7 +10,7 @@ import xbmc
import xbmcvfs import xbmcvfs
from utils import window, settings, language as lang, IfExists, tryDecode, \ from utils import window, settings, language as lang, IfExists, tryDecode, \
tryEncode, indent, normalize_nodes tryEncode, indent, normalize_nodes, KODIVERSION
############################################################################### ###############################################################################
@ -21,9 +21,6 @@ log = logging.getLogger("PLEX."+__name__)
class VideoNodes(object): class VideoNodes(object):
def __init__(self):
self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
def commonRoot(self, order, label, tagname, roottype=1): def commonRoot(self, order, label, tagname, roottype=1):
if roottype == 0: if roottype == 0:
@ -235,7 +232,7 @@ class VideoNodes(object):
# Custom query # Custom query
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=recentepisodes&type=%s&tagname=%s&limit=%s" path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=recentepisodes&type=%s&tagname=%s&limit=%s"
% (viewid, mediatype, tagname, limit)) % (viewid, mediatype, tagname, limit))
elif self.kodiversion == 14 and nodetype == "inprogressepisodes": elif KODIVERSION == 14 and nodetype == "inprogressepisodes":
# Custom query # Custom query
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=inprogressepisodes&limit=%s" % (tagname, limit) path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=inprogressepisodes&limit=%s" % (tagname, limit)
elif nodetype == 'ondeck': elif nodetype == 'ondeck':
@ -252,7 +249,7 @@ class VideoNodes(object):
if mediatype == "photos": if mediatype == "photos":
windowpath = "ActivateWindow(Pictures,%s,return)" % path windowpath = "ActivateWindow(Pictures,%s,return)" % path
else: else:
if self.kodiversion >= 17: if KODIVERSION >= 17:
# Krypton # Krypton
windowpath = "ActivateWindow(Videos,%s,return)" % path windowpath = "ActivateWindow(Videos,%s,return)" % path
else: else:
@ -374,7 +371,7 @@ class VideoNodes(object):
"special://profile/library/video/")) "special://profile/library/video/"))
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname) nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
path = "library://video/plex_%s.xml" % cleantagname path = "library://video/plex_%s.xml" % cleantagname
if self.kodiversion >= 17: if KODIVERSION >= 17:
# Krypton # Krypton
windowpath = "ActivateWindow(Videos,%s,return)" % path windowpath = "ActivateWindow(Videos,%s,return)" % path
else: else:

View file

@ -41,7 +41,11 @@ class WebSocket(threading.Thread):
log.error('Error decoding message from websocket: %s' % ex) log.error('Error decoding message from websocket: %s' % ex)
log.error(message) log.error(message)
return False return False
try:
message = message['NotificationContainer']
except KeyError:
log.error('Could not parse PMS message: %s' % message)
return False
# Triage # Triage
typus = message.get('type') typus = message.get('type')
if typus is None: if typus is None:
@ -139,7 +143,7 @@ class WebSocket(threading.Thread):
log.info("Error connecting") log.info("Error connecting")
self.ws = None self.ws = None
counter += 1 counter += 1
if counter > 10: 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')

View file

@ -49,7 +49,6 @@
<setting id="syncEmptyShows" type="bool" label="30508" default="false" visible="false"/> <setting id="syncEmptyShows" type="bool" label="30508" default="false" visible="false"/>
<setting id="dbSyncIndicator" label="30507" type="bool" default="true" /><!-- show syncing progress --> <setting id="dbSyncIndicator" label="30507" type="bool" default="true" /><!-- show syncing progress -->
<setting type="sep" /> <setting type="sep" />
<setting id="low_powered_device" type="bool" label="30543" default="true" /> <!-- Installation on low-powered device? (e.g. Raspberry Pi) -->
<setting id="syncThreadNumber" type="slider" label="39003" default="10" option="int" range="1,1,20"/><!-- Limit download sync threads (recommended for rpi: 1) --> <setting id="syncThreadNumber" type="slider" label="39003" default="10" option="int" range="1,1,20"/><!-- Limit download sync threads (recommended for rpi: 1) -->
<setting id="limitindex" type="number" label="30515" default="200" option="int" /><!-- Maximum items to request from the server at once --> <setting id="limitindex" type="number" label="30515" default="200" option="int" /><!-- Maximum items to request from the server at once -->
<setting type="lsep" label="39052" /><!-- Background Sync --> <setting type="lsep" label="39052" /><!-- Background Sync -->
@ -68,7 +67,6 @@
<setting id="enableExportSongRating" type="bool" label="30525" default="false" visible="false" /> <setting id="enableExportSongRating" type="bool" label="30525" default="false" visible="false" />
<setting id="kodiplextimeoffset" type="number" label="Time difference in seconds (Koditime - Plextime)" default="0" visible="false" option="int" /> <setting id="kodiplextimeoffset" type="number" label="Time difference in seconds (Koditime - Plextime)" default="0" visible="false" option="int" />
<setting id="enableUpdateSongRating" type="bool" label="30526" default="false" visible="false" /> <setting id="enableUpdateSongRating" type="bool" label="30526" default="false" visible="false" />
<setting id="plex_pathverified" type="bool" default="false" visible="false" /> <!-- If 'false': one single warning message pops up if PKC cannot verify direct paths -->
<setting id="themoviedbAPIKey" type="text" default="ae06df54334aa653354e9a010f4b81cb" visible="false"/> <setting id="themoviedbAPIKey" type="text" default="ae06df54334aa653354e9a010f4b81cb" visible="false"/>
<setting id="FanArtTVAPIKey" type="text" default="639191cb0774661597f28a47e7e2bad5" visible="false"/> <setting id="FanArtTVAPIKey" type="text" default="639191cb0774661597f28a47e7e2bad5" visible="false"/>
</category> </category>
@ -100,10 +98,10 @@
<setting id="resumeJumpBack" type="slider" label="30521" default="10" range="0,1,120" option="int" /> <setting id="resumeJumpBack" type="slider" label="30521" default="10" range="0,1,120" option="int" />
<setting type="sep" /> <setting type="sep" />
<setting id="playType" type="enum" label="30002" values="Direct Play (default)|Direct Stream|Force Transcode" default="0" /> <setting id="playType" type="enum" label="30002" values="Direct Play (default)|Direct Stream|Force Transcode" default="0" />
<setting id="transcoderVideoQualities" type="enum" label="30160" values="420x420, 320Kbps|576x320, 720Kbps|720x480, 1.5Mbps|1024x768, 2Mbps|1280x720, 3Mbps|1280x720, 4Mbps|1920x1080, 8Mbps|1920x1080, 10Mbps|1920x1080, 12Mbps|1920x1080, 20Mbps|1920x1080, 40Mbps" default="10" /> <setting id="transcoderVideoQualities" type="enum" label="30160" values="420x420, 320kbps|576x320, 720kbps|720x480, 1.5Mbps|1024x768, 2Mbps|1280x720, 3Mbps|1280x720, 4Mbps|1920x1080, 8Mbps|1920x1080, 10Mbps|1920x1080, 12Mbps|1920x1080, 20Mbps|1920x1080, 40Mbps" default="10" /><!-- Video Quality if Transcoding necessary -->
<setting id="transcodeH265" type="enum" label="30522" default="0" values="Disabled (default)|480p (and higher)|720p (and higher)|1080p" /> <setting id="maxVideoQualities" type="enum" label="30143" values="320kbps|720kbps|1.5Mbps|2Mbps|3Mbps|4Mbps|8Mbps|10Mbps|12Mbps|20Mbps|40Mbps|deactivated" default="11" /><!-- Always transcode if video bitrate is above -->
<setting id="transcodeH265" type="enum" label="30522" default="0" values="Disabled (default)|480p (and higher)|720p (and higher)|1080p" /><!-- Force transcode h265/HEVC -->
<setting id="transcodeHi10P" type="bool" label="39063" default="false"/> <setting id="transcodeHi10P" type="bool" label="39063" default="false"/>
<setting id="transcodeHEVC" type="bool" label="39065" default="false"/>
<setting id="audioBoost" type="slider" label="39001" default="0" range="0,10,100" option="int"/> <setting id="audioBoost" type="slider" label="39001" default="0" range="0,10,100" option="int"/>
<setting id="subtitleSize" label="39002" type="slider" option="int" range="0,30,300" default="100" /> <setting id="subtitleSize" label="39002" type="slider" option="int" range="0,30,300" default="100" />
<setting id="failedCount" type="number" visible="false" default="0" /> <setting id="failedCount" type="number" visible="false" default="0" />
@ -131,7 +129,6 @@
--> -->
<category label="39073"><!-- Appearance Tweaks --> <category label="39073"><!-- Appearance Tweaks -->
<setting id="connectMsg" type="bool" label="30249" default="true" />
<setting type="lsep" label="39074" /><!-- TV Shows --> <setting type="lsep" label="39074" /><!-- TV Shows -->
<setting id="OnDeckTVextended" type="bool" label="39058" default="true" /><!-- Extend Plex TV Series "On Deck" view to all shows --> <setting id="OnDeckTVextended" type="bool" label="39058" default="true" /><!-- Extend Plex TV Series "On Deck" view to all shows -->
<setting id="OnDeckTvAppendShow" type="bool" label="39047" default="false" /><!--On Deck view: Append show title to episode--> <setting id="OnDeckTvAppendShow" type="bool" label="39047" default="false" /><!--On Deck view: Append show title to episode-->

View file

@ -58,7 +58,6 @@ addonName = 'PlexKodiConnect'
class Service(): class Service():
welcome_msg = True
server_online = True server_online = True
warn_auth = True warn_auth = True
@ -87,8 +86,8 @@ class Service():
log.warn("%s Version: %s" % (addonName, self.clientInfo.getVersion())) log.warn("%s Version: %s" % (addonName, self.clientInfo.getVersion()))
log.warn("Using plugin paths: %s" log.warn("Using plugin paths: %s"
% (settings('useDirectPaths') != "true")) % (settings('useDirectPaths') != "true"))
log.warn("Using a low powered device: %s" log.warn("Number of sync threads: %s"
% settings('low_powered_device')) % settings('syncThreadNumber'))
log.warn("Log Level: %s" % logLevel) log.warn("Log Level: %s" % logLevel)
# Reset window props for profile switch # Reset window props for profile switch
@ -133,14 +132,13 @@ class Service():
# Queue for background sync # Queue for background sync
queue = Queue.Queue() queue = Queue.Queue()
connectMsg = True if settings('connectMsg') == 'true' else False
# Initialize important threads # Initialize important threads
user = userclient.UserClient() user = userclient.UserClient()
ws = wsc.WebSocket(queue) ws = wsc.WebSocket(queue)
library = librarysync.LibrarySync(queue) library = librarysync.LibrarySync(queue)
plx = PlexAPI.PlexAPI() plx = PlexAPI.PlexAPI()
welcome_msg = True
counter = 0 counter = 0
while not monitor.abortRequested(): while not monitor.abortRequested():
@ -163,9 +161,9 @@ class Service():
if not self.kodimonitor_running: if not self.kodimonitor_running:
# Start up events # Start up events
self.warn_auth = True self.warn_auth = True
if connectMsg and self.welcome_msg: if welcome_msg is True:
# Reset authentication warnings # Reset authentication warnings
self.welcome_msg = False welcome_msg = False
xbmcgui.Dialog().notification( xbmcgui.Dialog().notification(
heading=addonName, heading=addonName,
message="%s %s" % (lang(33000), user.currUser), message="%s %s" % (lang(33000), user.currUser),
@ -221,21 +219,22 @@ class Service():
# Server is offline or cannot be reached # Server is offline or cannot be reached
# Alert the user and suppress future warning # Alert the user and suppress future warning
if self.server_online: if self.server_online:
log.error("Server is offline.") self.server_online = False
window('plex_online', value="false") window('plex_online', value="false")
# Suspend threads # Suspend threads
window('suspend_LibraryThread', value='true') window('suspend_LibraryThread', value='true')
xbmcgui.Dialog().notification( log.error("Plex Media Server went offline")
heading=lang(33001), if settings('show_pms_offline') == 'true':
message="%s %s" xbmcgui.Dialog().notification(
% (addonName, lang(33002)), heading=lang(33001),
icon="special://home/addons/plugin.video." message="%s %s"
"plexkodiconnect/icon.png", % (addonName, lang(33002)),
sound=False) icon="special://home/addons/plugin.video."
self.server_online = False "plexkodiconnect/icon.png",
sound=False)
counter += 1 counter += 1
# Periodically check if the IP changed, e.g. per minute # Periodically check if the IP changed, e.g. per minute
if counter > 30: if counter > 20:
counter = 0 counter = 0
setup = initialsetup.InitialSetup() setup = initialsetup.InitialSetup()
tmp = setup.PickPMS() tmp = setup.PickPMS()
@ -250,16 +249,18 @@ class Service():
if monitor.waitForAbort(5): if monitor.waitForAbort(5):
# Abort was requested while waiting. # Abort was requested while waiting.
break break
self.server_online = True
# Alert the user that server is online. # Alert the user that server is online.
xbmcgui.Dialog().notification( if (welcome_msg is False and
heading=addonName, settings('show_pms_offline') == 'true'):
message=lang(33003), xbmcgui.Dialog().notification(
icon="special://home/addons/plugin.video." heading=addonName,
"plexkodiconnect/icon.png", message=lang(33003),
time=5000, icon="special://home/addons/plugin.video."
sound=False) "plexkodiconnect/icon.png",
self.server_online = True time=5000,
log.warn("Server %s is online and ready." % server) sound=False)
log.info("Server %s is online and ready." % server)
window('plex_online', value="true") window('plex_online', value="true")
if window('plex_authenticated') == 'true': if window('plex_authenticated') == 'true':
# Server got offline when we were authenticated. # Server got offline when we were authenticated.
@ -273,7 +274,7 @@ class Service():
break break
if monitor.waitForAbort(2): if monitor.waitForAbort(3):
# Abort was requested while waiting. # Abort was requested while waiting.
break break