Merge remote-tracking branch 'refs/remotes/croneter/master'
This commit is contained in:
commit
0b1266d8d8
23 changed files with 790 additions and 606 deletions
|
@ -5,6 +5,9 @@ PKC combines the best of Kodi - ultra smooth navigation, beautiful and highly cu
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
### 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!
|
||||||
|
|
||||||
### 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)
|
||||||
|
|
||||||
|
@ -20,9 +23,9 @@ I'm not in any way affiliated with Plex. Thank you very much for a small donatio
|
||||||
|
|
||||||
### IMPORTANT NOTES
|
### IMPORTANT NOTES
|
||||||
|
|
||||||
1. If your 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`
|
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.
|
Don't forget to reboot Kodi after that.
|
||||||
2. If you post logs, your **Plex tokens** might be included. Be sure to double and tripple check for tokens before posting any logs anywhere by searching for `token`
|
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.
|
3. **Compatibility**: PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
|
||||||
|
|
||||||
|
|
||||||
|
@ -107,5 +110,5 @@ The addon is not (and will not be) compatible with the MySQL database replacemen
|
||||||
### Credits
|
### Credits
|
||||||
|
|
||||||
- PlexKodiConnect shamelessly uses pretty much all the code of "Emby for Kodi" by the awesome Emby team (see https://github.com/MediaBrowser/plugin.video.emby). Thanks for sharing guys!!
|
- PlexKodiConnect shamelessly uses pretty much all the code of "Emby for Kodi" by the awesome Emby team (see https://github.com/MediaBrowser/plugin.video.emby). Thanks for sharing guys!!
|
||||||
- Plex Companion ("PlexBMC Helper") and other stuff was adapted from @Hippojay 's great work (see https://github.com/hippojay).
|
- Plex Companion ("PlexBMC Helper") and other stuff were adapted from @Hippojay 's great work (see https://github.com/hippojay).
|
||||||
- The foundation of the Plex API is all iBaa's work (https://github.com/iBaa/PlexConnect).
|
- The foundation of the Plex API is all iBaa's work (https://github.com/iBaa/PlexConnect).
|
||||||
|
|
|
@ -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.3.3"
|
version="1.4.2"
|
||||||
provider-name="croneter">
|
provider-name="croneter">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.1.0"/>
|
<import addon="xbmc.python" version="2.1.0"/>
|
||||||
|
@ -19,10 +19,10 @@
|
||||||
<platform>all</platform>
|
<platform>all</platform>
|
||||||
<language>en</language>
|
<language>en</language>
|
||||||
<license>GNU GENERAL PUBLIC LICENSE. Version 2, June 1991</license>
|
<license>GNU GENERAL PUBLIC LICENSE. Version 2, June 1991</license>
|
||||||
<forum></forum>
|
<forum>https://forums.plex.tv</forum>
|
||||||
<website>https://github.com/croneter/PlexKodiConnect</website>
|
<website>https://github.com/croneter/PlexKodiConnect</website>
|
||||||
<source></source>
|
<source>https://github.com/croneter/PlexKodiConnect</source>
|
||||||
<summary lang="en"></summary>
|
<summary lang="en"></summary>
|
||||||
<description lang="en">Connect Kodi to your Plex Media Server</description>
|
<description lang="en">Connect Kodi to your Plex Media Server. This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases (as this plugin directly changes them). Use at your own risk!</description>
|
||||||
</extension>
|
</extension>
|
||||||
</addon>
|
</addon>
|
|
@ -1,3 +1,69 @@
|
||||||
|
version 1.4.2
|
||||||
|
Make previous version available for everyone
|
||||||
|
|
||||||
|
version 1.4.1 (beta only)
|
||||||
|
- Fix Kodi crashing on low powered devices
|
||||||
|
- Fix movie year for Krypton (reset your Kodi DB!)
|
||||||
|
- Only start downloading art AFTER sync completed
|
||||||
|
- Add warning to addon description
|
||||||
|
- Revert "Don't set-up clips/trailers like other videos"
|
||||||
|
|
||||||
|
version 1.4.0
|
||||||
|
- Compatibility with new DVR component of the Plex Media Server
|
||||||
|
- Speed up sync - download all art in the background. This should especially speed up your initial sync. Remember to let Kodi sit for a while to let it download the artwork
|
||||||
|
- New setting to look for missing artwork (the non-Plex stuff ;-))
|
||||||
|
- Fix caching not working
|
||||||
|
- Ommit DVR status messages from the PMS. This should fix duplicate movies appearing
|
||||||
|
- Fix possible IndexError on deleting items
|
||||||
|
- Fix TypeError for manually entering PMS address
|
||||||
|
- Fix "Opening Stream..." dialog not closing
|
||||||
|
- Try to prevent OperationalError: database is locked
|
||||||
|
- Revert "Download one item at a time"
|
||||||
|
- Remove obsolete import
|
||||||
|
- Compile regex only once
|
||||||
|
- Music sync: Fix ProgrammingError
|
||||||
|
- Don't set-up clips/trailers like other videos (Should fix PKC trying to tell the PMS where we are playing that item)
|
||||||
|
- Fix capitalization
|
||||||
|
- Fix backgroundsync KeyError
|
||||||
|
- Don't double-update playstate of a playing item
|
||||||
|
- Rearrange the PKC settings
|
||||||
|
- Use file settings instead of window settings, should fix some errors on changing the PKC settings
|
||||||
|
- Remove size limitation on sync queue
|
||||||
|
- Fix TypeError if no extras available
|
||||||
|
- Other small fixes
|
||||||
|
|
||||||
|
version 1.3.9 (beta only)
|
||||||
|
- Hopefully maximum compatibility with the new DVR component of the Plex Media Server :-)
|
||||||
|
- Ommit DVR status messages from the PMS. This should fix duplicate movies appearing
|
||||||
|
- Fix possible IndexError on deleting items
|
||||||
|
|
||||||
|
version 1.3.8 (beta only)
|
||||||
|
- Fix TypeError for manually entering PMS address
|
||||||
|
- Fix "Opening Stream..." dialog not closing
|
||||||
|
- Try to prevent OperationalError: database is locked
|
||||||
|
- Revert "Download one item at a time"
|
||||||
|
- Remove obsolete import
|
||||||
|
- Compile regex only once
|
||||||
|
|
||||||
|
version 1.3.7 (beta only)
|
||||||
|
- Music sync: Fix ProgrammingError
|
||||||
|
- Don't set-up clips/trailers like other videos (Should fix PKC trying to tell the PMS where we are playing that item)
|
||||||
|
|
||||||
|
version 1.3.6 (beta only)
|
||||||
|
- Fix capitalization
|
||||||
|
|
||||||
|
version 1.3.5 (beta only)
|
||||||
|
- Fix backgroundsync KeyError
|
||||||
|
- Don't double-update playstate of a playing item
|
||||||
|
|
||||||
|
version 1.3.4 (beta only)
|
||||||
|
- Speed up sync - download all art in the background. This should especially speed up your initial sync. Remember to let Kodi sit for a while to let it download the artwork
|
||||||
|
- New setting to look for missing artwork (the non-Plex stuff ;-))
|
||||||
|
- Rearrange the PKC settings
|
||||||
|
- Fix caching not working
|
||||||
|
- Use file settings instead of window settings, should fix some errors on changing the PKC settings
|
||||||
|
- Other small fixes
|
||||||
|
|
||||||
version 1.3.3
|
version 1.3.3
|
||||||
- 1.3.1 and 1.3.2 for everyone
|
- 1.3.1 and 1.3.2 for everyone
|
||||||
- Fix direct play & transcoding subtitles, finally!
|
- Fix direct play & transcoding subtitles, finally!
|
||||||
|
|
17
default.py
17
default.py
|
@ -10,6 +10,7 @@ import urlparse
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
|
import xbmcplugin
|
||||||
|
|
||||||
|
|
||||||
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||||
|
@ -99,6 +100,10 @@ class Main():
|
||||||
entrypoint.getVideoFiles(plexid, plexpath)
|
entrypoint.getVideoFiles(plexid, plexpath)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if mode == 'fanart':
|
||||||
|
log.info('User requested fanarttv refresh')
|
||||||
|
utils.window('plex_runLibScan', value='fanart')
|
||||||
|
|
||||||
# Called by e.g. 3rd party plugin video extras
|
# Called by e.g. 3rd party plugin video extras
|
||||||
if ("/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0] or
|
if ("/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0] or
|
||||||
"/Extras" in sys.argv[2]):
|
"/Extras" in sys.argv[2]):
|
||||||
|
@ -156,22 +161,20 @@ class Main():
|
||||||
"Unable to run the sync, the add-on is not connected "
|
"Unable to run the sync, the add-on is not connected "
|
||||||
"to a Plex server.")
|
"to a Plex server.")
|
||||||
log.error("Not connected to a PMS.")
|
log.error("Not connected to a PMS.")
|
||||||
return
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if mode == 'repair':
|
if mode == 'repair':
|
||||||
utils.window('plex_runLibScan', value="repair")
|
utils.window('plex_runLibScan', value="repair")
|
||||||
log.warn("Requesting repair lib sync")
|
log.info("Requesting repair lib sync")
|
||||||
elif mode == 'manualsync':
|
elif mode == 'manualsync':
|
||||||
log.warn("Requesting full library scan")
|
log.info("Requesting full library scan")
|
||||||
utils.window('plex_runLibScan', value="full")
|
utils.window('plex_runLibScan', value="full")
|
||||||
|
|
||||||
elif mode == "texturecache":
|
elif mode == "texturecache":
|
||||||
import artwork
|
utils.window('plex_runLibScan', value='del_textures')
|
||||||
artwork.Artwork().fullTextureCacheSync()
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
entrypoint.doMainListing()
|
entrypoint.doMainListing()
|
||||||
|
# Prevent Kodi from hanging in an infinite loop waiting for more
|
||||||
|
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
log.info('plugin.video.plexkodiconnect started')
|
log.info('plugin.video.plexkodiconnect started')
|
||||||
|
|
|
@ -302,11 +302,12 @@
|
||||||
<string id="30536">Users must log in every time Kodi restarts</string>
|
<string id="30536">Users must log in every time Kodi restarts</string>
|
||||||
<string id="30537">RESTART KODI IF YOU MAKE ANY CHANGES</string>
|
<string id="30537">RESTART KODI IF YOU MAKE ANY CHANGES</string>
|
||||||
<string id="30538">Complete Re-Sync necessary</string>
|
<string id="30538">Complete Re-Sync necessary</string>
|
||||||
<string id="30539">Download additional art from FanArtTV (slower!)</string>
|
<string id="30539">Download additional art from FanArtTV</string>
|
||||||
<string id="30540">Download movie set/collection art from FanArtTV</string>
|
<string id="30540">Download movie set/collection art from FanArtTV</string>
|
||||||
<string id="30541">Don't ask to pick a certain stream/quality</string>
|
<string id="30541">Don't ask to pick a certain stream/quality</string>
|
||||||
<string id="30542">Always pick best quality for trailers</string>
|
<string id="30542">Always pick best quality for trailers</string>
|
||||||
|
<string id="30543">Kodi runs on a low-power device (e.g. Raspberry Pi)</string>
|
||||||
|
<string id="30544">Artwork</string>
|
||||||
|
|
||||||
<!-- service add-on -->
|
<!-- service add-on -->
|
||||||
<string id="33000">Welcome</string>
|
<string id="33000">Welcome</string>
|
||||||
|
@ -348,12 +349,12 @@
|
||||||
<string id="39000">- Number of trailers to play before a movie</string>
|
<string id="39000">- Number of trailers to play before a movie</string>
|
||||||
<string id="39001">Boost audio when transcoding</string>
|
<string id="39001">Boost audio when transcoding</string>
|
||||||
<string id="39002">Burnt-in subtitle size</string>
|
<string id="39002">Burnt-in subtitle size</string>
|
||||||
<string id="39003">Limit download sync threads (recommended for rpi: 1)</string>
|
<string id="39003">Limit download sync threads (rec. for rpi: 1)</string>
|
||||||
<string id="39004">Enable Plex Companion</string>
|
<string id="39004">Enable Plex Companion</string>
|
||||||
<string id="39005">Plex Companion Port (change only if needed)</string>
|
<string id="39005">Plex Companion Port (change only if needed)</string>
|
||||||
<string id="39006">Activate Plex Companion debug log</string>
|
<string id="39006">Activate Plex Companion debug log</string>
|
||||||
<string id="39007">Activate Plex Companion GDM debug log</string>
|
<string id="39007">Activate Plex Companion GDM debug log</string>
|
||||||
<string id="39008">Allows flinging media to Kodi through Plex</string>
|
<string id="39008">Plex Companion: Allows flinging media to Kodi through Plex</string>
|
||||||
<string id="39009">Could not login to plex.tv. Please try signing in again.</string>
|
<string id="39009">Could not login to plex.tv. Please try signing in again.</string>
|
||||||
<string id="39010">Problems connecting to plex.tv. Network or internet issue?</string>
|
<string id="39010">Problems connecting to plex.tv. Network or internet issue?</string>
|
||||||
<string id="39011">Could not find any Plex server in the network. Aborting...</string>
|
<string id="39011">Could not find any Plex server in the network. Aborting...</string>
|
||||||
|
@ -366,7 +367,7 @@
|
||||||
|
|
||||||
<string id="39018">[COLOR yellow]Repair local database (force update all content)[/COLOR]</string>
|
<string id="39018">[COLOR yellow]Repair local database (force update all content)[/COLOR]</string>
|
||||||
<string id="39019">[COLOR red]Partial or full reset of Database and PKC[/COLOR]</string>
|
<string id="39019">[COLOR red]Partial or full reset of Database and PKC[/COLOR]</string>
|
||||||
<string id="39020">[COLOR yellow]Cache all images to Kodi texture cache[/COLOR]</string>
|
<string id="39020">[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]</string>
|
||||||
<string id="39021">[COLOR yellow]Sync Emby Theme Media to Kodi[/COLOR]</string>
|
<string id="39021">[COLOR yellow]Sync Emby Theme Media to Kodi[/COLOR]</string>
|
||||||
<string id="39022">local</string>
|
<string id="39022">local</string>
|
||||||
<string id="39023">Failed to authenticate. Did you login to plex.tv?</string>
|
<string id="39023">Failed to authenticate. Did you login to plex.tv?</string>
|
||||||
|
@ -408,7 +409,7 @@
|
||||||
<string id="39058">Extend Plex TV Series "On Deck" view to all shows</string>
|
<string id="39058">Extend Plex TV Series "On Deck" view to all shows</string>
|
||||||
<string id="39059">Recently Added: Append show title to episode</string>
|
<string id="39059">Recently Added: Append show title to episode</string>
|
||||||
<string id="39060">Recently Added: Append season- and episode-number SxxExx</string>
|
<string id="39060">Recently Added: Append season- and episode-number SxxExx</string>
|
||||||
<string id="39061">Would you like to download additional artwork from FanArtTV? Sync will be slower!</string>
|
<string id="39061">Would you like to download additional artwork from FanArtTV in the background?</string>
|
||||||
<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>
|
||||||
|
@ -446,10 +447,14 @@
|
||||||
<string id="39219">Abort (Yes) or save address anyway (No)?</string>
|
<string id="39219">Abort (Yes) or save address anyway (No)?</string>
|
||||||
<string id="39220">connected</string>
|
<string id="39220">connected</string>
|
||||||
<string id="39221">plex.tv toggle successful</string>
|
<string id="39221">plex.tv toggle successful</string>
|
||||||
|
<string id="39222">[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]</string>
|
||||||
|
<string id="39223">Only look for missing fanart or refresh all fanart? The scan will take quite a while and happen in the background.</string>
|
||||||
|
<string id="39224">Refresh all</string>
|
||||||
|
<string id="39225">Missing only</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Plex Artwork.py -->
|
<!-- Plex Artwork.py -->
|
||||||
<string id="39250">Running the image cache process can take some time. Are you sure you want continue?</string>
|
<string id="39250">Running the image cache process can take some time. It will happen in the background. Are you sure you want continue?</string>
|
||||||
<string id="39251">Reset all existing cache data first?</string>
|
<string id="39251">Reset all existing cache data first?</string>
|
||||||
|
|
||||||
<!-- Plex PlexAPI.py -->
|
<!-- Plex PlexAPI.py -->
|
||||||
|
|
|
@ -28,10 +28,12 @@
|
||||||
<string id="30536">Benutzer müssen sich bei jedem Neustart von Kodi neu anmelden</string>
|
<string id="30536">Benutzer müssen sich bei jedem Neustart von Kodi neu anmelden</string>
|
||||||
<string id="30537">BEI ÄNDERUNGEN KODI NEU STARTEN</string>
|
<string id="30537">BEI ÄNDERUNGEN KODI NEU STARTEN</string>
|
||||||
<string id="30538">Komplette Neusynchronisierung nötig</string>
|
<string id="30538">Komplette Neusynchronisierung nötig</string>
|
||||||
<string id="30539">Zusätzliche Bilder von FanArtTV herunterladen (langsamer!)</string>
|
<string id="30539">Zusätzliche Bilder von FanArtTV herunterladen</string>
|
||||||
<string id="30540">FanArtTV Film-Sets/Collections Bilder herunterladen</string>
|
<string id="30540">FanArtTV Film-Sets/Collections Bilder herunterladen</string>
|
||||||
<string id="30541">Nicht fragen, welcher Stream/Qualität gespielt wird</string>
|
<string id="30541">Nicht fragen, welcher Stream/Qualität gespielt wird</string>
|
||||||
<string id="30542">Trailer immer in der besten Qualität abspielen</string>
|
<string id="30542">Trailer immer in der besten Qualität abspielen</string>
|
||||||
|
<string id="30543">Kodi läuft auf langsamer Hardware (z.B. Raspberry Pi)</string>
|
||||||
|
<string id="30544">Artwork</string>
|
||||||
|
|
||||||
<string id="30014">Verbindung</string>
|
<string id="30014">Verbindung</string>
|
||||||
<string id="30015">Netzwerk</string>
|
<string id="30015">Netzwerk</string>
|
||||||
|
@ -286,12 +288,12 @@
|
||||||
<string id="39000">- Anzahl abzuspielender Trailer vor einem Film</string>
|
<string id="39000">- Anzahl abzuspielender Trailer vor einem Film</string>
|
||||||
<string id="39001">Audio Verstärkung (audio boost) wenn transkodiert wird</string>
|
<string id="39001">Audio Verstärkung (audio boost) wenn transkodiert wird</string>
|
||||||
<string id="39002">Grösse des Untertitels, falls burnt-in</string>
|
<string id="39002">Grösse des Untertitels, falls burnt-in</string>
|
||||||
<string id="39003">Anzahl Download Sync Threads beschränken (Empfehlung für rpi: 1)</string>
|
<string id="39003">Download Sync Threads beschränken (Empfehlung RPI: 1)</string>
|
||||||
<string id="39004">Plex Companion aktivieren</string>
|
<string id="39004">Plex Companion aktivieren</string>
|
||||||
<string id="39005">Plex Companion Port (nur bei Bedarf ändern)</string>
|
<string id="39005">Plex Companion Port (nur bei Bedarf ändern)</string>
|
||||||
<string id="39006">Plex Companion debug log aktivieren</string>
|
<string id="39006">Plex Companion debug log aktivieren</string>
|
||||||
<string id="39007">Plex Companion GDM debug log aktivieren</string>
|
<string id="39007">Plex Companion GDM debug log aktivieren</string>
|
||||||
<string id="39008">Spiele Inhalt von anderen Plex Geräten ab</string>
|
<string id="39008">Plex Companion: Spiele Inhalt von anderen Plex Geräten ab</string>
|
||||||
<string id="39009">Login bei plex.tv fehlgeschlagen. Bitte erneut versuchen.</string>
|
<string id="39009">Login bei plex.tv fehlgeschlagen. Bitte erneut versuchen.</string>
|
||||||
<string id="39010">Netzwerk Verbindungsprobleme für plex.tv. Funktionieren Netzwerk und/oder Internet?</string>
|
<string id="39010">Netzwerk Verbindungsprobleme für plex.tv. Funktionieren Netzwerk und/oder Internet?</string>
|
||||||
<string id="39011">Konnte keine Plex Server im Netzwerk finden. Abbruch...</string>
|
<string id="39011">Konnte keine Plex Server im Netzwerk finden. Abbruch...</string>
|
||||||
|
@ -304,7 +306,7 @@
|
||||||
|
|
||||||
<string id="39018">[COLOR yellow]Lokale Datenbank reparieren (allen Inhalt aktualisieren)[/COLOR]</string>
|
<string id="39018">[COLOR yellow]Lokale Datenbank reparieren (allen Inhalt aktualisieren)[/COLOR]</string>
|
||||||
<string id="39019">[COLOR red]Datenbank und auf Wunsch PKC zurücksetzen[/COLOR]</string>
|
<string id="39019">[COLOR red]Datenbank und auf Wunsch PKC zurücksetzen[/COLOR]</string>
|
||||||
<string id="39020">[COLOR yellow]Alle Plex Bilder in Kodi zwischenspeichern[/COLOR]</string>
|
<string id="39020">[COLOR yellow]Alle Plex Bilder jetzt in Kodi zwischenspeichern[/COLOR]</string>
|
||||||
<string id="39021">[COLOR yellow]Plex Themes zu Kodi synchronisieren[/COLOR]</string>
|
<string id="39021">[COLOR yellow]Plex Themes zu Kodi synchronisieren[/COLOR]</string>
|
||||||
<string id="39022">lokal</string>
|
<string id="39022">lokal</string>
|
||||||
<string id="39023">Plex Media Server Authentifizierung fehlgeschlagen. Haben Sie sich bei plex.tv eingeloggt?</string>
|
<string id="39023">Plex Media Server Authentifizierung fehlgeschlagen. Haben Sie sich bei plex.tv eingeloggt?</string>
|
||||||
|
@ -346,7 +348,7 @@
|
||||||
<string id="39058">Standard Plex Ansicht "Aktuell" auf alle TV Shows erweitern</string>
|
<string id="39058">Standard Plex Ansicht "Aktuell" auf alle TV Shows erweitern</string>
|
||||||
<string id="39059">"Zuletzt hinzugefügt": Serien- an Episoden-Titel anfügen</string>
|
<string id="39059">"Zuletzt hinzugefügt": Serien- an Episoden-Titel anfügen</string>
|
||||||
<string id="39060">"Zuletzt hinzugefügt": Staffel und Episode anfügen, SxxExx</string>
|
<string id="39060">"Zuletzt hinzugefügt": Staffel und Episode anfügen, SxxExx</string>
|
||||||
<string id="39061">Zusätzliche Bilder von FanArtTV herunterladen? Die Synchronisierung wird länger dauern!</string>
|
<string id="39061">Zusätzliche Bilder von FanArtTV im Hintergrund herunterladen?</string>
|
||||||
<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>
|
||||||
|
@ -384,9 +386,14 @@
|
||||||
<string id="39219">Abbrechen (Ja) oder PMS Adresse trotzdem speichern (Nein)?</string>
|
<string id="39219">Abbrechen (Ja) oder PMS Adresse trotzdem speichern (Nein)?</string>
|
||||||
<string id="39220">verbunden</string>
|
<string id="39220">verbunden</string>
|
||||||
<string id="39221">plex.tv wechsel OK</string>
|
<string id="39221">plex.tv wechsel OK</string>
|
||||||
|
<string id="39222">[COLOR yellow]Jetzt zusätzliche Bilder auf FanartTV suchen[/COLOR]</string>
|
||||||
|
<string id="39223">Nur nach fehlender Fanart suchen oder alle Fanart neu herunterladen? Die Suche wird lange dauern und komplett im Hintergrund stattfinden!</string>
|
||||||
|
<string id="39224">Alle</string>
|
||||||
|
<string id="39225">Fehlend</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Plex Artwork.py -->
|
<!-- Plex Artwork.py -->
|
||||||
<string id="39250">Alle Plex Bilder in Kodi zwischenzuspeichern kann sehr lange dauern. Möchten Sie wirklich fortfahren?</string>
|
<string id="39250">Alle Plex Bilder in Kodi zwischenzuspeichern kann lange dauern. Es wird im Hintergrund stattfinden. Möchten Sie wirklich fortfahren?</string>
|
||||||
<string id="39251">Sollen erst alle bestehenden Bilder im Cache gelöscht werden?</string>
|
<string id="39251">Sollen erst alle bestehenden Bilder im Cache gelöscht werden?</string>
|
||||||
|
|
||||||
<!-- Plex PlexAPI.py -->
|
<!-- Plex PlexAPI.py -->
|
||||||
|
|
|
@ -57,7 +57,8 @@ import embydb_functions as embydb
|
||||||
log = logging.getLogger("PLEX."+__name__)
|
log = logging.getLogger("PLEX."+__name__)
|
||||||
|
|
||||||
addonName = 'PlexKodiConnect'
|
addonName = 'PlexKodiConnect'
|
||||||
|
REGEX_IMDB = re.compile(r'''/(tt\d+)''')
|
||||||
|
REGEX_TVDB = re.compile(r'''tvdb://(\d+)''')
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
@ -620,10 +621,10 @@ class PlexAPI():
|
||||||
args=(PMS, queue))
|
args=(PMS, queue))
|
||||||
threadQueue.append(t)
|
threadQueue.append(t)
|
||||||
|
|
||||||
maxThreads = int(settings('imageCacheLimit'))
|
maxThreads = 5
|
||||||
threads = []
|
threads = []
|
||||||
# poke PMS, own thread for each PMS
|
# poke PMS, own thread for each PMS
|
||||||
while(True):
|
while True:
|
||||||
# Remove finished threads
|
# Remove finished threads
|
||||||
for t in threads:
|
for t in threads:
|
||||||
if not t.isAlive():
|
if not t.isAlive():
|
||||||
|
@ -1450,10 +1451,10 @@ class API():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if providername == 'imdb':
|
if providername == 'imdb':
|
||||||
regex = re.compile(r'''/(tt\d+)''')
|
regex = REGEX_IMDB
|
||||||
elif providername == 'tvdb':
|
elif providername == 'tvdb':
|
||||||
# originally e.g. com.plexapp.agents.thetvdb://276564?lang=en
|
# originally e.g. com.plexapp.agents.thetvdb://276564?lang=en
|
||||||
regex = re.compile(r'''tvdb://(\d+)''')
|
regex = REGEX_TVDB
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -1720,7 +1721,7 @@ class API():
|
||||||
'year':
|
'year':
|
||||||
"""
|
"""
|
||||||
elements = []
|
elements = []
|
||||||
for extra in self.item.find('Extras'):
|
for extra in self.item.findall('Extras'):
|
||||||
# Trailer:
|
# Trailer:
|
||||||
key = extra.attrib.get('key', None)
|
key = extra.attrib.get('key', None)
|
||||||
title = extra.attrib.get('title', None)
|
title = extra.attrib.get('title', None)
|
||||||
|
@ -1841,16 +1842,14 @@ class API():
|
||||||
'Backdrop' : LIST with the first entry xml key "art"
|
'Backdrop' : LIST with the first entry xml key "art"
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
item = self.item.attrib
|
|
||||||
|
|
||||||
allartworks = {
|
allartworks = {
|
||||||
'Primary': "",
|
'Primary': "", # corresponds to Plex poster ('thumb')
|
||||||
'Art': "",
|
'Art': "",
|
||||||
'Banner': "",
|
'Banner': "", # corresponds to Plex banner ('banner') for series
|
||||||
'Logo': "",
|
'Logo': "",
|
||||||
'Thumb': "",
|
'Thumb': "", # corresponds to Plex (grand)parent posters (thumb)
|
||||||
'Disc': "",
|
'Disc': "",
|
||||||
'Backdrop': []
|
'Backdrop': [] # Corresponds to Plex fanart ('art')
|
||||||
}
|
}
|
||||||
# Process backdrops
|
# Process backdrops
|
||||||
# Get background artwork URL
|
# Get background artwork URL
|
||||||
|
@ -1870,11 +1869,23 @@ class API():
|
||||||
self.__getOneArtwork('parentArt'))
|
self.__getOneArtwork('parentArt'))
|
||||||
if not allartworks['Primary']:
|
if not allartworks['Primary']:
|
||||||
allartworks['Primary'] = self.__getOneArtwork('parentThumb')
|
allartworks['Primary'] = self.__getOneArtwork('parentThumb')
|
||||||
|
return allartworks
|
||||||
|
|
||||||
# Plex does not get much artwork - go ahead and get the rest from
|
def getFanartArtwork(self, allartworks, parentInfo=False):
|
||||||
# fanart tv only for movie or tv show
|
"""
|
||||||
if settings('FanartTV') == 'true':
|
Downloads additional fanart from third party sources (well, link to
|
||||||
if item.get('type') in ('movie', 'show'):
|
fanart only).
|
||||||
|
|
||||||
|
allartworks = {
|
||||||
|
'Primary': "",
|
||||||
|
'Art': "",
|
||||||
|
'Banner': "",
|
||||||
|
'Logo': "",
|
||||||
|
'Thumb': "",
|
||||||
|
'Disc': "",
|
||||||
|
'Backdrop': []
|
||||||
|
}
|
||||||
|
"""
|
||||||
externalId = self.getExternalItemId()
|
externalId = self.getExternalItemId()
|
||||||
if externalId is not None:
|
if externalId is not None:
|
||||||
allartworks = self.getFanartTVArt(externalId, allartworks)
|
allartworks = self.getFanartTVArt(externalId, allartworks)
|
||||||
|
@ -2505,16 +2516,16 @@ class API():
|
||||||
'photo': 'photo'
|
'photo': 'photo'
|
||||||
}
|
}
|
||||||
typus = types[typus]
|
typus = types[typus]
|
||||||
if window('remapSMB') == 'true':
|
if settings('remapSMB') == 'true':
|
||||||
path = path.replace(window('remapSMB%sOrg' % typus),
|
path = path.replace(settings('remapSMB%sOrg' % typus),
|
||||||
window('remapSMB%sNew' % 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 window('replaceSMB') == 'true':
|
elif settings('replaceSMB') == 'true':
|
||||||
if path.startswith('\\\\'):
|
if path.startswith('\\\\'):
|
||||||
path = 'smb:' + path.replace('\\', '/')
|
path = 'smb:' + path.replace('\\', '/')
|
||||||
if window('plex_pathverified') == 'true' and forceCheck is False:
|
if settings('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
|
||||||
|
@ -2535,13 +2546,11 @@ class API():
|
||||||
if self.askToValidate(path):
|
if self.askToValidate(path):
|
||||||
window('plex_shouldStop', value="true")
|
window('plex_shouldStop', value="true")
|
||||||
path = None
|
path = None
|
||||||
window('plex_pathverified', value='true')
|
|
||||||
settings('plex_pathverified', value='true')
|
settings('plex_pathverified', value='true')
|
||||||
else:
|
else:
|
||||||
path = None
|
path = None
|
||||||
elif forceCheck is False:
|
elif forceCheck is False:
|
||||||
if window('plex_pathverified') != 'true':
|
if settings('plex_pathverified') != 'true':
|
||||||
window('plex_pathverified', value='true')
|
|
||||||
settings('plex_pathverified', value='true')
|
settings('plex_pathverified', value='true')
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
import urllib
|
from urllib import quote_plus, unquote
|
||||||
from sqlite3 import OperationalError
|
from threading import Thread
|
||||||
|
import Queue
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
|
|
||||||
import image_cache_thread
|
from utils import window, settings, language as lang, kodiSQL, tryEncode, \
|
||||||
from utils import window, settings, language as lang, kodiSQL
|
tryDecode, IfExists, ThreadMethods, ThreadMethodsAdditionalStop
|
||||||
from utils import tryEncode, tryDecode, IfExists
|
|
||||||
|
|
||||||
# Disable annoying requests warnings
|
# Disable annoying requests warnings
|
||||||
import requests.packages.urllib3
|
import requests.packages.urllib3
|
||||||
|
@ -27,51 +26,18 @@ log = logging.getLogger("PLEX."+__name__)
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
class Artwork():
|
def setKodiWebServerDetails():
|
||||||
xbmc_host = 'localhost'
|
"""
|
||||||
|
Get the Kodi webserver details - used to set the texture cache
|
||||||
|
"""
|
||||||
xbmc_port = None
|
xbmc_port = None
|
||||||
xbmc_username = None
|
xbmc_username = None
|
||||||
xbmc_password = None
|
xbmc_password = None
|
||||||
|
|
||||||
imageCacheThreads = []
|
|
||||||
imageCacheLimitThreads = 0
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
self.enableTextureCache = settings('enableTextureCache') == "true"
|
|
||||||
self.imageCacheLimitThreads = int(settings('imageCacheLimit'))
|
|
||||||
self.imageCacheLimitThreads = int(self.imageCacheLimitThreads * 5)
|
|
||||||
log.info("Using Image Cache Thread Count: %s" % self.imageCacheLimitThreads)
|
|
||||||
|
|
||||||
if not self.xbmc_port and self.enableTextureCache:
|
|
||||||
self.setKodiWebServerDetails()
|
|
||||||
|
|
||||||
self.userId = window('currUserId')
|
|
||||||
self.server = window('pms_server')
|
|
||||||
|
|
||||||
def double_urlencode(self, text):
|
|
||||||
text = self.single_urlencode(text)
|
|
||||||
text = self.single_urlencode(text)
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
def single_urlencode(self, text):
|
|
||||||
|
|
||||||
text = urllib.urlencode({'blahblahblah': tryEncode(text)}) #urlencode needs a utf- string
|
|
||||||
text = text[13:]
|
|
||||||
|
|
||||||
return tryDecode(text) #return the result again as unicode
|
|
||||||
|
|
||||||
def setKodiWebServerDetails(self):
|
|
||||||
# Get the Kodi webserver details - used to set the texture cache
|
|
||||||
web_query = {
|
web_query = {
|
||||||
|
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"method": "Settings.GetSettingValue",
|
"method": "Settings.GetSettingValue",
|
||||||
"params": {
|
"params": {
|
||||||
|
|
||||||
"setting": "services.webserver"
|
"setting": "services.webserver"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,108 +47,189 @@ class Artwork():
|
||||||
xbmc_webserver_enabled = result['result']['value']
|
xbmc_webserver_enabled = result['result']['value']
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
xbmc_webserver_enabled = False
|
xbmc_webserver_enabled = False
|
||||||
|
|
||||||
if not xbmc_webserver_enabled:
|
if not xbmc_webserver_enabled:
|
||||||
# Enable the webserver, it is disabled
|
# Enable the webserver, it is disabled
|
||||||
web_port = {
|
web_port = {
|
||||||
|
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"method": "Settings.SetSettingValue",
|
"method": "Settings.SetSettingValue",
|
||||||
"params": {
|
"params": {
|
||||||
|
|
||||||
"setting": "services.webserverport",
|
"setting": "services.webserverport",
|
||||||
"value": 8080
|
"value": 8080
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = xbmc.executeJSONRPC(json.dumps(web_port))
|
result = xbmc.executeJSONRPC(json.dumps(web_port))
|
||||||
self.xbmc_port = 8080
|
xbmc_port = 8080
|
||||||
|
|
||||||
web_user = {
|
web_user = {
|
||||||
|
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"method": "Settings.SetSettingValue",
|
"method": "Settings.SetSettingValue",
|
||||||
"params": {
|
"params": {
|
||||||
|
|
||||||
"setting": "services.webserver",
|
"setting": "services.webserver",
|
||||||
"value": True
|
"value": True
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = xbmc.executeJSONRPC(json.dumps(web_user))
|
result = xbmc.executeJSONRPC(json.dumps(web_user))
|
||||||
self.xbmc_username = "kodi"
|
xbmc_username = "kodi"
|
||||||
|
|
||||||
|
|
||||||
# Webserver already enabled
|
# Webserver already enabled
|
||||||
web_port = {
|
web_port = {
|
||||||
|
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"method": "Settings.GetSettingValue",
|
"method": "Settings.GetSettingValue",
|
||||||
"params": {
|
"params": {
|
||||||
|
|
||||||
"setting": "services.webserverport"
|
"setting": "services.webserverport"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = xbmc.executeJSONRPC(json.dumps(web_port))
|
result = xbmc.executeJSONRPC(json.dumps(web_port))
|
||||||
result = json.loads(result)
|
result = json.loads(result)
|
||||||
try:
|
try:
|
||||||
self.xbmc_port = result['result']['value']
|
xbmc_port = result['result']['value']
|
||||||
except (TypeError, KeyError):
|
except (TypeError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
web_user = {
|
web_user = {
|
||||||
|
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"method": "Settings.GetSettingValue",
|
"method": "Settings.GetSettingValue",
|
||||||
"params": {
|
"params": {
|
||||||
|
|
||||||
"setting": "services.webserverusername"
|
"setting": "services.webserverusername"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = xbmc.executeJSONRPC(json.dumps(web_user))
|
result = xbmc.executeJSONRPC(json.dumps(web_user))
|
||||||
result = json.loads(result)
|
result = json.loads(result)
|
||||||
try:
|
try:
|
||||||
self.xbmc_username = result['result']['value']
|
xbmc_username = result['result']['value']
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
web_pass = {
|
web_pass = {
|
||||||
|
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"method": "Settings.GetSettingValue",
|
"method": "Settings.GetSettingValue",
|
||||||
"params": {
|
"params": {
|
||||||
|
|
||||||
"setting": "services.webserverpassword"
|
"setting": "services.webserverpassword"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = xbmc.executeJSONRPC(json.dumps(web_pass))
|
result = xbmc.executeJSONRPC(json.dumps(web_pass))
|
||||||
result = json.loads(result)
|
result = json.loads(result)
|
||||||
try:
|
try:
|
||||||
self.xbmc_password = result['result']['value']
|
xbmc_password = result['result']['value']
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
return (xbmc_port, xbmc_username, xbmc_password)
|
||||||
|
|
||||||
|
|
||||||
|
def double_urlencode(text):
|
||||||
|
return quote_plus(quote_plus(text))
|
||||||
|
|
||||||
|
|
||||||
|
def double_urldecode(text):
|
||||||
|
return unquote(unquote(text))
|
||||||
|
|
||||||
|
|
||||||
|
@ThreadMethodsAdditionalStop('plex_shouldStop')
|
||||||
|
@ThreadMethods
|
||||||
|
class Image_Cache_Thread(Thread):
|
||||||
|
xbmc_host = 'localhost'
|
||||||
|
xbmc_port, xbmc_username, xbmc_password = setKodiWebServerDetails()
|
||||||
|
sleep_between = 50
|
||||||
|
if settings('low_powered_device') == 'true':
|
||||||
|
# Low CPU, potentially issues with limited number of threads
|
||||||
|
# Hence let Kodi wait till download is successful
|
||||||
|
timeout = (35.1, 35.1)
|
||||||
|
else:
|
||||||
|
# High CPU, no issue with limited number of threads
|
||||||
|
timeout = (0.01, 0.01)
|
||||||
|
|
||||||
|
def __init__(self, queue):
|
||||||
|
self.queue = queue
|
||||||
|
Thread.__init__(self)
|
||||||
|
|
||||||
|
def threadSuspended(self):
|
||||||
|
# Overwrite method to add TWO additional suspends
|
||||||
|
return (self._threadSuspended or
|
||||||
|
window('suspend_LibraryThread') or
|
||||||
|
window('plex_dbScan'))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
threadStopped = self.threadStopped
|
||||||
|
threadSuspended = self.threadSuspended
|
||||||
|
queue = self.queue
|
||||||
|
sleep_between = self.sleep_between
|
||||||
|
while not threadStopped():
|
||||||
|
# In the event the server goes offline
|
||||||
|
while threadSuspended():
|
||||||
|
# Set in service.py
|
||||||
|
if threadStopped():
|
||||||
|
# Abort was requested while waiting. We should exit
|
||||||
|
log.info("---===### Stopped Image_Cache_Thread ###===---")
|
||||||
|
return
|
||||||
|
xbmc.sleep(1000)
|
||||||
|
try:
|
||||||
|
url = queue.get(block=False)
|
||||||
|
except Queue.Empty:
|
||||||
|
xbmc.sleep(1000)
|
||||||
|
continue
|
||||||
|
sleep = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
requests.head(
|
||||||
|
url="http://%s:%s/image/image://%s"
|
||||||
|
% (self.xbmc_host, self.xbmc_port, url),
|
||||||
|
auth=(self.xbmc_username, self.xbmc_password),
|
||||||
|
timeout=self.timeout)
|
||||||
|
except requests.Timeout:
|
||||||
|
# We don't need the result, only trigger Kodi to start the
|
||||||
|
# download. All is well
|
||||||
|
break
|
||||||
|
except requests.ConnectionError:
|
||||||
|
# Server thinks its a DOS attack, ('error 10053')
|
||||||
|
# Wait before trying again
|
||||||
|
if sleep > 5:
|
||||||
|
log.error('Repeatedly got ConnectionError for url %s'
|
||||||
|
% double_urldecode(url))
|
||||||
|
break
|
||||||
|
log.debug('Were trying too hard to download art, server '
|
||||||
|
'over-loaded. Sleep %s seconds before trying '
|
||||||
|
'again to download %s'
|
||||||
|
% (2**sleep, double_urldecode(url)))
|
||||||
|
xbmc.sleep((2**sleep)*1000)
|
||||||
|
sleep += 1
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
log.error('Unknown exception for url %s: %s'
|
||||||
|
% (double_urldecode(url), e))
|
||||||
|
import traceback
|
||||||
|
log.error("Traceback:\n%s" % traceback.format_exc())
|
||||||
|
break
|
||||||
|
# We did not even get a timeout
|
||||||
|
break
|
||||||
|
queue.task_done()
|
||||||
|
log.debug('Cached art: %s' % double_urldecode(url))
|
||||||
|
# Sleep for a bit to reduce CPU strain
|
||||||
|
xbmc.sleep(sleep_between)
|
||||||
|
log.info("---===### Stopped Image_Cache_Thread ###===---")
|
||||||
|
|
||||||
|
|
||||||
|
class Artwork():
|
||||||
|
enableTextureCache = settings('enableTextureCache') == "true"
|
||||||
|
if enableTextureCache:
|
||||||
|
queue = Queue.Queue()
|
||||||
|
download_thread = Image_Cache_Thread(queue)
|
||||||
|
download_thread.start()
|
||||||
|
|
||||||
def fullTextureCacheSync(self):
|
def fullTextureCacheSync(self):
|
||||||
# This method will sync all Kodi artwork to textures13.db
|
"""
|
||||||
# and cache them locally. This takes diskspace!
|
This method will sync all Kodi artwork to textures13.db
|
||||||
import xbmcaddon
|
and cache them locally. This takes diskspace!
|
||||||
string = xbmcaddon.Addon().getLocalizedString
|
"""
|
||||||
|
|
||||||
if not xbmcgui.Dialog().yesno(
|
if not xbmcgui.Dialog().yesno(
|
||||||
"Image Texture Cache", string(39250)):
|
"Image Texture Cache", lang(39250)):
|
||||||
return
|
return
|
||||||
|
|
||||||
log.info("Doing Image Cache Sync")
|
log.info("Doing Image Cache Sync")
|
||||||
|
|
||||||
pdialog = xbmcgui.DialogProgress()
|
|
||||||
pdialog.create("PlexKodiConnect", "Image Cache Sync")
|
|
||||||
|
|
||||||
# ask to rest all existing or not
|
# ask to rest all existing or not
|
||||||
if xbmcgui.Dialog().yesno(
|
if xbmcgui.Dialog().yesno(
|
||||||
"Image Texture Cache", string(39251), ""):
|
"Image Texture Cache", lang(39251), ""):
|
||||||
log.info("Resetting all cache data first")
|
log.info("Resetting all cache data first")
|
||||||
# Remove all existing textures first
|
# Remove all existing textures first
|
||||||
path = tryDecode(xbmc.translatePath("special://thumbnails/"))
|
path = tryDecode(xbmc.translatePath("special://thumbnails/"))
|
||||||
|
@ -210,105 +257,35 @@ class Artwork():
|
||||||
if tableName != "version":
|
if tableName != "version":
|
||||||
cursor.execute("DELETE FROM " + tableName)
|
cursor.execute("DELETE FROM " + tableName)
|
||||||
connection.commit()
|
connection.commit()
|
||||||
cursor.close()
|
connection.close()
|
||||||
|
|
||||||
# Cache all entries in video DB
|
# Cache all entries in video DB
|
||||||
connection = kodiSQL('video')
|
connection = kodiSQL('video')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors
|
# dont include actors
|
||||||
|
cursor.execute("SELECT url FROM art WHERE media_type != 'actor'")
|
||||||
result = cursor.fetchall()
|
result = cursor.fetchall()
|
||||||
total = len(result)
|
total = len(result)
|
||||||
log.info("Image cache sync about to process %s images" % total)
|
log.info("Image cache sync about to process %s video images" % total)
|
||||||
cursor.close()
|
connection.close()
|
||||||
|
|
||||||
count = 0
|
|
||||||
for url in result:
|
for url in result:
|
||||||
|
|
||||||
if pdialog.iscanceled():
|
|
||||||
break
|
|
||||||
|
|
||||||
percentage = int((float(count) / float(total))*100)
|
|
||||||
message = "%s of %s (%s)" % (count, total, self.imageCacheThreads)
|
|
||||||
pdialog.update(percentage, "%s %s" % (lang(33045), message))
|
|
||||||
self.cacheTexture(url[0])
|
self.cacheTexture(url[0])
|
||||||
count += 1
|
|
||||||
|
|
||||||
# Cache all entries in music DB
|
# Cache all entries in music DB
|
||||||
connection = kodiSQL('music')
|
connection = kodiSQL('music')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute("SELECT url FROM art")
|
cursor.execute("SELECT url FROM art")
|
||||||
result = cursor.fetchall()
|
result = cursor.fetchall()
|
||||||
total = len(result)
|
total = len(result)
|
||||||
log.info("Image cache sync about to process %s images" % total)
|
log.info("Image cache sync about to process %s music images" % total)
|
||||||
cursor.close()
|
connection.close()
|
||||||
|
|
||||||
count = 0
|
|
||||||
for url in result:
|
for url in result:
|
||||||
|
|
||||||
if pdialog.iscanceled():
|
|
||||||
break
|
|
||||||
|
|
||||||
percentage = int((float(count) / float(total))*100)
|
|
||||||
message = "%s of %s" % (count, total)
|
|
||||||
pdialog.update(percentage, "%s %s" % (lang(33045), message))
|
|
||||||
self.cacheTexture(url[0])
|
self.cacheTexture(url[0])
|
||||||
count += 1
|
|
||||||
|
|
||||||
pdialog.update(100, "%s %s" % (lang(33046), len(self.imageCacheThreads)))
|
|
||||||
log.info("Waiting for all threads to exit")
|
|
||||||
|
|
||||||
while len(self.imageCacheThreads):
|
|
||||||
for thread in self.imageCacheThreads:
|
|
||||||
if thread.is_finished:
|
|
||||||
self.imageCacheThreads.remove(thread)
|
|
||||||
pdialog.update(100, "%s %s" % (lang(33046), len(self.imageCacheThreads)))
|
|
||||||
log.info("Waiting for all threads to exit: %s" % len(self.imageCacheThreads))
|
|
||||||
xbmc.sleep(500)
|
|
||||||
|
|
||||||
pdialog.close()
|
|
||||||
|
|
||||||
def addWorkerImageCacheThread(self, url):
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# removed finished
|
|
||||||
for thread in self.imageCacheThreads:
|
|
||||||
if thread.is_finished:
|
|
||||||
self.imageCacheThreads.remove(thread)
|
|
||||||
|
|
||||||
# add a new thread or wait and retry if we hit our limit
|
|
||||||
if len(self.imageCacheThreads) < self.imageCacheLimitThreads:
|
|
||||||
newThread = image_cache_thread.ImageCacheThread()
|
|
||||||
newThread.set_url(self.double_urlencode(url))
|
|
||||||
newThread.set_host(self.xbmc_host, self.xbmc_port)
|
|
||||||
newThread.set_auth(self.xbmc_username, self.xbmc_password)
|
|
||||||
newThread.start()
|
|
||||||
self.imageCacheThreads.append(newThread)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
log.info("Waiting for empty queue spot: %s" % len(self.imageCacheThreads))
|
|
||||||
xbmc.sleep(50)
|
|
||||||
|
|
||||||
def cacheTexture(self, url):
|
def cacheTexture(self, url):
|
||||||
# Cache a single image url to the texture cache
|
# Cache a single image url to the texture cache
|
||||||
if url and self.enableTextureCache:
|
if url and self.enableTextureCache:
|
||||||
log.debug("Processing: %s" % url)
|
self.queue.put(double_urlencode(url))
|
||||||
|
|
||||||
if not self.imageCacheLimitThreads:
|
|
||||||
# Add image to texture cache by simply calling it at the http endpoint
|
|
||||||
|
|
||||||
url = self.double_urlencode(url)
|
|
||||||
try: # Extreme short timeouts so we will have a exception.
|
|
||||||
response = requests.head(
|
|
||||||
url=("http://%s:%s/image/image://%s"
|
|
||||||
% (self.xbmc_host, self.xbmc_port, url)),
|
|
||||||
auth=(self.xbmc_username, self.xbmc_password),
|
|
||||||
timeout=(0.01, 0.01))
|
|
||||||
# We don't need the result
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.addWorkerImageCacheThread(url)
|
|
||||||
|
|
||||||
|
|
||||||
def addArtwork(self, artwork, kodiId, mediaType, cursor):
|
def addArtwork(self, artwork, kodiId, mediaType, cursor):
|
||||||
# Kodi conversion table
|
# Kodi conversion table
|
||||||
|
@ -328,7 +305,8 @@ class Artwork():
|
||||||
for art in artwork:
|
for art in artwork:
|
||||||
if art == "Backdrop":
|
if art == "Backdrop":
|
||||||
# Backdrop entry is a list
|
# Backdrop entry is a list
|
||||||
# Process extra fanart for artwork downloader (fanart, fanart1, fanart2...)
|
# Process extra fanart for artwork downloader (fanart, fanart1,
|
||||||
|
# fanart2...)
|
||||||
backdrops = artwork[art]
|
backdrops = artwork[art]
|
||||||
backdropsNumber = len(backdrops)
|
backdropsNumber = len(backdrops)
|
||||||
|
|
||||||
|
@ -390,12 +368,11 @@ class Artwork():
|
||||||
cursor=cursor)
|
cursor=cursor)
|
||||||
|
|
||||||
def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
|
def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
|
||||||
|
if not imageUrl:
|
||||||
# Possible that the imageurl is an empty string
|
# Possible that the imageurl is an empty string
|
||||||
if imageUrl:
|
return
|
||||||
cacheimage = False
|
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"SELECT url",
|
"SELECT url",
|
||||||
"FROM art",
|
"FROM art",
|
||||||
"WHERE media_id = ?",
|
"WHERE media_id = ?",
|
||||||
|
@ -403,37 +380,32 @@ class Artwork():
|
||||||
"AND type = ?"
|
"AND type = ?"
|
||||||
))
|
))
|
||||||
cursor.execute(query, (kodiId, mediaType, imageType,))
|
cursor.execute(query, (kodiId, mediaType, imageType,))
|
||||||
try: # Update the artwork
|
try:
|
||||||
|
# Update the artwork
|
||||||
url = cursor.fetchone()[0]
|
url = cursor.fetchone()[0]
|
||||||
|
except TypeError:
|
||||||
except TypeError: # Add the artwork
|
# Add the artwork
|
||||||
cacheimage = True
|
log.debug("Adding Art Link for kodiId: %s (%s)"
|
||||||
log.debug("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl))
|
% (kodiId, imageUrl))
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
'''
|
'''
|
||||||
INSERT INTO art(media_id, media_type, type, url)
|
INSERT INTO art(media_id, media_type, type, url)
|
||||||
|
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
|
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
|
||||||
|
else:
|
||||||
else: # Only cache artwork if it changed
|
if url == imageUrl:
|
||||||
if url != imageUrl:
|
# Only cache artwork if it changed
|
||||||
cacheimage = True
|
return
|
||||||
|
|
||||||
# Only for the main backdrop, poster
|
# Only for the main backdrop, poster
|
||||||
if (window('plex_initialScan') != "true" and
|
if (window('plex_initialScan') != "true" and
|
||||||
imageType in ("fanart", "poster")):
|
imageType in ("fanart", "poster")):
|
||||||
# Delete current entry before updating with the new one
|
# Delete current entry before updating with the new one
|
||||||
self.deleteCachedArtwork(url)
|
self.deleteCachedArtwork(url)
|
||||||
|
log.debug("Updating Art url for %s kodiId %s %s -> (%s)"
|
||||||
log.info("Updating Art url for %s kodiId: %s (%s) -> (%s)"
|
|
||||||
% (imageType, kodiId, url, imageUrl))
|
% (imageType, kodiId, url, imageUrl))
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"UPDATE art",
|
"UPDATE art",
|
||||||
"SET url = ?",
|
"SET url = ?",
|
||||||
"WHERE media_id = ?",
|
"WHERE media_id = ?",
|
||||||
|
@ -443,14 +415,11 @@ class Artwork():
|
||||||
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
|
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
|
||||||
|
|
||||||
# Cache fanart and poster in Kodi texture cache
|
# Cache fanart and poster in Kodi texture cache
|
||||||
if cacheimage:
|
|
||||||
self.cacheTexture(imageUrl)
|
self.cacheTexture(imageUrl)
|
||||||
|
|
||||||
def deleteArtwork(self, kodiId, mediaType, cursor):
|
def deleteArtwork(self, kodiId, mediaType, cursor):
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
"SELECT url",
|
||||||
"SELECT url, type",
|
|
||||||
"FROM art",
|
"FROM art",
|
||||||
"WHERE media_id = ?",
|
"WHERE media_id = ?",
|
||||||
"AND media_type = ?"
|
"AND media_type = ?"
|
||||||
|
@ -458,159 +427,29 @@ class Artwork():
|
||||||
cursor.execute(query, (kodiId, mediaType,))
|
cursor.execute(query, (kodiId, mediaType,))
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
for row in rows:
|
for row in rows:
|
||||||
|
self.deleteCachedArtwork(row[0])
|
||||||
url = row[0]
|
|
||||||
imageType = row[1]
|
|
||||||
if imageType in ("poster", "fanart"):
|
|
||||||
self.deleteCachedArtwork(url)
|
|
||||||
|
|
||||||
def deleteCachedArtwork(self, url):
|
def deleteCachedArtwork(self, url):
|
||||||
# Only necessary to remove and apply a new backdrop or poster
|
# Only necessary to remove and apply a new backdrop or poster
|
||||||
connection = kodiSQL('texture')
|
connection = kodiSQL('texture')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
|
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?",
|
||||||
|
(url,))
|
||||||
cachedurl = cursor.fetchone()[0]
|
cachedurl = cursor.fetchone()[0]
|
||||||
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
log.info("Could not find cached url.")
|
log.info("Could not find cached url.")
|
||||||
|
else:
|
||||||
except OperationalError:
|
# Delete thumbnail as well as the entry
|
||||||
log.info("Database is locked. Skip deletion process.")
|
|
||||||
|
|
||||||
else: # Delete thumbnail as well as the entry
|
|
||||||
thumbnails = tryDecode(
|
thumbnails = tryDecode(
|
||||||
xbmc.translatePath("special://thumbnails/%s" % cachedurl))
|
xbmc.translatePath("special://thumbnails/%s" % cachedurl))
|
||||||
log.info("Deleting cached thumbnail: %s" % thumbnails)
|
log.debug("Deleting cached thumbnail: %s" % thumbnails)
|
||||||
xbmcvfs.delete(thumbnails)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
xbmcvfs.delete(thumbnails)
|
||||||
|
except Exception as e:
|
||||||
|
log.error('Could not delete cached artwork %s. Error: %s'
|
||||||
|
% (thumbnails, e))
|
||||||
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
|
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
|
||||||
connection.commit()
|
connection.commit()
|
||||||
except OperationalError:
|
|
||||||
log.debug("Issue deleting url from cache. Skipping.")
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
connection.close()
|
||||||
|
|
||||||
def getPeopleArtwork(self, people):
|
|
||||||
# append imageurl if existing
|
|
||||||
for person in people:
|
|
||||||
|
|
||||||
personId = person['Id']
|
|
||||||
tag = person.get('PrimaryImageTag')
|
|
||||||
|
|
||||||
image = ""
|
|
||||||
if tag:
|
|
||||||
image = (
|
|
||||||
"%s/emby/Items/%s/Images/Primary?"
|
|
||||||
"MaxWidth=400&MaxHeight=400&Index=0&Tag=%s"
|
|
||||||
% (self.server, personId, tag))
|
|
||||||
|
|
||||||
person['imageurl'] = image
|
|
||||||
|
|
||||||
return people
|
|
||||||
|
|
||||||
def getUserArtwork(self, itemId, itemType):
|
|
||||||
# Load user information set by UserClient
|
|
||||||
image = ("%s/emby/Users/%s/Images/%s?Format=original"
|
|
||||||
% (self.server, itemId, itemType))
|
|
||||||
return image
|
|
||||||
|
|
||||||
|
|
||||||
def getAllArtwork(self, item, parentInfo=False):
|
|
||||||
|
|
||||||
itemid = item['Id']
|
|
||||||
artworks = item['ImageTags']
|
|
||||||
backdrops = item.get('BackdropImageTags', [])
|
|
||||||
|
|
||||||
maxHeight = 10000
|
|
||||||
maxWidth = 10000
|
|
||||||
customquery = ""
|
|
||||||
|
|
||||||
# if utils.settings('compressArt') == "true":
|
|
||||||
# customquery = "&Quality=90"
|
|
||||||
|
|
||||||
# if utils.settings('enableCoverArt') == "false":
|
|
||||||
# customquery += "&EnableImageEnhancers=false"
|
|
||||||
|
|
||||||
allartworks = {
|
|
||||||
|
|
||||||
'Primary': "",
|
|
||||||
'Art': "",
|
|
||||||
'Banner': "",
|
|
||||||
'Logo': "",
|
|
||||||
'Thumb': "",
|
|
||||||
'Disc': "",
|
|
||||||
'Backdrop': []
|
|
||||||
}
|
|
||||||
|
|
||||||
# Process backdrops
|
|
||||||
for index, tag in enumerate(backdrops):
|
|
||||||
artwork = (
|
|
||||||
"%s/emby/Items/%s/Images/Backdrop/%s?"
|
|
||||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
|
||||||
% (self.server, itemid, index, maxWidth, maxHeight, tag, customquery))
|
|
||||||
allartworks['Backdrop'].append(artwork)
|
|
||||||
|
|
||||||
# Process the rest of the artwork
|
|
||||||
for art in artworks:
|
|
||||||
# Filter backcover
|
|
||||||
if art != "BoxRear":
|
|
||||||
tag = artworks[art]
|
|
||||||
artwork = (
|
|
||||||
"%s/emby/Items/%s/Images/%s/0?"
|
|
||||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
|
||||||
% (self.server, itemid, art, maxWidth, maxHeight, tag, customquery))
|
|
||||||
allartworks[art] = artwork
|
|
||||||
|
|
||||||
# Process parent items if the main item is missing artwork
|
|
||||||
if parentInfo:
|
|
||||||
|
|
||||||
# Process parent backdrops
|
|
||||||
if not allartworks['Backdrop']:
|
|
||||||
|
|
||||||
parentId = item.get('ParentBackdropItemId')
|
|
||||||
if parentId:
|
|
||||||
# If there is a parentId, go through the parent backdrop list
|
|
||||||
parentbackdrops = item['ParentBackdropImageTags']
|
|
||||||
|
|
||||||
for index, tag in enumerate(parentbackdrops):
|
|
||||||
artwork = (
|
|
||||||
"%s/emby/Items/%s/Images/Backdrop/%s?"
|
|
||||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
|
||||||
% (self.server, parentId, index, maxWidth, maxHeight, tag, customquery))
|
|
||||||
allartworks['Backdrop'].append(artwork)
|
|
||||||
|
|
||||||
# Process the rest of the artwork
|
|
||||||
parentartwork = ['Logo', 'Art', 'Thumb']
|
|
||||||
for parentart in parentartwork:
|
|
||||||
|
|
||||||
if not allartworks[parentart]:
|
|
||||||
|
|
||||||
parentId = item.get('Parent%sItemId' % parentart)
|
|
||||||
if parentId:
|
|
||||||
|
|
||||||
parentTag = item['Parent%sImageTag' % parentart]
|
|
||||||
artwork = (
|
|
||||||
"%s/emby/Items/%s/Images/%s/0?"
|
|
||||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
|
||||||
% (self.server, parentId, parentart,
|
|
||||||
maxWidth, maxHeight, parentTag, customquery))
|
|
||||||
allartworks[parentart] = artwork
|
|
||||||
|
|
||||||
# Parent album works a bit differently
|
|
||||||
if not allartworks['Primary']:
|
|
||||||
|
|
||||||
parentId = item.get('AlbumId')
|
|
||||||
if parentId and item.get('AlbumPrimaryImageTag'):
|
|
||||||
|
|
||||||
parentTag = item['AlbumPrimaryImageTag']
|
|
||||||
artwork = (
|
|
||||||
"%s/emby/Items/%s/Images/Primary/0?"
|
|
||||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
|
||||||
% (self.server, parentId, maxWidth, maxHeight, parentTag, customquery))
|
|
||||||
allartworks['Primary'] = artwork
|
|
||||||
|
|
||||||
return allartworks
|
|
||||||
|
|
|
@ -373,3 +373,30 @@ class Embydb_Functions():
|
||||||
query = "DELETE FROM emby WHERE emby_id LIKE ?"
|
query = "DELETE FROM emby WHERE emby_id LIKE ?"
|
||||||
self.embycursor.execute(query, (plexid+"%",))
|
self.embycursor.execute(query, (plexid+"%",))
|
||||||
|
|
||||||
|
def itemsByType(self, plextype):
|
||||||
|
"""
|
||||||
|
Returns a list of dictionaries for all Kodi DB items present for
|
||||||
|
plextype. One dict is of the type
|
||||||
|
|
||||||
|
{
|
||||||
|
'plexId': the Plex id
|
||||||
|
'kodiId': the Kodi id
|
||||||
|
'kodi_type': e.g. 'movie', 'tvshow'
|
||||||
|
'plex_type': e.g. 'Movie', 'Series', the input plextype
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
query = ' '.join((
|
||||||
|
"SELECT emby_id, kodi_id, media_type",
|
||||||
|
"FROM emby",
|
||||||
|
"WHERE emby_type = ?",
|
||||||
|
))
|
||||||
|
self.embycursor.execute(query, (plextype, ))
|
||||||
|
result = []
|
||||||
|
for row in self.embycursor.fetchall():
|
||||||
|
result.append({
|
||||||
|
'plexId': row[0],
|
||||||
|
'kodiId': row[1],
|
||||||
|
'kodi_type': row[2],
|
||||||
|
'plex_type': plextype
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
|
@ -275,7 +275,6 @@ def doMainListing():
|
||||||
# addDirectoryItem("Add user to session", "plugin://plugin.video.plexkodiconnect/?mode=adduser")
|
# addDirectoryItem("Add user to session", "plugin://plugin.video.plexkodiconnect/?mode=adduser")
|
||||||
addDirectoryItem(lang(39203), "plugin://plugin.video.plexkodiconnect/?mode=refreshplaylist")
|
addDirectoryItem(lang(39203), "plugin://plugin.video.plexkodiconnect/?mode=refreshplaylist")
|
||||||
addDirectoryItem(lang(39204), "plugin://plugin.video.plexkodiconnect/?mode=manualsync")
|
addDirectoryItem(lang(39204), "plugin://plugin.video.plexkodiconnect/?mode=manualsync")
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
||||||
|
|
||||||
|
|
||||||
|
@ -1363,7 +1362,7 @@ def enterPMS():
|
||||||
settings('plex_machineIdentifier', '')
|
settings('plex_machineIdentifier', '')
|
||||||
else:
|
else:
|
||||||
settings('plex_machineIdentifier', machineIdentifier)
|
settings('plex_machineIdentifier', machineIdentifier)
|
||||||
log.info('Setting new PMS to https %s, ip %s, port %s, machineIdentifier '
|
log.info('Set new PMS to https %s, ip %s, port %s, machineIdentifier %s'
|
||||||
% (https, ip, port, machineIdentifier))
|
% (https, ip, port, machineIdentifier))
|
||||||
settings('https', value=https)
|
settings('https', value=https)
|
||||||
settings('ipaddress', value=ip)
|
settings('ipaddress', value=ip)
|
||||||
|
|
|
@ -47,19 +47,13 @@ class ImageCacheThread(threading.Thread):
|
||||||
self.xbmc_password = password
|
self.xbmc_password = password
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
log.debug("Image Caching Thread Processing: %s", self.url_to_process)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.head(
|
response = requests.head(
|
||||||
url=(
|
url=("http://%s:%s/image/image://%s"
|
||||||
"http://%s:%s/image/image://%s"
|
% (self.xbmc_host, self.xbmc_port, self.url_to_process)),
|
||||||
% (self.xbmc_host, self.xbmc_port, self.urlToProcess)),
|
|
||||||
auth=(self.xbmc_username, self.xbmc_password),
|
auth=(self.xbmc_username, self.xbmc_password),
|
||||||
timeout=(5, 5))
|
timeout=(5, 5))
|
||||||
# We don't need the result
|
# We don't need the result
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
log.debug("Image Caching Thread Exited")
|
|
||||||
self.is_finished = True
|
self.is_finished = True
|
||||||
|
|
|
@ -417,6 +417,15 @@ 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
|
||||||
|
@ -463,13 +472,6 @@ class InitialSetup():
|
||||||
log.debug("User opted to use FanArtTV")
|
log.debug("User opted to use FanArtTV")
|
||||||
settings('FanartTV', value="true")
|
settings('FanartTV', value="true")
|
||||||
|
|
||||||
# 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)):
|
|
||||||
log.debug('User thinks that PKC runs on a raspi or similar')
|
|
||||||
settings('imageCacheLimit', value='1')
|
|
||||||
|
|
||||||
# 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')
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import urllib
|
from urllib import urlencode
|
||||||
from ntpath import dirname
|
from ntpath import dirname
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@ import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
|
|
||||||
import artwork
|
import artwork
|
||||||
from utils import settings, window, kodiSQL, CatchExceptions
|
from utils import tryEncode, tryDecode, settings, window, kodiSQL, \
|
||||||
from utils import tryEncode, tryDecode
|
CatchExceptions
|
||||||
import embydb_functions as embydb
|
import embydb_functions as embydb
|
||||||
import kodidb_functions as kodidb
|
import kodidb_functions as kodidb
|
||||||
|
|
||||||
|
@ -65,6 +65,28 @@ class Items(object):
|
||||||
self.kodiconn.close()
|
self.kodiconn.close()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@CatchExceptions(warnuser=True)
|
||||||
|
def getfanart(self, item, kodiId, mediaType, allartworks=None):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
API = PlexAPI.API(item)
|
||||||
|
if allartworks is None:
|
||||||
|
allartworks = API.getAllArtwork()
|
||||||
|
self.artwork.addArtwork(API.getFanartArtwork(allartworks),
|
||||||
|
kodiId,
|
||||||
|
mediaType,
|
||||||
|
self.kodicursor)
|
||||||
|
# Also get artwork for collections/movie sets
|
||||||
|
if mediaType == 'movie':
|
||||||
|
for setname in API.getCollections():
|
||||||
|
log.debug('Getting artwork for movie set %s' % setname)
|
||||||
|
setid = self.kodi_db.createBoxset(setname)
|
||||||
|
self.artwork.addArtwork(API.getSetArtwork(),
|
||||||
|
setid,
|
||||||
|
"set",
|
||||||
|
self.kodicursor)
|
||||||
|
self.kodi_db.assignBoxset(setid, kodiId)
|
||||||
|
|
||||||
def itemsbyId(self, items, process, pdialog=None):
|
def itemsbyId(self, items, process, pdialog=None):
|
||||||
# Process items by itemid. Process can be added, update, userdata, remove
|
# Process items by itemid. Process can be added, update, userdata, remove
|
||||||
embycursor = self.embycursor
|
embycursor = self.embycursor
|
||||||
|
@ -394,7 +416,7 @@ class Movies(Items):
|
||||||
'dbid': movieid,
|
'dbid': movieid,
|
||||||
'mode': "play"
|
'mode': "play"
|
||||||
}
|
}
|
||||||
filename = "%s?%s" % (path, urllib.urlencode(params))
|
filename = "%s?%s" % (path, urlencode(params))
|
||||||
playurl = filename
|
playurl = filename
|
||||||
|
|
||||||
# movie table:
|
# movie table:
|
||||||
|
@ -408,17 +430,32 @@ class Movies(Items):
|
||||||
% (itemid, title))
|
% (itemid, title))
|
||||||
|
|
||||||
# Update the movie entry
|
# Update the movie entry
|
||||||
|
if self.kodiversion > 16:
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"UPDATE movie",
|
"UPDATE movie",
|
||||||
"SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?, c06 = ?,",
|
"SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?,"
|
||||||
"c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?,",
|
"c06 = ?, c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?,"
|
||||||
"c16 = ?, c18 = ?, c19 = ?, c21 = ?, c22 = ?, c23 = ?",
|
"c14 = ?, c15 = ?, c16 = ?, c18 = ?, c19 = ?, c21 = ?,"
|
||||||
|
"c22 = ?, c23 = ?, premiered = ?",
|
||||||
"WHERE idMovie = ?"
|
"WHERE idMovie = ?"
|
||||||
))
|
))
|
||||||
kodicursor.execute(query, (title, plot, shortplot, tagline, votecount, rating, writer,
|
kodicursor.execute(query, (title, plot, shortplot, tagline,
|
||||||
year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, trailer,
|
votecount, rating, writer, year, imdb, sorttitle, runtime,
|
||||||
country, playurl, pathid, movieid))
|
mpaa, genre, director, title, studio, trailer, country,
|
||||||
|
playurl, pathid, year, movieid))
|
||||||
|
else:
|
||||||
|
query = ' '.join((
|
||||||
|
"UPDATE movie",
|
||||||
|
"SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?,"
|
||||||
|
"c06 = ?, c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?,"
|
||||||
|
"c14 = ?, c15 = ?, c16 = ?, c18 = ?, c19 = ?, c21 = ?,"
|
||||||
|
"c22 = ?, c23 = ?",
|
||||||
|
"WHERE idMovie = ?"
|
||||||
|
))
|
||||||
|
kodicursor.execute(query, (title, plot, shortplot, tagline,
|
||||||
|
votecount, rating, writer, year, imdb, sorttitle, runtime,
|
||||||
|
mpaa, genre, director, title, studio, trailer, country,
|
||||||
|
playurl, pathid, movieid))
|
||||||
|
|
||||||
# Update the checksum in emby table
|
# Update the checksum in emby table
|
||||||
emby_db.updateReference(itemid, checksum)
|
emby_db.updateReference(itemid, checksum)
|
||||||
|
@ -469,16 +506,13 @@ class Movies(Items):
|
||||||
# Process countries
|
# Process countries
|
||||||
self.kodi_db.addCountries(movieid, countries, "movie")
|
self.kodi_db.addCountries(movieid, countries, "movie")
|
||||||
# Process cast
|
# Process cast
|
||||||
people = API.getPeopleList()
|
self.kodi_db.addPeople(movieid, API.getPeopleList(), "movie")
|
||||||
self.kodi_db.addPeople(movieid, people, "movie")
|
|
||||||
# Process genres
|
# Process genres
|
||||||
self.kodi_db.addGenres(movieid, genres, "movie")
|
self.kodi_db.addGenres(movieid, genres, "movie")
|
||||||
# Process artwork
|
# Process artwork
|
||||||
allartworks = API.getAllArtwork()
|
artwork.addArtwork(API.getAllArtwork(), movieid, "movie", kodicursor)
|
||||||
artwork.addArtwork(allartworks, movieid, "movie", kodicursor)
|
|
||||||
# Process stream details
|
# Process stream details
|
||||||
streams = API.getMediaStreams()
|
self.kodi_db.addStreams(fileid, API.getMediaStreams(), runtime)
|
||||||
self.kodi_db.addStreams(fileid, streams, runtime)
|
|
||||||
# Process studios
|
# Process studios
|
||||||
self.kodi_db.addStudios(movieid, studios, "movie")
|
self.kodi_db.addStudios(movieid, studios, "movie")
|
||||||
# Process tags: view, Plex collection tags
|
# Process tags: view, Plex collection tags
|
||||||
|
@ -488,7 +522,7 @@ class Movies(Items):
|
||||||
tags.append("Favorite movies")
|
tags.append("Favorite movies")
|
||||||
self.kodi_db.addTags(movieid, tags, "movie")
|
self.kodi_db.addTags(movieid, tags, "movie")
|
||||||
# Add any sets from Plex collection tags
|
# Add any sets from Plex collection tags
|
||||||
self.kodi_db.addSets(movieid, collections, kodicursor, API)
|
self.kodi_db.addSets(movieid, collections, kodicursor)
|
||||||
# Process playstates
|
# Process playstates
|
||||||
self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
||||||
|
|
||||||
|
@ -906,7 +940,7 @@ class TVShows(Items):
|
||||||
'dbid': episodeid,
|
'dbid': episodeid,
|
||||||
'mode': "play"
|
'mode': "play"
|
||||||
}
|
}
|
||||||
filename = "%s?%s" % (path, tryDecode(urllib.urlencode(params)))
|
filename = "%s?%s" % (path, tryDecode(urlencode(params)))
|
||||||
playurl = filename
|
playurl = filename
|
||||||
parentPathId = self.kodi_db.addPath(
|
parentPathId = self.kodi_db.addPath(
|
||||||
'plugin://plugin.video.plexkodiconnect.tvshows/')
|
'plugin://plugin.video.plexkodiconnect.tvshows/')
|
||||||
|
|
|
@ -508,6 +508,48 @@ class Kodidb_Functions():
|
||||||
|
|
||||||
self.artwork.addOrUpdateArt(thumb, actorid, arttype, "thumb", self.cursor)
|
self.artwork.addOrUpdateArt(thumb, actorid, arttype, "thumb", self.cursor)
|
||||||
|
|
||||||
|
def existingArt(self, kodiId, mediaType, refresh=False):
|
||||||
|
"""
|
||||||
|
For kodiId, returns an artwork dict with already existing art from
|
||||||
|
the Kodi db
|
||||||
|
"""
|
||||||
|
# Only get EITHER poster OR thumb (should have same URL)
|
||||||
|
kodiToPKC = {
|
||||||
|
'banner': 'Banner',
|
||||||
|
'clearart': 'Art',
|
||||||
|
'clearlogo': 'Logo',
|
||||||
|
'discart': 'Disc',
|
||||||
|
'landscape': 'Thumb',
|
||||||
|
'thumb': 'Primary'
|
||||||
|
}
|
||||||
|
# BoxRear yet unused
|
||||||
|
result = {'BoxRear': ''}
|
||||||
|
for art in kodiToPKC:
|
||||||
|
query = ' '.join((
|
||||||
|
"SELECT url",
|
||||||
|
"FROM art",
|
||||||
|
"WHERE media_id = ?",
|
||||||
|
"AND media_type = ?",
|
||||||
|
"AND type = ?"
|
||||||
|
))
|
||||||
|
self.cursor.execute(query, (kodiId, mediaType, art,))
|
||||||
|
try:
|
||||||
|
url = self.cursor.fetchone()[0]
|
||||||
|
except TypeError:
|
||||||
|
url = ""
|
||||||
|
result[kodiToPKC[art]] = url
|
||||||
|
# There may be several fanart URLs saved
|
||||||
|
query = ' '.join((
|
||||||
|
"SELECT url",
|
||||||
|
"FROM art",
|
||||||
|
"WHERE media_id = ?",
|
||||||
|
"AND media_type = ?",
|
||||||
|
"AND type LIKE ?"
|
||||||
|
))
|
||||||
|
data = self.cursor.execute(query, (kodiId, mediaType, "fanart%",))
|
||||||
|
result['Backdrop'] = [d[0] for d in data]
|
||||||
|
return result
|
||||||
|
|
||||||
def addGenres(self, kodiid, genres, mediatype):
|
def addGenres(self, kodiid, genres, mediatype):
|
||||||
|
|
||||||
|
|
||||||
|
@ -1180,15 +1222,9 @@ class Kodidb_Functions():
|
||||||
))
|
))
|
||||||
self.cursor.execute(query, (kodiid, mediatype, tag_id,))
|
self.cursor.execute(query, (kodiid, mediatype, tag_id,))
|
||||||
|
|
||||||
def addSets(self, movieid, collections, kodicursor, API):
|
def addSets(self, movieid, collections, kodicursor):
|
||||||
for setname in collections:
|
for setname in collections:
|
||||||
setid = self.createBoxset(setname)
|
setid = self.createBoxset(setname)
|
||||||
# Process artwork
|
|
||||||
if settings('setFanartTV') == 'true':
|
|
||||||
self.artwork.addArtwork(API.getSetArtwork(),
|
|
||||||
setid,
|
|
||||||
"set",
|
|
||||||
kodicursor)
|
|
||||||
self.assignBoxset(setid, movieid)
|
self.assignBoxset(setid, movieid)
|
||||||
|
|
||||||
def createBoxset(self, boxsetname):
|
def createBoxset(self, boxsetname):
|
||||||
|
|
|
@ -254,7 +254,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
|
|
||||||
# Save currentFile for cleanup later and to be able to access refs
|
# Save currentFile for cleanup later and to be able to access refs
|
||||||
window('plex_lastPlayedFiled', value=currentFile)
|
window('plex_lastPlayedFiled', value=currentFile)
|
||||||
window('Plex_currently_playing_itemid', value=plexid)
|
window('plex_currently_playing_itemid', value=plexid)
|
||||||
window("emby_%s.itemid" % tryEncode(currentFile), value=plexid)
|
window("emby_%s.itemid" % tryEncode(currentFile), value=plexid)
|
||||||
log.info('Finish playback startup')
|
log.info('Finish playback startup')
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import logging
|
import logging
|
||||||
from threading import Thread, Lock
|
from threading import Thread, Lock
|
||||||
import Queue
|
import Queue
|
||||||
|
from random import shuffle
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
|
@ -62,7 +63,7 @@ class ThreadedGetMetadata(Thread):
|
||||||
try:
|
try:
|
||||||
self.queue.get(block=False)
|
self.queue.get(block=False)
|
||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
xbmc.sleep(50)
|
xbmc.sleep(10)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
self.queue.task_done()
|
self.queue.task_done()
|
||||||
|
@ -73,7 +74,7 @@ class ThreadedGetMetadata(Thread):
|
||||||
try:
|
try:
|
||||||
self.out_queue.get(block=False)
|
self.out_queue.get(block=False)
|
||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
xbmc.sleep(50)
|
xbmc.sleep(10)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
self.out_queue.task_done()
|
self.out_queue.task_done()
|
||||||
|
@ -93,7 +94,7 @@ class ThreadedGetMetadata(Thread):
|
||||||
updateItem = queue.get(block=False)
|
updateItem = queue.get(block=False)
|
||||||
# Empty queue
|
# Empty queue
|
||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
xbmc.sleep(100)
|
xbmc.sleep(10)
|
||||||
continue
|
continue
|
||||||
# Download Metadata
|
# Download Metadata
|
||||||
plexXML = PF.GetPlexMetadata(updateItem['itemId'])
|
plexXML = PF.GetPlexMetadata(updateItem['itemId'])
|
||||||
|
@ -155,7 +156,7 @@ class ThreadedProcessMetadata(Thread):
|
||||||
try:
|
try:
|
||||||
self.queue.get(block=False)
|
self.queue.get(block=False)
|
||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
xbmc.sleep(100)
|
xbmc.sleep(10)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
self.queue.task_done()
|
self.queue.task_done()
|
||||||
|
@ -175,7 +176,7 @@ class ThreadedProcessMetadata(Thread):
|
||||||
try:
|
try:
|
||||||
updateItem = queue.get(block=False)
|
updateItem = queue.get(block=False)
|
||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
xbmc.sleep(50)
|
xbmc.sleep(10)
|
||||||
continue
|
continue
|
||||||
# Do the work
|
# Do the work
|
||||||
plexitem = updateItem['XML']
|
plexitem = updateItem['XML']
|
||||||
|
@ -250,11 +251,106 @@ class ThreadedShowSyncInfo(Thread):
|
||||||
processMetadataProgress,
|
processMetadataProgress,
|
||||||
viewName))
|
viewName))
|
||||||
# Sleep for x milliseconds
|
# Sleep for x milliseconds
|
||||||
xbmc.sleep(500)
|
xbmc.sleep(200)
|
||||||
dialog.close()
|
dialog.close()
|
||||||
log.debug('Dialog Infobox thread terminated')
|
log.debug('Dialog Infobox thread terminated')
|
||||||
|
|
||||||
|
|
||||||
|
@ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
|
||||||
|
@ThreadMethodsAdditionalStop('plex_shouldStop')
|
||||||
|
@ThreadMethods
|
||||||
|
class ProcessFanartThread(Thread):
|
||||||
|
"""
|
||||||
|
Threaded download of additional fanart in the background
|
||||||
|
|
||||||
|
Input:
|
||||||
|
queue Queue.Queue() object that you will need to fill with
|
||||||
|
dicts of the following form:
|
||||||
|
{
|
||||||
|
'itemId': the Plex id as a string
|
||||||
|
'class': the itemtypes class, e.g. 'Movies'
|
||||||
|
'mediaType': the kodi media type, e.g. 'movie'
|
||||||
|
'refresh': True/False if true, will overwrite any 3rd party
|
||||||
|
fanart. If False, will only get missing
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
def __init__(self, queue):
|
||||||
|
self.queue = queue
|
||||||
|
Thread.__init__(self)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
threadStopped = self.threadStopped
|
||||||
|
threadSuspended = self.threadSuspended
|
||||||
|
queue = self.queue
|
||||||
|
log.info("---===### Starting FanartSync ###===---")
|
||||||
|
while not threadStopped():
|
||||||
|
# In the event the server goes offline
|
||||||
|
while threadSuspended() or window('plex_dbScan'):
|
||||||
|
# Set in service.py
|
||||||
|
if threadStopped():
|
||||||
|
# Abort was requested while waiting. We should exit
|
||||||
|
log.info("---===### Stopped FanartSync ###===---")
|
||||||
|
return
|
||||||
|
xbmc.sleep(1000)
|
||||||
|
# grabs Plex item from queue
|
||||||
|
try:
|
||||||
|
item = queue.get(block=False)
|
||||||
|
except Queue.Empty:
|
||||||
|
xbmc.sleep(200)
|
||||||
|
continue
|
||||||
|
if item['refresh'] is True:
|
||||||
|
# Leave the Plex art untouched
|
||||||
|
allartworks = None
|
||||||
|
else:
|
||||||
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
|
try:
|
||||||
|
kodiId = emby_db.getItem_byId(item['itemId'])[0]
|
||||||
|
except TypeError:
|
||||||
|
log.error('Could not get Kodi id for plex id %s'
|
||||||
|
% item['itemId'])
|
||||||
|
queue.task_done()
|
||||||
|
continue
|
||||||
|
with kodidb.GetKodiDB('video') as kodi_db:
|
||||||
|
allartworks = kodi_db.existingArt(kodiId,
|
||||||
|
item['mediaType'])
|
||||||
|
# Check if we even need to get additional art
|
||||||
|
needsupdate = False
|
||||||
|
for key, value in allartworks.iteritems():
|
||||||
|
if not value and not key == 'BoxRear':
|
||||||
|
needsupdate = True
|
||||||
|
break
|
||||||
|
if needsupdate is False:
|
||||||
|
log.debug('Already got all art for Plex id %s'
|
||||||
|
% item['itemId'])
|
||||||
|
queue.task_done()
|
||||||
|
continue
|
||||||
|
|
||||||
|
log.debug('Getting additional fanart for Plex id %s'
|
||||||
|
% item['itemId'])
|
||||||
|
# Download Metadata
|
||||||
|
xml = PF.GetPlexMetadata(item['itemId'])
|
||||||
|
if xml is None:
|
||||||
|
# Did not receive a valid XML - skip that item for now
|
||||||
|
log.warn("Could not get metadata for %s. Skipping that item "
|
||||||
|
"for now" % item['itemId'])
|
||||||
|
queue.task_done()
|
||||||
|
continue
|
||||||
|
elif xml == 401:
|
||||||
|
log.warn('HTTP 401 returned by PMS. Too much strain? '
|
||||||
|
'Cancelling sync for now')
|
||||||
|
# Kill remaining items in queue (for main thread to cont.)
|
||||||
|
queue.task_done()
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Do the work
|
||||||
|
with getattr(itemtypes, item['class'])() as cls:
|
||||||
|
cls.getfanart(xml[0], kodiId, item['mediaType'], allartworks)
|
||||||
|
# signals to queue job is done
|
||||||
|
log.debug('Done getting fanart for Plex id %s' % item['itemId'])
|
||||||
|
queue.task_done()
|
||||||
|
log.info("---===### Stopped FanartSync ###===---")
|
||||||
|
|
||||||
|
|
||||||
@ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
|
@ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
|
||||||
@ThreadMethodsAdditionalStop('plex_shouldStop')
|
@ThreadMethodsAdditionalStop('plex_shouldStop')
|
||||||
@ThreadMethods
|
@ThreadMethods
|
||||||
|
@ -275,6 +371,9 @@ class LibrarySync(Thread):
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.itemsToProcess = []
|
self.itemsToProcess = []
|
||||||
self.sessionKeys = []
|
self.sessionKeys = []
|
||||||
|
self.fanartqueue = Queue.Queue()
|
||||||
|
if settings('FanartTV') == 'true':
|
||||||
|
self.fanartthread = ProcessFanartThread(self.fanartqueue)
|
||||||
# How long should we wait at least to process new/changed PMS items?
|
# How long should we wait at least to process new/changed PMS items?
|
||||||
self.saftyMargin = int(settings('saftyMargin'))
|
self.saftyMargin = int(settings('saftyMargin'))
|
||||||
|
|
||||||
|
@ -285,6 +384,7 @@ class LibrarySync(Thread):
|
||||||
self.vnodes = videonodes.VideoNodes()
|
self.vnodes = videonodes.VideoNodes()
|
||||||
self.dialog = xbmcgui.Dialog()
|
self.dialog = xbmcgui.Dialog()
|
||||||
|
|
||||||
|
self.syncThreadNumber = int(settings('syncThreadNumber'))
|
||||||
self.installSyncDone = settings('SyncInstallRunDone') == 'true'
|
self.installSyncDone = settings('SyncInstallRunDone') == 'true'
|
||||||
self.showDbSync = settings('dbSyncIndicator') == 'true'
|
self.showDbSync = settings('dbSyncIndicator') == 'true'
|
||||||
self.enableMusic = settings('enableMusic') == "true"
|
self.enableMusic = settings('enableMusic') == "true"
|
||||||
|
@ -292,9 +392,6 @@ class LibrarySync(Thread):
|
||||||
'enableBackgroundSync') == "true"
|
'enableBackgroundSync') == "true"
|
||||||
self.limitindex = int(settings('limitindex'))
|
self.limitindex = int(settings('limitindex'))
|
||||||
|
|
||||||
if settings('plex_pathverified') == 'true':
|
|
||||||
window('plex_pathverified', value='true')
|
|
||||||
|
|
||||||
# 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))
|
||||||
|
@ -760,6 +857,7 @@ class LibrarySync(Thread):
|
||||||
'viewName': xxx,
|
'viewName': xxx,
|
||||||
'viewId': xxx,
|
'viewId': xxx,
|
||||||
'title': xxx
|
'title': xxx
|
||||||
|
'mediaType': xxx, e.g. 'movie', 'episode'
|
||||||
|
|
||||||
self.allPlexElementsId APPENDED(!!) dict
|
self.allPlexElementsId APPENDED(!!) dict
|
||||||
= {itemid: checksum}
|
= {itemid: checksum}
|
||||||
|
@ -779,12 +877,15 @@ class LibrarySync(Thread):
|
||||||
# Only update if movie is not in Kodi or checksum is
|
# Only update if movie is not in Kodi or checksum is
|
||||||
# different
|
# different
|
||||||
if kodi_checksum != plex_checksum:
|
if kodi_checksum != plex_checksum:
|
||||||
self.updatelist.append({'itemId': itemId,
|
self.updatelist.append({
|
||||||
|
'itemId': itemId,
|
||||||
'itemType': itemType,
|
'itemType': itemType,
|
||||||
'method': method,
|
'method': method,
|
||||||
'viewName': viewName,
|
'viewName': viewName,
|
||||||
'viewId': viewId,
|
'viewId': viewId,
|
||||||
'title': title})
|
'title': title,
|
||||||
|
'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:
|
||||||
|
@ -796,12 +897,15 @@ class LibrarySync(Thread):
|
||||||
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
|
||||||
self.updatelist.append({'itemId': itemId,
|
self.updatelist.append({
|
||||||
|
'itemId': itemId,
|
||||||
'itemType': itemType,
|
'itemType': itemType,
|
||||||
'method': method,
|
'method': method,
|
||||||
'viewName': viewName,
|
'viewName': viewName,
|
||||||
'viewId': viewId,
|
'viewId': viewId,
|
||||||
'title': title})
|
'title': title,
|
||||||
|
'mediaType': item.attrib.get('type')
|
||||||
|
})
|
||||||
|
|
||||||
def GetAndProcessXMLs(self, itemType, showProgress=True):
|
def GetAndProcessXMLs(self, itemType, showProgress=True):
|
||||||
"""
|
"""
|
||||||
|
@ -836,8 +940,9 @@ class LibrarySync(Thread):
|
||||||
# Populate queue: GetMetadata
|
# Populate queue: GetMetadata
|
||||||
for updateItem in self.updatelist:
|
for updateItem in self.updatelist:
|
||||||
getMetadataQueue.put(updateItem)
|
getMetadataQueue.put(updateItem)
|
||||||
# Spawn GetMetadata thread for downloading
|
# Spawn GetMetadata threads for downloading
|
||||||
threads = []
|
threads = []
|
||||||
|
for i in range(min(self.syncThreadNumber, itemNumber)):
|
||||||
thread = ThreadedGetMetadata(getMetadataQueue,
|
thread = ThreadedGetMetadata(getMetadataQueue,
|
||||||
processMetadataQueue,
|
processMetadataQueue,
|
||||||
getMetadataLock,
|
getMetadataLock,
|
||||||
|
@ -887,6 +992,18 @@ class LibrarySync(Thread):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
log.info("Sync threads finished")
|
log.info("Sync threads finished")
|
||||||
|
if (settings('FanartTV') == 'true' and
|
||||||
|
itemType in ('Movies', 'TVShows')):
|
||||||
|
# Save to queue for later processing
|
||||||
|
typus = {'Movies': 'movie', 'TVShows': 'tvshow'}[itemType]
|
||||||
|
for item in self.updatelist:
|
||||||
|
if item['mediaType'] in ('movie', 'tvshow'):
|
||||||
|
self.fanartqueue.put({
|
||||||
|
'itemId': item['itemId'],
|
||||||
|
'class': itemType,
|
||||||
|
'mediaType': typus,
|
||||||
|
'refresh': False
|
||||||
|
})
|
||||||
self.updatelist = []
|
self.updatelist = []
|
||||||
|
|
||||||
@LogTime
|
@LogTime
|
||||||
|
@ -1284,7 +1401,17 @@ class LibrarySync(Thread):
|
||||||
if item['state'] == 9:
|
if item['state'] == 9:
|
||||||
successful = self.process_deleteditems(item)
|
successful = self.process_deleteditems(item)
|
||||||
else:
|
else:
|
||||||
successful = self.process_newitems(item)
|
successful, item = self.process_newitems(item)
|
||||||
|
if successful and settings('FanartTV') == 'true':
|
||||||
|
if item['mediatype'] in ('movie', 'show'):
|
||||||
|
mediaType = {'movie': 'Movie'}[item['mediatype']]
|
||||||
|
cls = {'movie': 'Movies'}[item['mediatype']]
|
||||||
|
self.fanartqueue.put({
|
||||||
|
'itemId': item['ratingKey'],
|
||||||
|
'class': cls,
|
||||||
|
'mediaType': mediaType,
|
||||||
|
'refresh': False
|
||||||
|
})
|
||||||
if successful is True:
|
if successful is True:
|
||||||
deleteListe.append(i)
|
deleteListe.append(i)
|
||||||
else:
|
else:
|
||||||
|
@ -1310,13 +1437,16 @@ class LibrarySync(Thread):
|
||||||
def process_newitems(self, item):
|
def process_newitems(self, item):
|
||||||
ratingKey = item['ratingKey']
|
ratingKey = item['ratingKey']
|
||||||
xml = PF.GetPlexMetadata(ratingKey)
|
xml = PF.GetPlexMetadata(ratingKey)
|
||||||
if xml in (None, 401):
|
try:
|
||||||
log.error('Could not download data for %s, skipping' % ratingKey)
|
mediatype = xml[0].attrib['type']
|
||||||
return False
|
except (IndexError, KeyError, TypeError):
|
||||||
|
log.error('Could not download metadata for %s' % ratingKey)
|
||||||
|
return False, item
|
||||||
log.debug("Processing new/updated PMS item: %s" % ratingKey)
|
log.debug("Processing new/updated PMS item: %s" % ratingKey)
|
||||||
viewtag = xml.attrib.get('librarySectionTitle')
|
viewtag = xml.attrib.get('librarySectionTitle')
|
||||||
viewid = xml.attrib.get('librarySectionID')
|
viewid = xml.attrib.get('librarySectionID')
|
||||||
mediatype = xml[0].attrib.get('type')
|
# Attach mediatype for later
|
||||||
|
item['mediatype'] = mediatype
|
||||||
if mediatype == 'movie':
|
if mediatype == 'movie':
|
||||||
self.videoLibUpdate = True
|
self.videoLibUpdate = True
|
||||||
with itemtypes.Movies() as movie:
|
with itemtypes.Movies() as movie:
|
||||||
|
@ -1335,7 +1465,7 @@ class LibrarySync(Thread):
|
||||||
music.add_updateSong(xml[0],
|
music.add_updateSong(xml[0],
|
||||||
viewtag=viewtag,
|
viewtag=viewtag,
|
||||||
viewid=viewid)
|
viewid=viewid)
|
||||||
return True
|
return True, item
|
||||||
|
|
||||||
def process_deleteditems(self, item):
|
def process_deleteditems(self, item):
|
||||||
if item.get('type') == 1:
|
if item.get('type') == 1:
|
||||||
|
@ -1362,10 +1492,19 @@ class LibrarySync(Thread):
|
||||||
"processing queue" for later
|
"processing queue" for later
|
||||||
"""
|
"""
|
||||||
for item in data:
|
for item in data:
|
||||||
typus = item.get('type')
|
if 'tv.plex' in item.get('identifier', ''):
|
||||||
state = item.get('state')
|
# Ommit Plex DVR messages - the Plex IDs are not corresponding
|
||||||
|
# (DVR ratingKeys are not unique and might correspond to a
|
||||||
|
# movie or episode)
|
||||||
|
continue
|
||||||
|
typus = int(item.get('type', 0))
|
||||||
|
state = int(item.get('state', 0))
|
||||||
if state == 9 or typus in (1, 4, 10):
|
if state == 9 or typus in (1, 4, 10):
|
||||||
itemId = item.get('itemID')
|
# Only process deleted items OR movies, episodes, tracks/songs
|
||||||
|
itemId = str(item.get('itemID', '0'))
|
||||||
|
if itemId == '0':
|
||||||
|
log.warn('Received malformed PMS message: %s' % item)
|
||||||
|
continue
|
||||||
# Have we already added this element?
|
# Have we already added this element?
|
||||||
for existingItem in self.itemsToProcess:
|
for existingItem in self.itemsToProcess:
|
||||||
if existingItem['ratingKey'] == itemId:
|
if existingItem['ratingKey'] == itemId:
|
||||||
|
@ -1400,7 +1539,7 @@ class LibrarySync(Thread):
|
||||||
sessionKey = item.get('sessionKey')
|
sessionKey = item.get('sessionKey')
|
||||||
# Do we already have a sessionKey stored?
|
# Do we already have a sessionKey stored?
|
||||||
if sessionKey not in self.sessionKeys:
|
if sessionKey not in self.sessionKeys:
|
||||||
if window('plex_serverowned') == 'false':
|
if settings('plex_serverowned') == 'false':
|
||||||
# Not our PMS, we are not authorized to get the
|
# Not our PMS, we are not authorized to get the
|
||||||
# sessions
|
# sessions
|
||||||
# On the bright side, it must be us playing :-)
|
# On the bright side, it must be us playing :-)
|
||||||
|
@ -1419,7 +1558,10 @@ class LibrarySync(Thread):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
currSess = self.sessionKeys[sessionKey]
|
currSess = self.sessionKeys[sessionKey]
|
||||||
if window('plex_serverowned') != 'false':
|
if window('plex_currently_playing_itemid') == ratingKey:
|
||||||
|
# Don't update what we already know
|
||||||
|
continue
|
||||||
|
if settings('plex_serverowned') != 'false':
|
||||||
# Identify the user - same one as signed on with PKC? Skip
|
# Identify the user - same one as signed on with PKC? Skip
|
||||||
# update if neither session's username nor userid match
|
# update if neither session's username nor userid match
|
||||||
# (Owner sometime's returns id '1', not always)
|
# (Owner sometime's returns id '1', not always)
|
||||||
|
@ -1484,6 +1626,30 @@ class LibrarySync(Thread):
|
||||||
with itemFkt() as Fkt:
|
with itemFkt() as Fkt:
|
||||||
Fkt.updatePlaystate(item)
|
Fkt.updatePlaystate(item)
|
||||||
|
|
||||||
|
def fanartSync(self, refresh=False):
|
||||||
|
"""
|
||||||
|
Checks all Plex movies and TV shows whether they still need fanart
|
||||||
|
|
||||||
|
refresh=True Force refresh all external fanart
|
||||||
|
"""
|
||||||
|
items = []
|
||||||
|
typus = {
|
||||||
|
'Movie': 'Movies',
|
||||||
|
'Series': 'TVShows'
|
||||||
|
}
|
||||||
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
|
for plextype in typus:
|
||||||
|
items.extend(emby_db.itemsByType(plextype))
|
||||||
|
# Shuffle the list to not always start out identically
|
||||||
|
shuffle(items)
|
||||||
|
for item in items:
|
||||||
|
self.fanartqueue.put({
|
||||||
|
'itemId': item['plexId'],
|
||||||
|
'mediaType': item['kodi_type'],
|
||||||
|
'class': typus[item['plex_type']],
|
||||||
|
'refresh': refresh
|
||||||
|
})
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
self.run_internal()
|
self.run_internal()
|
||||||
|
@ -1527,6 +1693,9 @@ class LibrarySync(Thread):
|
||||||
if self.enableMusic:
|
if self.enableMusic:
|
||||||
advancedSettingsXML()
|
advancedSettingsXML()
|
||||||
|
|
||||||
|
if settings('FanartTV') == 'true':
|
||||||
|
self.fanartthread.start()
|
||||||
|
|
||||||
while not threadStopped():
|
while not threadStopped():
|
||||||
|
|
||||||
# In the event the server goes offline
|
# In the event the server goes offline
|
||||||
|
@ -1640,6 +1809,21 @@ class LibrarySync(Thread):
|
||||||
forced=True,
|
forced=True,
|
||||||
icon="error")
|
icon="error")
|
||||||
window('plex_dbScan', clear=True)
|
window('plex_dbScan', clear=True)
|
||||||
|
elif window('plex_runLibScan') == 'fanart':
|
||||||
|
window('plex_runLibScan', clear=True)
|
||||||
|
# Only look for missing fanart (No)
|
||||||
|
# or refresh all fanart (Yes)
|
||||||
|
self.fanartSync(refresh=self.dialog.yesno(
|
||||||
|
heading=addonName,
|
||||||
|
line1=lang(39223),
|
||||||
|
nolabel=lang(39224),
|
||||||
|
yeslabel=lang(39225)))
|
||||||
|
elif window('plex_runLibScan') == 'del_textures':
|
||||||
|
window('plex_runLibScan', clear=True)
|
||||||
|
window('plex_dbScan', value="true")
|
||||||
|
import artwork
|
||||||
|
artwork.Artwork().fullTextureCacheSync()
|
||||||
|
window('plex_dbScan', clear=True)
|
||||||
else:
|
else:
|
||||||
now = getUnixTimestamp()
|
now = getUnixTimestamp()
|
||||||
if (now - lastSync > fullSyncInterval and
|
if (now - lastSync > fullSyncInterval and
|
||||||
|
@ -1666,9 +1850,7 @@ class LibrarySync(Thread):
|
||||||
# Only do this once every 10 seconds
|
# Only do this once every 10 seconds
|
||||||
if now - lastProcessing > 10:
|
if now - lastProcessing > 10:
|
||||||
lastProcessing = now
|
lastProcessing = now
|
||||||
window('plex_dbScan', value="true")
|
|
||||||
processItems()
|
processItems()
|
||||||
window('plex_dbScan', clear=True)
|
|
||||||
# See if there is a PMS message we need to handle
|
# See if there is a PMS message we need to handle
|
||||||
try:
|
try:
|
||||||
message = queue.get(block=False)
|
message = queue.get(block=False)
|
||||||
|
@ -1677,10 +1859,8 @@ class LibrarySync(Thread):
|
||||||
continue
|
continue
|
||||||
# Got a message from PMS; process it
|
# Got a message from PMS; process it
|
||||||
else:
|
else:
|
||||||
window('plex_dbScan', value="true")
|
|
||||||
processMessage(message)
|
processMessage(message)
|
||||||
queue.task_done()
|
queue.task_done()
|
||||||
window('plex_dbScan', clear=True)
|
|
||||||
# NO sleep!
|
# NO sleep!
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -11,7 +11,6 @@ import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
import xbmcplugin
|
import xbmcplugin
|
||||||
|
|
||||||
import artwork
|
|
||||||
import playutils as putils
|
import playutils as putils
|
||||||
import playlist
|
import playlist
|
||||||
from utils import window, settings, tryEncode, tryDecode
|
from utils import window, settings, tryEncode, tryDecode
|
||||||
|
@ -39,7 +38,6 @@ class PlaybackUtils():
|
||||||
self.userid = window('currUserId')
|
self.userid = window('currUserId')
|
||||||
self.server = window('pms_server')
|
self.server = window('pms_server')
|
||||||
|
|
||||||
self.artwork = artwork.Artwork()
|
|
||||||
if self.API.getType() == 'track':
|
if self.API.getType() == 'track':
|
||||||
self.pl = playlist.Playlist(typus='music')
|
self.pl = playlist.Playlist(typus='music')
|
||||||
else:
|
else:
|
||||||
|
@ -208,6 +206,7 @@ class PlaybackUtils():
|
||||||
(homeScreen and not sizePlaylist)):
|
(homeScreen and not sizePlaylist)):
|
||||||
# Playlist was created just now, play it.
|
# Playlist was created just now, play it.
|
||||||
log.info("Play playlist.")
|
log.info("Play playlist.")
|
||||||
|
xbmcplugin.endOfDirectory(int(sys.argv[1]), True, False, False)
|
||||||
xbmc.Player().play(kodiPl, startpos=startPos)
|
xbmc.Player().play(kodiPl, startpos=startPos)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -305,7 +305,7 @@ class Player(xbmc.Player):
|
||||||
|
|
||||||
self.stopAll()
|
self.stopAll()
|
||||||
|
|
||||||
window('Plex_currently_playing_itemid', clear=True)
|
window('plex_currently_playing_itemid', clear=True)
|
||||||
window('plex_customplaylist', clear=True)
|
window('plex_customplaylist', clear=True)
|
||||||
window('plex_customplaylist.seektime', clear=True)
|
window('plex_customplaylist.seektime', clear=True)
|
||||||
window('plex_customplaylist.seektime', clear=True)
|
window('plex_customplaylist.seektime', clear=True)
|
||||||
|
|
|
@ -102,7 +102,7 @@ class SubscriptionManager:
|
||||||
while not keyid:
|
while not keyid:
|
||||||
if count > 300:
|
if count > 300:
|
||||||
break
|
break
|
||||||
keyid = window('Plex_currently_playing_itemid')
|
keyid = window('plex_currently_playing_itemid')
|
||||||
xbmc.sleep(100)
|
xbmc.sleep(100)
|
||||||
count += 1
|
count += 1
|
||||||
if keyid:
|
if keyid:
|
||||||
|
@ -149,7 +149,7 @@ class SubscriptionManager:
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
# Don't tell anyone if we don't know a Plex ID and are still playing
|
# Don't tell anyone if we don't know a Plex ID and are still playing
|
||||||
# (e.g. no stop called). Used for e.g. PVR/TV without PKC usage
|
# (e.g. no stop called). Used for e.g. PVR/TV without PKC usage
|
||||||
if (not window('Plex_currently_playing_itemid')
|
if (not window('plex_currently_playing_itemid')
|
||||||
and not self.lastplayers):
|
and not self.lastplayers):
|
||||||
return True
|
return True
|
||||||
players = self.js.getPlayers()
|
players = self.js.getPlayers()
|
||||||
|
|
|
@ -174,26 +174,9 @@ class UserClient(threading.Thread):
|
||||||
window('plex_machineIdentifier', value=self.machineIdentifier)
|
window('plex_machineIdentifier', value=self.machineIdentifier)
|
||||||
window('plex_servername', value=self.servername)
|
window('plex_servername', value=self.servername)
|
||||||
window('plex_authenticated', value='true')
|
window('plex_authenticated', value='true')
|
||||||
window('plex_serverowned', value=settings('plex_serverowned'))
|
|
||||||
|
|
||||||
window('useDirectPaths', value='true'
|
window('useDirectPaths', value='true'
|
||||||
if settings('useDirectPaths') == "1" else 'false')
|
if settings('useDirectPaths') == "1" else 'false')
|
||||||
window('replaceSMB', value='true'
|
|
||||||
if settings('replaceSMB') == "true" else 'false')
|
|
||||||
window('remapSMB', value='true'
|
|
||||||
if settings('remapSMB') == "true" else 'false')
|
|
||||||
if window('remapSMB') == 'true':
|
|
||||||
items = ('movie', 'tv', 'music')
|
|
||||||
for item in items:
|
|
||||||
# Normalize! Get rid of potential (back)slashes at the end
|
|
||||||
org = settings('remapSMB%sOrg' % item)
|
|
||||||
new = settings('remapSMB%sNew' % item)
|
|
||||||
if org.endswith('\\') or org.endswith('/'):
|
|
||||||
org = org[:-1]
|
|
||||||
if new.endswith('\\') or new.endswith('/'):
|
|
||||||
new = new[:-1]
|
|
||||||
window('remapSMB%sOrg' % item, value=org)
|
|
||||||
window('remapSMB%sNew' % item, value=new)
|
|
||||||
|
|
||||||
# Start DownloadUtils session
|
# Start DownloadUtils session
|
||||||
doUtils.startSession(reset=True)
|
doUtils.startSession(reset=True)
|
||||||
|
|
|
@ -182,7 +182,7 @@ def kodiSQL(media_type="video"):
|
||||||
else:
|
else:
|
||||||
dbPath = getKodiVideoDBPath()
|
dbPath = getKodiVideoDBPath()
|
||||||
|
|
||||||
connection = sqlite3.connect(dbPath)
|
connection = sqlite3.connect(dbPath, timeout=15.0)
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
def getKodiVideoDBPath():
|
def getKodiVideoDBPath():
|
||||||
|
|
|
@ -19,29 +19,35 @@
|
||||||
<setting id="accessToken" type="text" visible="false" default="" />
|
<setting id="accessToken" type="text" visible="false" default="" />
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="plex.tv"><!-- plex.tv -->
|
<category label="Plex">
|
||||||
|
<setting type="lsep" label="plex.tv"/>
|
||||||
<setting id="plex_status" label="39071" type="text" default="Not logged in to plex.tv" enable="false" /><!-- Current plex.tv status: -->
|
<setting id="plex_status" label="39071" type="text" default="Not logged in to plex.tv" enable="false" /><!-- Current plex.tv status: -->
|
||||||
|
<setting id="plexLogin" label="Plex user:" type="text" default="" enable="false" />
|
||||||
<setting type="sep" text=""/>
|
<setting type="sep" text=""/>
|
||||||
|
|
||||||
<setting id="myplexlogin" label="39025" type="bool" default="true" /> <!-- Log into plex.tv on startup -->
|
<setting id="myplexlogin" label="39025" type="bool" default="true" /> <!-- Log into plex.tv on startup -->
|
||||||
<setting label="39209" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=togglePlexTV)" option="close" />
|
<setting label="39209" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=togglePlexTV)" option="close" />
|
||||||
|
|
||||||
<setting id="plexLogin" label="plex.tv username" type="text" default="" visible="false" />
|
|
||||||
<setting id="plexhome" label="Plex home in use" type="bool" default="" visible="false" />
|
<setting id="plexhome" label="Plex home in use" type="bool" default="" visible="false" />
|
||||||
<setting id="plexToken" label="plexToken" type="text" default="" visible="false" />
|
<setting id="plexToken" label="plexToken" type="text" default="" visible="false" />
|
||||||
<setting id="plexHomeSize" type="number" default="1" visible="false" />
|
<setting id="plexHomeSize" type="number" default="1" visible="false" />
|
||||||
|
|
||||||
|
<setting type="lsep" label="39008" />
|
||||||
|
<setting id="plexCompanion" label="39004" type="bool" default="true" />
|
||||||
|
<setting id="deviceNameOpt" label="30504" type="bool" default="false" />
|
||||||
|
<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="Kodi" />
|
||||||
|
<setting id="companionPort" label="39005" type="number" default="3005" option="int" visible="eq(-3,true)"/>
|
||||||
|
|
||||||
|
<setting id="plex_restricteduser" type="bool" default="false" visible="false"/>
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="30506"><!-- Sync Options -->
|
<category label="30506"><!-- Sync Options -->
|
||||||
<setting type="lsep" label="30537" /><!-- Restart if you make changes -->
|
<setting type="lsep" label="30537" /><!-- Restart if you make changes -->
|
||||||
<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" />
|
<setting id="dbSyncIndicator" label="30507" type="bool" default="true" /><!-- show syncing progress -->
|
||||||
<setting type="sep" /><!-- show syncing progress -->
|
<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="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 id="enableTextureCache" label="30512" type="bool" default="true" /> <!-- Force Artwork Caching -->
|
|
||||||
<setting id="imageCacheLimit" type="enum" label="30513" values="Disabled|5|10|15|20|25" default="5" visible="eq(-1,true)" subsetting="true" /> <!-- Limit artwork cache threads -->
|
|
||||||
<setting id="serverSync" type="bool" label="30514" default="true" visible="false"/><!-- Enable fast startup (requires server plugin) -->
|
|
||||||
|
|
||||||
<setting type="lsep" label="39052" /><!-- Background Sync -->
|
<setting type="lsep" label="39052" /><!-- Background Sync -->
|
||||||
<setting id="enableBackgroundSync" type="bool" label="39026" default="true" visible="true"/>
|
<setting id="enableBackgroundSync" type="bool" label="39026" default="true" visible="true"/>
|
||||||
<setting id="saftyMargin" type="slider" label="39051" default="60" option="int" range="10,1,300" visible="eq(-1,true)"/>
|
<setting id="saftyMargin" type="slider" label="39051" default="60" option="int" range="10,1,300" visible="eq(-1,true)"/>
|
||||||
|
@ -49,8 +55,6 @@
|
||||||
<setting id="dbSyncScreensaver" type="bool" label="39062" default="false" /><!--Sync when screensaver is deactivated-->
|
<setting id="dbSyncScreensaver" type="bool" label="39062" default="false" /><!--Sync when screensaver is deactivated-->
|
||||||
|
|
||||||
<setting type="lsep" label="30538" /><!-- Complete Re-Sync necessary -->
|
<setting type="lsep" label="30538" /><!-- Complete Re-Sync necessary -->
|
||||||
<setting id="FanartTV" label="30539" type="bool" default="false" /><!-- Download additional art from FanArtTV -->
|
|
||||||
<setting id="setFanartTV" label="30540" type="bool" default="true" /><!-- Download movie set/collection art from FanArtTV -->
|
|
||||||
<setting id="enableMusic" type="bool" label="30509" default="true" />
|
<setting id="enableMusic" type="bool" label="30509" default="true" />
|
||||||
<setting id="useDirectPaths" type="enum" label="30511" values="Addon(Default)|Native(Direct paths)" default="0" visible="true"/> <!-- Playback mode -->
|
<setting id="useDirectPaths" type="enum" label="30511" values="Addon(Default)|Native(Direct paths)" default="0" visible="true"/> <!-- Playback mode -->
|
||||||
|
|
||||||
|
@ -104,6 +108,12 @@
|
||||||
<setting id="bestTrailer" type="bool" label="30542" default="true" />
|
<setting id="bestTrailer" type="bool" label="30542" default="true" />
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
|
<category label="30544"><!-- artwork -->
|
||||||
|
<setting id="enableTextureCache" label="30512" type="bool" default="true" /> <!-- Force Artwork Caching -->
|
||||||
|
<setting id="FanartTV" label="30539" type="bool" default="false" /><!-- Download additional art from FanArtTV -->
|
||||||
|
<setting label="39222" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=fanart)" option="close" visible="eq(-1,true)" /> <!-- Look for missing fanart on FanartTV now -->
|
||||||
|
<setting label="39020" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=texturecache)" option="close" /> <!-- Cache all images to Kodi texture cache now -->
|
||||||
|
</category>
|
||||||
<!--
|
<!--
|
||||||
<category label="30235" visible="false">
|
<category label="30235" visible="false">
|
||||||
<setting id="enableCoverArt" type="bool" label="30157" default="true" visible="false"/>
|
<setting id="enableCoverArt" type="bool" label="30157" default="true" visible="false"/>
|
||||||
|
@ -116,16 +126,6 @@
|
||||||
<setting id="newmusictime" type="number" label="30533" visible="false" default="2" option="int" subsetting="true" />
|
<setting id="newmusictime" type="number" label="30533" visible="false" default="2" option="int" subsetting="true" />
|
||||||
</category>
|
</category>
|
||||||
-->
|
-->
|
||||||
<category label="Plex Companion">
|
|
||||||
<setting type="lsep" label="39008" />
|
|
||||||
<setting id="plexCompanion" label="39004" type="bool" default="true" />
|
|
||||||
<setting id="deviceNameOpt" label="30504" type="bool" default="false" />
|
|
||||||
<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="Kodi" />
|
|
||||||
<setting id="companionPort" label="39005" type="number" default="3005" option="int" visible="eq(-3,true)"/>
|
|
||||||
|
|
||||||
<setting id="plex_restricteduser" type="bool" default="false" visible="false"/>
|
|
||||||
|
|
||||||
</category>
|
|
||||||
|
|
||||||
<category label="39073"><!-- Appearance Tweaks -->
|
<category label="39073"><!-- Appearance Tweaks -->
|
||||||
<setting id="connectMsg" type="bool" label="30249" default="true" />
|
<setting id="connectMsg" type="bool" label="30249" default="true" />
|
||||||
|
@ -144,7 +144,6 @@
|
||||||
<setting id="logLevel" type="enum" label="30004" values="Disabled|Info|Debug" default="1" />
|
<setting id="logLevel" type="enum" label="30004" values="Disabled|Info|Debug" default="1" />
|
||||||
<setting id="startupDelay" type="number" label="30529" default="0" option="int" />
|
<setting id="startupDelay" type="number" label="30529" default="0" option="int" />
|
||||||
<setting label="39018" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=repair)" option="close" /> <!-- Repair local database (force update all content) -->
|
<setting label="39018" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=repair)" option="close" /> <!-- Repair local database (force update all content) -->
|
||||||
<setting label="39020" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=texturecache)" option="close" /> <!-- Cache all images to Kodi texture cache -->
|
|
||||||
<setting label="30535" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=deviceid)" /><!-- Reset device id uuid -->
|
<setting label="30535" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=deviceid)" /><!-- Reset device id uuid -->
|
||||||
<setting label="39021" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=thememedia)" option="close" visible="false" /> <!-- Sync Plex Theme Media to Kodi -->
|
<setting label="39021" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=thememedia)" option="close" visible="false" /> <!-- Sync Plex Theme Media to Kodi -->
|
||||||
<setting type="lsep" label="39049" /><!-- Nothing works? Try a full reset -->
|
<setting type="lsep" label="39049" /><!-- Nothing works? Try a full reset -->
|
||||||
|
|
|
@ -85,6 +85,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"
|
||||||
|
% settings('low_powered_device'))
|
||||||
log.warn("Log Level: %s" % logLevel)
|
log.warn("Log Level: %s" % logLevel)
|
||||||
|
|
||||||
# Reset window props for profile switch
|
# Reset window props for profile switch
|
||||||
|
@ -97,9 +99,6 @@ class Service():
|
||||||
"plex_runLibScan", "plex_username", "pms_token", "plex_token",
|
"plex_runLibScan", "plex_username", "pms_token", "plex_token",
|
||||||
"pms_server", "plex_machineIdentifier", "plex_servername",
|
"pms_server", "plex_machineIdentifier", "plex_servername",
|
||||||
"plex_authenticated", "PlexUserImage", "useDirectPaths",
|
"plex_authenticated", "PlexUserImage", "useDirectPaths",
|
||||||
"replaceSMB", "remapSMB", "remapSMBmovieOrg", "remapSMBtvOrg",
|
|
||||||
"remapSMBmusicOrg", "remapSMBmovieNew", "remapSMBtvNew",
|
|
||||||
"remapSMBmusicNew", "remapSMBphotoOrg", "remapSMBphotoNew",
|
|
||||||
"suspend_LibraryThread", "plex_terminateNow",
|
"suspend_LibraryThread", "plex_terminateNow",
|
||||||
"kodiplextimeoffset", "countError", "countUnauthorized"
|
"kodiplextimeoffset", "countError", "countUnauthorized"
|
||||||
]
|
]
|
||||||
|
@ -129,7 +128,7 @@ class Service():
|
||||||
initialsetup.InitialSetup().setup()
|
initialsetup.InitialSetup().setup()
|
||||||
|
|
||||||
# Queue for background sync
|
# Queue for background sync
|
||||||
queue = Queue.Queue(maxsize=200)
|
queue = Queue.Queue()
|
||||||
|
|
||||||
connectMsg = True if settings('connectMsg') == 'true' else False
|
connectMsg = True if settings('connectMsg') == 'true' else False
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue