Merge Master
This commit is contained in:
parent
db02a001a8
commit
ad8b7c7d90
21 changed files with 473 additions and 410 deletions
88
README.md
88
README.md
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"/>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -126,6 +126,8 @@
|
||||||
<string id="30140"> - Loop Theme Music</string>
|
<string id="30140"> - Loop Theme Music</string>
|
||||||
<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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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 _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):
|
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,12 +1091,9 @@ 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((
|
||||||
|
|
||||||
"UPDATE tag_link",
|
"UPDATE tag_link",
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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-->
|
||||||
|
|
55
service.py
55
service.py
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue