diff --git a/README.md b/README.md
index afd25786..253850e5 100644
--- a/README.md
+++ b/README.md
@@ -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.
+### 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](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
-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.
-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.
@@ -107,5 +110,5 @@ The addon is not (and will not be) compatible with the MySQL database replacemen
### 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!!
-- 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).
diff --git a/addon.xml b/addon.xml
index 06023b34..8d848b4a 100644
--- a/addon.xml
+++ b/addon.xml
@@ -1,7 +1,7 @@
@@ -19,10 +19,10 @@
all
en
GNU GENERAL PUBLIC LICENSE. Version 2, June 1991
-
+ https://forums.plex.tv
https://github.com/croneter/PlexKodiConnect
-
+
- Connect Kodi to your Plex Media Server
+ 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!
\ No newline at end of file
diff --git a/changelog.txt b/changelog.txt
index 618ec8d5..c7e9b2ac 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -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
- 1.3.1 and 1.3.2 for everyone
- Fix direct play & transcoding subtitles, finally!
diff --git a/default.py b/default.py
index 5c661387..ff769506 100644
--- a/default.py
+++ b/default.py
@@ -10,6 +10,7 @@ import urlparse
import xbmc
import xbmcaddon
import xbmcgui
+import xbmcplugin
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
@@ -99,6 +100,10 @@ class Main():
entrypoint.getVideoFiles(plexid, plexpath)
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
if ("/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0] or
"/Extras" in sys.argv[2]):
@@ -156,22 +161,20 @@ class Main():
"Unable to run the sync, the add-on is not connected "
"to a Plex server.")
log.error("Not connected to a PMS.")
- return
-
else:
if mode == 'repair':
utils.window('plex_runLibScan', value="repair")
- log.warn("Requesting repair lib sync")
+ log.info("Requesting repair lib sync")
elif mode == 'manualsync':
- log.warn("Requesting full library scan")
+ log.info("Requesting full library scan")
utils.window('plex_runLibScan', value="full")
elif mode == "texturecache":
- import artwork
- artwork.Artwork().fullTextureCacheSync()
-
+ utils.window('plex_runLibScan', value='del_textures')
else:
entrypoint.doMainListing()
+ # Prevent Kodi from hanging in an infinite loop waiting for more
+ xbmcplugin.endOfDirectory(int(sys.argv[1]))
if __name__ == "__main__":
log.info('plugin.video.plexkodiconnect started')
diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml
index 5850605a..913a3512 100644
--- a/resources/language/English/strings.xml
+++ b/resources/language/English/strings.xml
@@ -302,11 +302,12 @@
Users must log in every time Kodi restarts
RESTART KODI IF YOU MAKE ANY CHANGES
Complete Re-Sync necessary
- Download additional art from FanArtTV (slower!)
+ Download additional art from FanArtTV
Download movie set/collection art from FanArtTV
Don't ask to pick a certain stream/quality
Always pick best quality for trailers
-
+ Kodi runs on a low-power device (e.g. Raspberry Pi)
+ Artwork
Welcome
@@ -348,12 +349,12 @@
- Number of trailers to play before a movie
Boost audio when transcoding
Burnt-in subtitle size
- Limit download sync threads (recommended for rpi: 1)
+ Limit download sync threads (rec. for rpi: 1)
Enable Plex Companion
Plex Companion Port (change only if needed)
Activate Plex Companion debug log
Activate Plex Companion GDM debug log
- Allows flinging media to Kodi through Plex
+ Plex Companion: Allows flinging media to Kodi through Plex
Could not login to plex.tv. Please try signing in again.
Problems connecting to plex.tv. Network or internet issue?
Could not find any Plex server in the network. Aborting...
@@ -366,7 +367,7 @@
[COLOR yellow]Repair local database (force update all content)[/COLOR]
[COLOR red]Partial or full reset of Database and PKC[/COLOR]
- [COLOR yellow]Cache all images to Kodi texture cache[/COLOR]
+ [COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]
[COLOR yellow]Sync Emby Theme Media to Kodi[/COLOR]
local
Failed to authenticate. Did you login to plex.tv?
@@ -408,7 +409,7 @@
Extend Plex TV Series "On Deck" view to all shows
Recently Added: Append show title to episode
Recently Added: Append season- and episode-number SxxExx
- Would you like to download additional artwork from FanArtTV? Sync will be slower!
+ Would you like to download additional artwork from FanArtTV in the background?
Sync when screensaver is deactivated
Force Transcode Hi10P
Recently Added: Also show already watched episodes
@@ -446,10 +447,14 @@
Abort (Yes) or save address anyway (No)?
connected
plex.tv toggle successful
+ [COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]
+ Only look for missing fanart or refresh all fanart? The scan will take quite a while and happen in the background.
+ Refresh all
+ Missing only
- Running the image cache process can take some time. Are you sure you want continue?
+ Running the image cache process can take some time. It will happen in the background. Are you sure you want continue?
Reset all existing cache data first?
diff --git a/resources/language/German/strings.xml b/resources/language/German/strings.xml
index d35b2963..789debcf 100644
--- a/resources/language/German/strings.xml
+++ b/resources/language/German/strings.xml
@@ -28,10 +28,12 @@
Benutzer müssen sich bei jedem Neustart von Kodi neu anmelden
BEI ÄNDERUNGEN KODI NEU STARTEN
Komplette Neusynchronisierung nötig
- Zusätzliche Bilder von FanArtTV herunterladen (langsamer!)
+ Zusätzliche Bilder von FanArtTV herunterladen
FanArtTV Film-Sets/Collections Bilder herunterladen
Nicht fragen, welcher Stream/Qualität gespielt wird
Trailer immer in der besten Qualität abspielen
+ Kodi läuft auf langsamer Hardware (z.B. Raspberry Pi)
+ Artwork
Verbindung
Netzwerk
@@ -286,12 +288,12 @@
- Anzahl abzuspielender Trailer vor einem Film
Audio Verstärkung (audio boost) wenn transkodiert wird
Grösse des Untertitels, falls burnt-in
- Anzahl Download Sync Threads beschränken (Empfehlung für rpi: 1)
+ Download Sync Threads beschränken (Empfehlung RPI: 1)
Plex Companion aktivieren
Plex Companion Port (nur bei Bedarf ändern)
Plex Companion debug log aktivieren
Plex Companion GDM debug log aktivieren
- Spiele Inhalt von anderen Plex Geräten ab
+ Plex Companion: Spiele Inhalt von anderen Plex Geräten ab
Login bei plex.tv fehlgeschlagen. Bitte erneut versuchen.
Netzwerk Verbindungsprobleme für plex.tv. Funktionieren Netzwerk und/oder Internet?
Konnte keine Plex Server im Netzwerk finden. Abbruch...
@@ -304,7 +306,7 @@
[COLOR yellow]Lokale Datenbank reparieren (allen Inhalt aktualisieren)[/COLOR]
[COLOR red]Datenbank und auf Wunsch PKC zurücksetzen[/COLOR]
- [COLOR yellow]Alle Plex Bilder in Kodi zwischenspeichern[/COLOR]
+ [COLOR yellow]Alle Plex Bilder jetzt in Kodi zwischenspeichern[/COLOR]
[COLOR yellow]Plex Themes zu Kodi synchronisieren[/COLOR]
lokal
Plex Media Server Authentifizierung fehlgeschlagen. Haben Sie sich bei plex.tv eingeloggt?
@@ -346,7 +348,7 @@
Standard Plex Ansicht "Aktuell" auf alle TV Shows erweitern
"Zuletzt hinzugefügt": Serien- an Episoden-Titel anfügen
"Zuletzt hinzugefügt": Staffel und Episode anfügen, SxxExx
- Zusätzliche Bilder von FanArtTV herunterladen? Die Synchronisierung wird länger dauern!
+ Zusätzliche Bilder von FanArtTV im Hintergrund herunterladen?
Sync wenn Bildschirmschoner deaktiviert wird
Hi10p Codec Transkodierung erzwingen
"Zuletzt hinzugefügt": gesehene Folgen anzeigen
@@ -384,9 +386,14 @@
Abbrechen (Ja) oder PMS Adresse trotzdem speichern (Nein)?
verbunden
plex.tv wechsel OK
+ [COLOR yellow]Jetzt zusätzliche Bilder auf FanartTV suchen[/COLOR]
+ Nur nach fehlender Fanart suchen oder alle Fanart neu herunterladen? Die Suche wird lange dauern und komplett im Hintergrund stattfinden!
+ Alle
+ Fehlend
+
- Alle Plex Bilder in Kodi zwischenzuspeichern kann sehr lange dauern. Möchten Sie wirklich fortfahren?
+ Alle Plex Bilder in Kodi zwischenzuspeichern kann lange dauern. Es wird im Hintergrund stattfinden. Möchten Sie wirklich fortfahren?
Sollen erst alle bestehenden Bilder im Cache gelöscht werden?
diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py
index 962dd6d1..db4921c8 100644
--- a/resources/lib/PlexAPI.py
+++ b/resources/lib/PlexAPI.py
@@ -57,7 +57,8 @@ import embydb_functions as embydb
log = logging.getLogger("PLEX."+__name__)
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))
threadQueue.append(t)
- maxThreads = int(settings('imageCacheLimit'))
+ maxThreads = 5
threads = []
# poke PMS, own thread for each PMS
- while(True):
+ while True:
# Remove finished threads
for t in threads:
if not t.isAlive():
@@ -1450,10 +1451,10 @@ class API():
return None
if providername == 'imdb':
- regex = re.compile(r'''/(tt\d+)''')
+ regex = REGEX_IMDB
elif providername == 'tvdb':
# originally e.g. com.plexapp.agents.thetvdb://276564?lang=en
- regex = re.compile(r'''tvdb://(\d+)''')
+ regex = REGEX_TVDB
else:
return None
@@ -1720,7 +1721,7 @@ class API():
'year':
"""
elements = []
- for extra in self.item.find('Extras'):
+ for extra in self.item.findall('Extras'):
# Trailer:
key = extra.attrib.get('key', None)
title = extra.attrib.get('title', None)
@@ -1841,16 +1842,14 @@ class API():
'Backdrop' : LIST with the first entry xml key "art"
}
"""
- item = self.item.attrib
-
allartworks = {
- 'Primary': "",
+ 'Primary': "", # corresponds to Plex poster ('thumb')
'Art': "",
- 'Banner': "",
+ 'Banner': "", # corresponds to Plex banner ('banner') for series
'Logo': "",
- 'Thumb': "",
+ 'Thumb': "", # corresponds to Plex (grand)parent posters (thumb)
'Disc': "",
- 'Backdrop': []
+ 'Backdrop': [] # Corresponds to Plex fanart ('art')
}
# Process backdrops
# Get background artwork URL
@@ -1870,14 +1869,26 @@ class API():
self.__getOneArtwork('parentArt'))
if not allartworks['Primary']:
allartworks['Primary'] = self.__getOneArtwork('parentThumb')
+ return allartworks
- # Plex does not get much artwork - go ahead and get the rest from
- # fanart tv only for movie or tv show
- if settings('FanartTV') == 'true':
- if item.get('type') in ('movie', 'show'):
- externalId = self.getExternalItemId()
- if externalId is not None:
- allartworks = self.getFanartTVArt(externalId, allartworks)
+ def getFanartArtwork(self, allartworks, parentInfo=False):
+ """
+ Downloads additional fanart from third party sources (well, link to
+ fanart only).
+
+ allartworks = {
+ 'Primary': "",
+ 'Art': "",
+ 'Banner': "",
+ 'Logo': "",
+ 'Thumb': "",
+ 'Disc': "",
+ 'Backdrop': []
+ }
+ """
+ externalId = self.getExternalItemId()
+ if externalId is not None:
+ allartworks = self.getFanartTVArt(externalId, allartworks)
return allartworks
def getExternalItemId(self, collection=False):
@@ -2505,16 +2516,16 @@ class API():
'photo': 'photo'
}
typus = types[typus]
- if window('remapSMB') == 'true':
- path = path.replace(window('remapSMB%sOrg' % typus),
- window('remapSMB%sNew' % typus),
+ if settings('remapSMB') == 'true':
+ path = path.replace(settings('remapSMB%sOrg' % typus),
+ settings('remapSMB%sNew' % typus),
1)
# There might be backslashes left over:
path = path.replace('\\', '/')
- elif window('replaceSMB') == 'true':
+ elif settings('replaceSMB') == 'true':
if path.startswith('\\\\'):
path = 'smb:' + path.replace('\\', '/')
- if window('plex_pathverified') == 'true' and forceCheck is False:
+ if settings('plex_pathverified') == 'true' and forceCheck is False:
return path
# exist() needs a / or \ at the end to work for directories
@@ -2535,13 +2546,11 @@ class API():
if self.askToValidate(path):
window('plex_shouldStop', value="true")
path = None
- window('plex_pathverified', value='true')
settings('plex_pathverified', value='true')
else:
path = None
elif forceCheck is False:
- if window('plex_pathverified') != 'true':
- window('plex_pathverified', value='true')
+ if settings('plex_pathverified') != 'true':
settings('plex_pathverified', value='true')
return path
diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py
index 7f4f93aa..7c789bce 100644
--- a/resources/lib/artwork.py
+++ b/resources/lib/artwork.py
@@ -1,21 +1,20 @@
# -*- coding: utf-8 -*-
###############################################################################
-
import json
import logging
import requests
import os
-import urllib
-from sqlite3 import OperationalError
+from urllib import quote_plus, unquote
+from threading import Thread
+import Queue
import xbmc
import xbmcgui
import xbmcvfs
-import image_cache_thread
-from utils import window, settings, language as lang, kodiSQL
-from utils import tryEncode, tryDecode, IfExists
+from utils import window, settings, language as lang, kodiSQL, tryEncode, \
+ tryDecode, IfExists, ThreadMethods, ThreadMethodsAdditionalStop
# Disable annoying requests warnings
import requests.packages.urllib3
@@ -27,162 +26,210 @@ log = logging.getLogger("PLEX."+__name__)
###############################################################################
-class Artwork():
- xbmc_host = 'localhost'
+def setKodiWebServerDetails():
+ """
+ Get the Kodi webserver details - used to set the texture cache
+ """
xbmc_port = None
xbmc_username = 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 = {
-
- "jsonrpc": "2.0",
- "id": 1,
- "method": "Settings.GetSettingValue",
- "params": {
-
- "setting": "services.webserver"
- }
+ web_query = {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "Settings.GetSettingValue",
+ "params": {
+ "setting": "services.webserver"
}
- result = xbmc.executeJSONRPC(json.dumps(web_query))
- result = json.loads(result)
- try:
- xbmc_webserver_enabled = result['result']['value']
- except (KeyError, TypeError):
- xbmc_webserver_enabled = False
-
- if not xbmc_webserver_enabled:
- # Enable the webserver, it is disabled
- web_port = {
-
- "jsonrpc": "2.0",
- "id": 1,
- "method": "Settings.SetSettingValue",
- "params": {
-
- "setting": "services.webserverport",
- "value": 8080
- }
- }
- result = xbmc.executeJSONRPC(json.dumps(web_port))
- self.xbmc_port = 8080
-
- web_user = {
-
- "jsonrpc": "2.0",
- "id": 1,
- "method": "Settings.SetSettingValue",
- "params": {
-
- "setting": "services.webserver",
- "value": True
- }
- }
- result = xbmc.executeJSONRPC(json.dumps(web_user))
- self.xbmc_username = "kodi"
-
-
- # Webserver already enabled
+ }
+ result = xbmc.executeJSONRPC(json.dumps(web_query))
+ result = json.loads(result)
+ try:
+ xbmc_webserver_enabled = result['result']['value']
+ except (KeyError, TypeError):
+ xbmc_webserver_enabled = False
+ if not xbmc_webserver_enabled:
+ # Enable the webserver, it is disabled
web_port = {
-
"jsonrpc": "2.0",
"id": 1,
- "method": "Settings.GetSettingValue",
+ "method": "Settings.SetSettingValue",
"params": {
-
- "setting": "services.webserverport"
+ "setting": "services.webserverport",
+ "value": 8080
}
}
result = xbmc.executeJSONRPC(json.dumps(web_port))
- result = json.loads(result)
- try:
- self.xbmc_port = result['result']['value']
- except (TypeError, KeyError):
- pass
-
+ xbmc_port = 8080
web_user = {
-
"jsonrpc": "2.0",
"id": 1,
- "method": "Settings.GetSettingValue",
+ "method": "Settings.SetSettingValue",
"params": {
-
- "setting": "services.webserverusername"
+ "setting": "services.webserver",
+ "value": True
}
}
result = xbmc.executeJSONRPC(json.dumps(web_user))
- result = json.loads(result)
- try:
- self.xbmc_username = result['result']['value']
- except TypeError:
- pass
-
- web_pass = {
-
- "jsonrpc": "2.0",
- "id": 1,
- "method": "Settings.GetSettingValue",
- "params": {
-
- "setting": "services.webserverpassword"
- }
+ xbmc_username = "kodi"
+ # Webserver already enabled
+ web_port = {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "Settings.GetSettingValue",
+ "params": {
+ "setting": "services.webserverport"
}
- result = xbmc.executeJSONRPC(json.dumps(web_pass))
- result = json.loads(result)
- try:
- self.xbmc_password = result['result']['value']
- except TypeError:
- pass
+ }
+ result = xbmc.executeJSONRPC(json.dumps(web_port))
+ result = json.loads(result)
+ try:
+ xbmc_port = result['result']['value']
+ except (TypeError, KeyError):
+ pass
+ web_user = {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "Settings.GetSettingValue",
+ "params": {
+ "setting": "services.webserverusername"
+ }
+ }
+ result = xbmc.executeJSONRPC(json.dumps(web_user))
+ result = json.loads(result)
+ try:
+ xbmc_username = result['result']['value']
+ except TypeError:
+ pass
+ web_pass = {
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "Settings.GetSettingValue",
+ "params": {
+ "setting": "services.webserverpassword"
+ }
+ }
+ result = xbmc.executeJSONRPC(json.dumps(web_pass))
+ result = json.loads(result)
+ try:
+ xbmc_password = result['result']['value']
+ except TypeError:
+ 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):
- # This method will sync all Kodi artwork to textures13.db
- # and cache them locally. This takes diskspace!
- import xbmcaddon
- string = xbmcaddon.Addon().getLocalizedString
-
+ """
+ This method will sync all Kodi artwork to textures13.db
+ and cache them locally. This takes diskspace!
+ """
if not xbmcgui.Dialog().yesno(
- "Image Texture Cache", string(39250)):
+ "Image Texture Cache", lang(39250)):
return
log.info("Doing Image Cache Sync")
- pdialog = xbmcgui.DialogProgress()
- pdialog.create("PlexKodiConnect", "Image Cache Sync")
-
# ask to rest all existing or not
if xbmcgui.Dialog().yesno(
- "Image Texture Cache", string(39251), ""):
+ "Image Texture Cache", lang(39251), ""):
log.info("Resetting all cache data first")
# Remove all existing textures first
path = tryDecode(xbmc.translatePath("special://thumbnails/"))
@@ -210,106 +257,36 @@ class Artwork():
if tableName != "version":
cursor.execute("DELETE FROM " + tableName)
connection.commit()
- cursor.close()
+ connection.close()
# Cache all entries in video DB
connection = kodiSQL('video')
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()
total = len(result)
- log.info("Image cache sync about to process %s images" % total)
- cursor.close()
+ log.info("Image cache sync about to process %s video images" % total)
+ connection.close()
- count = 0
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])
- count += 1
-
# Cache all entries in music DB
connection = kodiSQL('music')
cursor = connection.cursor()
cursor.execute("SELECT url FROM art")
result = cursor.fetchall()
total = len(result)
- log.info("Image cache sync about to process %s images" % total)
- cursor.close()
-
- count = 0
+ log.info("Image cache sync about to process %s music images" % total)
+ connection.close()
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])
- 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):
# Cache a single image url to the texture cache
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):
# Kodi conversion table
kodiart = {
@@ -328,7 +305,8 @@ class Artwork():
for art in artwork:
if art == "Backdrop":
# 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]
backdropsNumber = len(backdrops)
@@ -390,67 +368,58 @@ class Artwork():
cursor=cursor)
def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
- # Possible that the imageurl is an empty string
- if imageUrl:
- cacheimage = False
+ if not imageUrl:
+ # Possible that the imageurl is an empty string
+ return
+ query = ' '.join((
+ "SELECT url",
+ "FROM art",
+ "WHERE media_id = ?",
+ "AND media_type = ?",
+ "AND type = ?"
+ ))
+ cursor.execute(query, (kodiId, mediaType, imageType,))
+ try:
+ # Update the artwork
+ url = cursor.fetchone()[0]
+ except TypeError:
+ # Add the artwork
+ log.debug("Adding Art Link for kodiId: %s (%s)"
+ % (kodiId, imageUrl))
+ query = (
+ '''
+ INSERT INTO art(media_id, media_type, type, url)
+ VALUES (?, ?, ?, ?)
+ '''
+ )
+ cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
+ else:
+ if url == imageUrl:
+ # Only cache artwork if it changed
+ return
+ # Only for the main backdrop, poster
+ if (window('plex_initialScan') != "true" and
+ imageType in ("fanart", "poster")):
+ # Delete current entry before updating with the new one
+ self.deleteCachedArtwork(url)
+ log.debug("Updating Art url for %s kodiId %s %s -> (%s)"
+ % (imageType, kodiId, url, imageUrl))
query = ' '.join((
-
- "SELECT url",
- "FROM art",
+ "UPDATE art",
+ "SET url = ?",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type = ?"
))
- cursor.execute(query, (kodiId, mediaType, imageType,))
- try: # Update the artwork
- url = cursor.fetchone()[0]
+ cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
- except TypeError: # Add the artwork
- cacheimage = True
- log.debug("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl))
-
- query = (
- '''
- INSERT INTO art(media_id, media_type, type, url)
-
- VALUES (?, ?, ?, ?)
- '''
- )
- cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
-
- else: # Only cache artwork if it changed
- if url != imageUrl:
- cacheimage = True
-
- # Only for the main backdrop, poster
- if (window('plex_initialScan') != "true" and
- imageType in ("fanart", "poster")):
- # Delete current entry before updating with the new one
- self.deleteCachedArtwork(url)
-
- log.info("Updating Art url for %s kodiId: %s (%s) -> (%s)"
- % (imageType, kodiId, url, imageUrl))
-
- query = ' '.join((
-
- "UPDATE art",
- "SET url = ?",
- "WHERE media_id = ?",
- "AND media_type = ?",
- "AND type = ?"
- ))
- cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
-
- # Cache fanart and poster in Kodi texture cache
- if cacheimage:
- self.cacheTexture(imageUrl)
+ # Cache fanart and poster in Kodi texture cache
+ self.cacheTexture(imageUrl)
def deleteArtwork(self, kodiId, mediaType, cursor):
-
query = ' '.join((
-
- "SELECT url, type",
+ "SELECT url",
"FROM art",
"WHERE media_id = ?",
"AND media_type = ?"
@@ -458,159 +427,29 @@ class Artwork():
cursor.execute(query, (kodiId, mediaType,))
rows = cursor.fetchall()
for row in rows:
-
- url = row[0]
- imageType = row[1]
- if imageType in ("poster", "fanart"):
- self.deleteCachedArtwork(url)
+ self.deleteCachedArtwork(row[0])
def deleteCachedArtwork(self, url):
# Only necessary to remove and apply a new backdrop or poster
connection = kodiSQL('texture')
cursor = connection.cursor()
-
try:
- cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
+ cursor.execute("SELECT cachedurl FROM texture WHERE url = ?",
+ (url,))
cachedurl = cursor.fetchone()[0]
-
except TypeError:
log.info("Could not find cached url.")
-
- except OperationalError:
- log.info("Database is locked. Skip deletion process.")
-
- else: # Delete thumbnail as well as the entry
+ else:
+ # Delete thumbnail as well as the entry
thumbnails = tryDecode(
xbmc.translatePath("special://thumbnails/%s" % cachedurl))
- log.info("Deleting cached thumbnail: %s" % thumbnails)
- xbmcvfs.delete(thumbnails)
-
+ log.debug("Deleting cached thumbnail: %s" % thumbnails)
try:
- cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
- connection.commit()
- except OperationalError:
- log.debug("Issue deleting url from cache. Skipping.")
-
+ 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,))
+ connection.commit()
finally:
- cursor.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
\ No newline at end of file
+ connection.close()
diff --git a/resources/lib/embydb_functions.py b/resources/lib/embydb_functions.py
index 05a9f4d5..3f173e70 100644
--- a/resources/lib/embydb_functions.py
+++ b/resources/lib/embydb_functions.py
@@ -372,4 +372,31 @@ class Embydb_Functions():
query = "DELETE FROM emby WHERE emby_id LIKE ?"
self.embycursor.execute(query, (plexid+"%",))
-
\ No newline at end of file
+
+ 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
diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py
index e1397ce7..a66744db 100644
--- a/resources/lib/entrypoint.py
+++ b/resources/lib/entrypoint.py
@@ -275,7 +275,6 @@ def doMainListing():
# addDirectoryItem("Add user to session", "plugin://plugin.video.plexkodiconnect/?mode=adduser")
addDirectoryItem(lang(39203), "plugin://plugin.video.plexkodiconnect/?mode=refreshplaylist")
addDirectoryItem(lang(39204), "plugin://plugin.video.plexkodiconnect/?mode=manualsync")
-
xbmcplugin.endOfDirectory(int(sys.argv[1]))
@@ -1363,7 +1362,7 @@ def enterPMS():
settings('plex_machineIdentifier', '')
else:
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))
settings('https', value=https)
settings('ipaddress', value=ip)
diff --git a/resources/lib/image_cache_thread.py b/resources/lib/image_cache_thread.py
index e0714e93..b01377a4 100644
--- a/resources/lib/image_cache_thread.py
+++ b/resources/lib/image_cache_thread.py
@@ -47,19 +47,13 @@ class ImageCacheThread(threading.Thread):
self.xbmc_password = password
def run(self):
-
- log.debug("Image Caching Thread Processing: %s", self.url_to_process)
-
try:
response = requests.head(
- url=(
- "http://%s:%s/image/image://%s"
- % (self.xbmc_host, self.xbmc_port, self.urlToProcess)),
- auth=(self.xbmc_username, self.xbmc_password),
- timeout=(5, 5))
+ url=("http://%s:%s/image/image://%s"
+ % (self.xbmc_host, self.xbmc_port, self.url_to_process)),
+ auth=(self.xbmc_username, self.xbmc_password),
+ timeout=(5, 5))
# We don't need the result
except Exception:
pass
-
- log.debug("Image Caching Thread Exited")
self.is_finished = True
diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py
index 3e5a6ff3..62ea9e20 100644
--- a/resources/lib/initialsetup.py
+++ b/resources/lib/initialsetup.py
@@ -417,6 +417,15 @@ class InitialSetup():
if settings('InstallQuestionsAnswered') == 'true':
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
# Direct paths (\\NAS\mymovie.mkv) or addon (http)?
goToSettings = False
@@ -463,13 +472,6 @@ class InitialSetup():
log.debug("User opted to use FanArtTV")
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
settings('InstallQuestionsAnswered', value='true')
diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py
index fd0ee550..65490717 100644
--- a/resources/lib/itemtypes.py
+++ b/resources/lib/itemtypes.py
@@ -3,7 +3,7 @@
###############################################################################
import logging
-import urllib
+from urllib import urlencode
from ntpath import dirname
from datetime import datetime
@@ -11,8 +11,8 @@ import xbmc
import xbmcgui
import artwork
-from utils import settings, window, kodiSQL, CatchExceptions
-from utils import tryEncode, tryDecode
+from utils import tryEncode, tryDecode, settings, window, kodiSQL, \
+ CatchExceptions
import embydb_functions as embydb
import kodidb_functions as kodidb
@@ -65,6 +65,28 @@ class Items(object):
self.kodiconn.close()
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):
# Process items by itemid. Process can be added, update, userdata, remove
embycursor = self.embycursor
@@ -394,7 +416,7 @@ class Movies(Items):
'dbid': movieid,
'mode': "play"
}
- filename = "%s?%s" % (path, urllib.urlencode(params))
+ filename = "%s?%s" % (path, urlencode(params))
playurl = filename
# movie table:
@@ -408,17 +430,32 @@ class Movies(Items):
% (itemid, title))
# Update the movie entry
- 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))
+ if self.kodiversion > 16:
+ 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 = ?, premiered = ?",
+ "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, 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
emby_db.updateReference(itemid, checksum)
@@ -469,16 +506,13 @@ class Movies(Items):
# Process countries
self.kodi_db.addCountries(movieid, countries, "movie")
# Process cast
- people = API.getPeopleList()
- self.kodi_db.addPeople(movieid, people, "movie")
+ self.kodi_db.addPeople(movieid, API.getPeopleList(), "movie")
# Process genres
self.kodi_db.addGenres(movieid, genres, "movie")
# Process artwork
- allartworks = API.getAllArtwork()
- artwork.addArtwork(allartworks, movieid, "movie", kodicursor)
+ artwork.addArtwork(API.getAllArtwork(), movieid, "movie", kodicursor)
# Process stream details
- streams = API.getMediaStreams()
- self.kodi_db.addStreams(fileid, streams, runtime)
+ self.kodi_db.addStreams(fileid, API.getMediaStreams(), runtime)
# Process studios
self.kodi_db.addStudios(movieid, studios, "movie")
# Process tags: view, Plex collection tags
@@ -488,7 +522,7 @@ class Movies(Items):
tags.append("Favorite movies")
self.kodi_db.addTags(movieid, tags, "movie")
# Add any sets from Plex collection tags
- self.kodi_db.addSets(movieid, collections, kodicursor, API)
+ self.kodi_db.addSets(movieid, collections, kodicursor)
# Process playstates
self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
@@ -906,7 +940,7 @@ class TVShows(Items):
'dbid': episodeid,
'mode': "play"
}
- filename = "%s?%s" % (path, tryDecode(urllib.urlencode(params)))
+ filename = "%s?%s" % (path, tryDecode(urlencode(params)))
playurl = filename
parentPathId = self.kodi_db.addPath(
'plugin://plugin.video.plexkodiconnect.tvshows/')
@@ -1730,7 +1764,7 @@ class Music(Items):
VALUES (?, ?, ?, ?)
'''
)
- kodicursor.execute(query, (artistid, songid, index, artist_name))
+ kodicursor.execute(query, (artistid, songid, index, artist_name))
# Verify if album artist exists
album_artists = []
diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py
index 0a5a565a..d2950017 100644
--- a/resources/lib/kodidb_functions.py
+++ b/resources/lib/kodidb_functions.py
@@ -508,6 +508,48 @@ class Kodidb_Functions():
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):
@@ -1180,15 +1222,9 @@ class Kodidb_Functions():
))
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:
setid = self.createBoxset(setname)
- # Process artwork
- if settings('setFanartTV') == 'true':
- self.artwork.addArtwork(API.getSetArtwork(),
- setid,
- "set",
- kodicursor)
self.assignBoxset(setid, movieid)
def createBoxset(self, boxsetname):
diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py
index 27733b13..5cac1315 100644
--- a/resources/lib/kodimonitor.py
+++ b/resources/lib/kodimonitor.py
@@ -254,7 +254,7 @@ class KodiMonitor(xbmc.Monitor):
# Save currentFile for cleanup later and to be able to access refs
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)
log.info('Finish playback startup')
diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py
index 77fd88d9..6394cbf0 100644
--- a/resources/lib/librarysync.py
+++ b/resources/lib/librarysync.py
@@ -5,6 +5,7 @@
import logging
from threading import Thread, Lock
import Queue
+from random import shuffle
import xbmc
import xbmcgui
@@ -62,7 +63,7 @@ class ThreadedGetMetadata(Thread):
try:
self.queue.get(block=False)
except Queue.Empty:
- xbmc.sleep(50)
+ xbmc.sleep(10)
continue
else:
self.queue.task_done()
@@ -73,7 +74,7 @@ class ThreadedGetMetadata(Thread):
try:
self.out_queue.get(block=False)
except Queue.Empty:
- xbmc.sleep(50)
+ xbmc.sleep(10)
continue
else:
self.out_queue.task_done()
@@ -93,7 +94,7 @@ class ThreadedGetMetadata(Thread):
updateItem = queue.get(block=False)
# Empty queue
except Queue.Empty:
- xbmc.sleep(100)
+ xbmc.sleep(10)
continue
# Download Metadata
plexXML = PF.GetPlexMetadata(updateItem['itemId'])
@@ -155,7 +156,7 @@ class ThreadedProcessMetadata(Thread):
try:
self.queue.get(block=False)
except Queue.Empty:
- xbmc.sleep(100)
+ xbmc.sleep(10)
continue
else:
self.queue.task_done()
@@ -175,7 +176,7 @@ class ThreadedProcessMetadata(Thread):
try:
updateItem = queue.get(block=False)
except Queue.Empty:
- xbmc.sleep(50)
+ xbmc.sleep(10)
continue
# Do the work
plexitem = updateItem['XML']
@@ -250,11 +251,106 @@ class ThreadedShowSyncInfo(Thread):
processMetadataProgress,
viewName))
# Sleep for x milliseconds
- xbmc.sleep(500)
+ xbmc.sleep(200)
dialog.close()
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')
@ThreadMethodsAdditionalStop('plex_shouldStop')
@ThreadMethods
@@ -275,6 +371,9 @@ class LibrarySync(Thread):
self.queue = queue
self.itemsToProcess = []
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?
self.saftyMargin = int(settings('saftyMargin'))
@@ -285,6 +384,7 @@ class LibrarySync(Thread):
self.vnodes = videonodes.VideoNodes()
self.dialog = xbmcgui.Dialog()
+ self.syncThreadNumber = int(settings('syncThreadNumber'))
self.installSyncDone = settings('SyncInstallRunDone') == 'true'
self.showDbSync = settings('dbSyncIndicator') == 'true'
self.enableMusic = settings('enableMusic') == "true"
@@ -292,9 +392,6 @@ class LibrarySync(Thread):
'enableBackgroundSync') == "true"
self.limitindex = int(settings('limitindex'))
- if settings('plex_pathverified') == 'true':
- window('plex_pathverified', value='true')
-
# Just in case a time sync goes wrong
self.timeoffset = int(settings('kodiplextimeoffset'))
window('kodiplextimeoffset', value=str(self.timeoffset))
@@ -760,6 +857,7 @@ class LibrarySync(Thread):
'viewName': xxx,
'viewId': xxx,
'title': xxx
+ 'mediaType': xxx, e.g. 'movie', 'episode'
self.allPlexElementsId APPENDED(!!) dict
= {itemid: checksum}
@@ -779,12 +877,15 @@ class LibrarySync(Thread):
# Only update if movie is not in Kodi or checksum is
# different
if kodi_checksum != plex_checksum:
- self.updatelist.append({'itemId': itemId,
- 'itemType': itemType,
- 'method': method,
- 'viewName': viewName,
- 'viewId': viewId,
- 'title': title})
+ self.updatelist.append({
+ 'itemId': itemId,
+ 'itemType': itemType,
+ 'method': method,
+ 'viewName': viewName,
+ 'viewId': viewId,
+ 'title': title,
+ 'mediaType': item.attrib.get('type')
+ })
else:
# Initial or repair sync: get all Plex movies
for item in xml:
@@ -796,12 +897,15 @@ class LibrarySync(Thread):
plex_checksum = ("K%s%s"
% (itemId, item.attrib.get('updatedAt', '')))
self.allPlexElementsId[itemId] = plex_checksum
- self.updatelist.append({'itemId': itemId,
- 'itemType': itemType,
- 'method': method,
- 'viewName': viewName,
- 'viewId': viewId,
- 'title': title})
+ self.updatelist.append({
+ 'itemId': itemId,
+ 'itemType': itemType,
+ 'method': method,
+ 'viewName': viewName,
+ 'viewId': viewId,
+ 'title': title,
+ 'mediaType': item.attrib.get('type')
+ })
def GetAndProcessXMLs(self, itemType, showProgress=True):
"""
@@ -836,15 +940,16 @@ class LibrarySync(Thread):
# Populate queue: GetMetadata
for updateItem in self.updatelist:
getMetadataQueue.put(updateItem)
- # Spawn GetMetadata thread for downloading
+ # Spawn GetMetadata threads for downloading
threads = []
- thread = ThreadedGetMetadata(getMetadataQueue,
- processMetadataQueue,
- getMetadataLock,
- processMetadataLock)
- thread.setDaemon(True)
- thread.start()
- threads.append(thread)
+ for i in range(min(self.syncThreadNumber, itemNumber)):
+ thread = ThreadedGetMetadata(getMetadataQueue,
+ processMetadataQueue,
+ getMetadataLock,
+ processMetadataLock)
+ thread.setDaemon(True)
+ thread.start()
+ threads.append(thread)
log.info("%s download threads spawned" % len(threads))
# Spawn one more thread to process Metadata, once downloaded
thread = ThreadedProcessMetadata(processMetadataQueue,
@@ -887,6 +992,18 @@ class LibrarySync(Thread):
except:
pass
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 = []
@LogTime
@@ -1284,7 +1401,17 @@ class LibrarySync(Thread):
if item['state'] == 9:
successful = self.process_deleteditems(item)
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:
deleteListe.append(i)
else:
@@ -1310,13 +1437,16 @@ class LibrarySync(Thread):
def process_newitems(self, item):
ratingKey = item['ratingKey']
xml = PF.GetPlexMetadata(ratingKey)
- if xml in (None, 401):
- log.error('Could not download data for %s, skipping' % ratingKey)
- return False
+ try:
+ mediatype = xml[0].attrib['type']
+ 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)
viewtag = xml.attrib.get('librarySectionTitle')
viewid = xml.attrib.get('librarySectionID')
- mediatype = xml[0].attrib.get('type')
+ # Attach mediatype for later
+ item['mediatype'] = mediatype
if mediatype == 'movie':
self.videoLibUpdate = True
with itemtypes.Movies() as movie:
@@ -1335,7 +1465,7 @@ class LibrarySync(Thread):
music.add_updateSong(xml[0],
viewtag=viewtag,
viewid=viewid)
- return True
+ return True, item
def process_deleteditems(self, item):
if item.get('type') == 1:
@@ -1362,10 +1492,19 @@ class LibrarySync(Thread):
"processing queue" for later
"""
for item in data:
- typus = item.get('type')
- state = item.get('state')
+ if 'tv.plex' in item.get('identifier', ''):
+ # 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):
- 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?
for existingItem in self.itemsToProcess:
if existingItem['ratingKey'] == itemId:
@@ -1400,7 +1539,7 @@ class LibrarySync(Thread):
sessionKey = item.get('sessionKey')
# Do we already have a sessionKey stored?
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
# sessions
# On the bright side, it must be us playing :-)
@@ -1419,7 +1558,10 @@ class LibrarySync(Thread):
continue
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
# update if neither session's username nor userid match
# (Owner sometime's returns id '1', not always)
@@ -1484,6 +1626,30 @@ class LibrarySync(Thread):
with itemFkt() as Fkt:
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):
try:
self.run_internal()
@@ -1527,6 +1693,9 @@ class LibrarySync(Thread):
if self.enableMusic:
advancedSettingsXML()
+ if settings('FanartTV') == 'true':
+ self.fanartthread.start()
+
while not threadStopped():
# In the event the server goes offline
@@ -1640,6 +1809,21 @@ class LibrarySync(Thread):
forced=True,
icon="error")
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:
now = getUnixTimestamp()
if (now - lastSync > fullSyncInterval and
@@ -1666,9 +1850,7 @@ class LibrarySync(Thread):
# Only do this once every 10 seconds
if now - lastProcessing > 10:
lastProcessing = now
- window('plex_dbScan', value="true")
processItems()
- window('plex_dbScan', clear=True)
# See if there is a PMS message we need to handle
try:
message = queue.get(block=False)
@@ -1677,10 +1859,8 @@ class LibrarySync(Thread):
continue
# Got a message from PMS; process it
else:
- window('plex_dbScan', value="true")
processMessage(message)
queue.task_done()
- window('plex_dbScan', clear=True)
# NO sleep!
continue
else:
diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py
index 65a8f5d1..d1970f79 100644
--- a/resources/lib/playbackutils.py
+++ b/resources/lib/playbackutils.py
@@ -11,7 +11,6 @@ import xbmc
import xbmcgui
import xbmcplugin
-import artwork
import playutils as putils
import playlist
from utils import window, settings, tryEncode, tryDecode
@@ -39,7 +38,6 @@ class PlaybackUtils():
self.userid = window('currUserId')
self.server = window('pms_server')
- self.artwork = artwork.Artwork()
if self.API.getType() == 'track':
self.pl = playlist.Playlist(typus='music')
else:
@@ -208,6 +206,7 @@ class PlaybackUtils():
(homeScreen and not sizePlaylist)):
# Playlist was created just now, play it.
log.info("Play playlist.")
+ xbmcplugin.endOfDirectory(int(sys.argv[1]), True, False, False)
xbmc.Player().play(kodiPl, startpos=startPos)
else:
diff --git a/resources/lib/player.py b/resources/lib/player.py
index f18829d3..718caec6 100644
--- a/resources/lib/player.py
+++ b/resources/lib/player.py
@@ -305,7 +305,7 @@ class Player(xbmc.Player):
self.stopAll()
- window('Plex_currently_playing_itemid', clear=True)
+ window('plex_currently_playing_itemid', clear=True)
window('plex_customplaylist', clear=True)
window('plex_customplaylist.seektime', clear=True)
window('plex_customplaylist.seektime', clear=True)
diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py
index 66fc8eb1..bf6941a9 100644
--- a/resources/lib/plexbmchelper/subscribers.py
+++ b/resources/lib/plexbmchelper/subscribers.py
@@ -102,7 +102,7 @@ class SubscriptionManager:
while not keyid:
if count > 300:
break
- keyid = window('Plex_currently_playing_itemid')
+ keyid = window('plex_currently_playing_itemid')
xbmc.sleep(100)
count += 1
if keyid:
@@ -149,7 +149,7 @@ class SubscriptionManager:
self.cleanup()
# 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
- if (not window('Plex_currently_playing_itemid')
+ if (not window('plex_currently_playing_itemid')
and not self.lastplayers):
return True
players = self.js.getPlayers()
diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py
index ed9be930..78aff86b 100644
--- a/resources/lib/userclient.py
+++ b/resources/lib/userclient.py
@@ -174,26 +174,9 @@ class UserClient(threading.Thread):
window('plex_machineIdentifier', value=self.machineIdentifier)
window('plex_servername', value=self.servername)
window('plex_authenticated', value='true')
- window('plex_serverowned', value=settings('plex_serverowned'))
window('useDirectPaths', value='true'
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
doUtils.startSession(reset=True)
diff --git a/resources/lib/utils.py b/resources/lib/utils.py
index 0a28abb7..ceae438a 100644
--- a/resources/lib/utils.py
+++ b/resources/lib/utils.py
@@ -182,7 +182,7 @@ def kodiSQL(media_type="video"):
else:
dbPath = getKodiVideoDBPath()
- connection = sqlite3.connect(dbPath)
+ connection = sqlite3.connect(dbPath, timeout=15.0)
return connection
def getKodiVideoDBPath():
diff --git a/resources/settings.xml b/resources/settings.xml
index 6e5624be..9c18a978 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -19,29 +19,35 @@
-
+
+
+
-
-
+
+
+
+
+
+
+
+
-
-
+
+
+
+
-
-
-
-
@@ -49,8 +55,6 @@
-
-
@@ -104,6 +108,12 @@
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
@@ -144,7 +144,6 @@
-
diff --git a/service.py b/service.py
index 180dac3c..efdbd2d8 100644
--- a/service.py
+++ b/service.py
@@ -85,6 +85,8 @@ class Service():
log.warn("%s Version: %s" % (addonName, self.clientInfo.getVersion()))
log.warn("Using plugin paths: %s"
% (settings('useDirectPaths') != "true"))
+ log.warn("Using a low powered device: %s"
+ % settings('low_powered_device'))
log.warn("Log Level: %s" % logLevel)
# Reset window props for profile switch
@@ -97,9 +99,6 @@ class Service():
"plex_runLibScan", "plex_username", "pms_token", "plex_token",
"pms_server", "plex_machineIdentifier", "plex_servername",
"plex_authenticated", "PlexUserImage", "useDirectPaths",
- "replaceSMB", "remapSMB", "remapSMBmovieOrg", "remapSMBtvOrg",
- "remapSMBmusicOrg", "remapSMBmovieNew", "remapSMBtvNew",
- "remapSMBmusicNew", "remapSMBphotoOrg", "remapSMBphotoNew",
"suspend_LibraryThread", "plex_terminateNow",
"kodiplextimeoffset", "countError", "countUnauthorized"
]
@@ -129,7 +128,7 @@ class Service():
initialsetup.InitialSetup().setup()
# Queue for background sync
- queue = Queue.Queue(maxsize=200)
+ queue = Queue.Queue()
connectMsg = True if settings('connectMsg') == 'true' else False