Merge Master
This commit is contained in:
parent
db02a001a8
commit
ad8b7c7d90
21 changed files with 473 additions and 410 deletions
88
README.md
88
README.md
|
@ -1,52 +1,65 @@
|
|||
# PlexKodiConnect (PKC)
|
||||
**Combine the best frontend media player Kodi with the best multimedia backend server Plex**
|
||||
|
||||
PKC combines the best of Kodi - ultra smooth navigation, beautiful and highly customizable user interfaces and playback of any file under the sun, and the Plex Media Server to manage all your media without lifting a finger.
|
||||
PKC combines the best of Kodi - ultra smooth navigation, beautiful and highly customizable user interfaces and playback of any file under the sun - and the Plex Media Server.
|
||||
|
||||
Have a look at [some screenshots](https://github.com/croneter/PlexKodiConnect/wiki/Some-PKC-Screenshots) to see what's possible.
|
||||
|
||||
|
||||
### Content
|
||||
* [**Warning**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#warning)
|
||||
* [**What does PKC do and how is it different from the official 'Plex for Kodi'**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#what-does-pkc-do-and-how-is-it-different-from-the-official-plex-for-kod)
|
||||
* [**Download and Installation**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#download-and-installation)
|
||||
* [**Important notes**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#important-notes)
|
||||
* [**Donations**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#donations)
|
||||
* [**What is currently supported?**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#what-is-currently-supported)
|
||||
* [**Known Larger Issues**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#known-larger-issues)
|
||||
* [**Issues being worked on**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#issues-being-worked-on)
|
||||
* [**Pipeline for future development**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#what-could-be-in-the-pipeline-for-future-development)
|
||||
* [**Checkout the PKC Wiki**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#checkout-the-pkc-wiki)
|
||||
* [**Credits**](https://github.com/croneter/PlexKodiConnect/tree/hotfixes#credits)
|
||||
|
||||
### Warning
|
||||
This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases (as this plugin directly changes them). Use at your own risk!
|
||||
Use at your own risk! This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases as this plugin directly changes them. Don't worry if you want Plex to manage all your media (like you should ;-)).
|
||||
|
||||
### What does PKC do and how is it different from the official ['Plex for Kodi'](https://www.plex.tv/apps/computer/kodi/)?
|
||||
|
||||
With other Plex addons for Kodi such as the official [Plex for Kodi](https://www.plex.tv/apps/computer/kodi/) or [PlexBMC](https://forums.plex.tv/discussion/106593/plexbmc-xbmc-add-on-to-connect-to-plex-media-server) there are a couple of issues:
|
||||
- Other Kodi addons such as NextAired, remote apps and others won't work
|
||||
- You can only use special Kodi skins
|
||||
- Slow speed: when browsing data has to be retrieved from the server. Especially on slower devices this can take too much time and you will notice artwork being loaded slowly while you browse the library
|
||||
- All kinds of workarounds are needed to get the best experience on Kodi clients
|
||||
|
||||
PKC synchronizes your media from your Plex server to the native Kodi database. Because PKC uses the native Kodi database, the above limitations are gone!
|
||||
- Use any Kodi skin you want!
|
||||
- You can browse your media at full speed, images are cached
|
||||
- All other Kodi addons will be able to "see" your media, thinking it's normal Kodi stuff
|
||||
|
||||
Some people argue that PKC is 'hacky' because of the way it directly accesses the Kodi database. See [here for a more thorough discussion](https://github.com/croneter/PlexKodiConnect/wiki/Is-PKC-'hacky'%3F).
|
||||
|
||||
### Download and Installation
|
||||
[ ![Download](https://api.bintray.com/packages/croneter/PlexKodiConnect/PlexKodiConnect/images/download.svg) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip)
|
||||
|
||||
The easiest way to install PKC is via our PlexKodiConnect Kodi repository (we cannot use the official Kodi repository as PKC messes with Kodi's databases). See the [installation guideline on how to do this](https://github.com/croneter/PlexKodiConnect/wiki/Installation).
|
||||
Install PKC via the PlexKodiConnect Kodi repository (we cannot use the official Kodi repository as PKC messes with Kodi's databases). See the [installation guideline on how to do this](https://github.com/croneter/PlexKodiConnect/wiki/Installation).
|
||||
|
||||
**Possibly UNSTABLE BETA version:** [ ![Download](https://api.bintray.com/packages/croneter/PlexKodiConnect_BETA/PlexKodiConnect_BETA/images/download.svg) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip)
|
||||
|
||||
### Important Notes
|
||||
|
||||
1. If you are using a **low CPU device like a Raspberry Pi or a CuBox**, PKC might be instable or crash during initial sync. Lower the number of threads in the [PKC settings under Sync Options](https://github.com/croneter/PlexKodiConnect/wiki/PKC-settings#sync-options): `Limit artwork cache threads: 5`
|
||||
Don't forget to reboot Kodi after that.
|
||||
2. **Compatibility**:
|
||||
* PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
|
||||
* PKC is not (and will never be) compatible with the **MySQL database replacement** in Kodi. In fact, PKC replaces the MySQL functionality because it acts as a "man in the middle" for your entire media library.
|
||||
* If **another plugin is not working** like it's supposed to, try to use [PKC direct paths](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths)
|
||||
3. If you post logs, your **Plex tokens** might be included. Be sure to double and triple check for tokens before posting any logs anywhere by searching for `token`
|
||||
|
||||
### Donations
|
||||
I'm not in any way affiliated with Plex. Thank you very much for a small donation via ko-fi.com and PayPal if you appreciate PKC.
|
||||
**Full disclaimer:** I will see your name and address on my PayPal account. Rest assured that I will not share this with anyone.
|
||||
|
||||
[ ![Download](https://az743702.vo.msecnd.net/cdn/kofi1.png?v=a|alt=Buy Me a Coffee)](https://ko-fi.com/A8182EB)
|
||||
|
||||
### IMPORTANT NOTES
|
||||
|
||||
1. If you are using a **low CPU device like a Raspberry Pi or a CuBox**, PKC might be instable or crash during initial sync. Lower the number of threads in the [PKC settings under Sync Options](https://github.com/croneter/PlexKodiConnect/wiki/PKC-settings#sync-options): `Limit artwork cache threads: 5`
|
||||
Don't forget to reboot Kodi after that.
|
||||
2. If you post logs, your **Plex tokens** might be included. Be sure to double and triple check for tokens before posting any logs anywhere by searching for `token`
|
||||
3. **Compatibility**: PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
|
||||
|
||||
|
||||
### Checkout the PKC Wiki
|
||||
The [Wiki can be found here](https://github.com/croneter/PlexKodiConnect/wiki) and will hopefully answer all your questions.
|
||||
|
||||
|
||||
### What does PKC do?
|
||||
|
||||
With other addons for Kodi there are a couple of issues:
|
||||
- 3rd party addons such as NextAired, remote apps etc. won't work
|
||||
- Slow speed: when browsing the data has to be retrieved from the server. Especially on slower devices this can take too much time
|
||||
- You can only use special Kodi skins
|
||||
- All kinds of workarounds are needed to get the best experience on Kodi clients
|
||||
|
||||
PKC synchronizes your media from your Plex server to the native Kodi database. Because PKC uses the native Kodi database, the above limitations are gone!
|
||||
- You can browse your media full speed, images are cached
|
||||
- All other Kodi addons will be able to "see" your media, thinking it's normal Kodi stuff
|
||||
- Use any Kodi skin you want!
|
||||
|
||||
|
||||
### What is currently supported?
|
||||
|
||||
PKC currently provides the following features:
|
||||
|
@ -71,14 +84,14 @@ PKC currently provides the following features:
|
|||
+ Extra fanart backgrounds
|
||||
- Automatically group movies into [movie sets](http://kodi.wiki/view/movie_sets)
|
||||
- Direct play from network paths (e.g. "\\\\server\\Plex\\movie.mkv") instead of streaming from slow HTTP (e.g. "192.168.1.1:32400"). You have to setup all your Plex libraries to point to such network paths. Do have a look at [the wiki here](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths)
|
||||
|
||||
- Delete PMS items from the Kodi context menu
|
||||
|
||||
### Known Larger Issues
|
||||
|
||||
Solutions are unlikely due to the nature of these issues
|
||||
- A Plex Media Server "bug" leads to frequent and slow syncs, see [here for more info](https://github.com/croneter/PlexKodiConnect/issues/135)
|
||||
- *Plex Music when using Addon paths instead of Native Direct Paths:* Kodi tries to scan every(!) single Plex song on startup. This leads to errors in the Kodi log file and potentially even crashes. See the [Github issue](https://github.com/croneter/PlexKodiConnect/issues/14) for more details
|
||||
- *Plex Music when using Addon paths instead of Native Direct Paths:* You must have a static IP address for your Plex media server if you plan to use Plex Music features
|
||||
- If something on the PMS has changed, this change is synced to Kodi. Hence if you rescan your entire library, a long PlexKodiConnect re-sync is triggered. You can [change your PMS settings to avoid that](https://github.com/croneter/PlexKodiConnect/wiki/Configure-PKC-on-the-First-Run#deactivate-frequent-updates)
|
||||
- External Plex subtitles (in separate files, e.g. mymovie.srt) can be used, but it is impossible to label them correctly and tell what language they are in. However, this is not the case if you use direct paths
|
||||
|
||||
*Background Sync:*
|
||||
|
@ -91,21 +104,18 @@ However, some changes to individual items are instantly detected, e.g. if you ma
|
|||
|
||||
### Issues being worked on
|
||||
|
||||
Have a look at the [Github Issues Page](https://github.com/croneter/PlexKodiConnect/issues).
|
||||
Have a look at the [Github Issues Page](https://github.com/croneter/PlexKodiConnect/issues). Before you open your own issue, please read [How to report a bug](https://github.com/croneter/PlexKodiConnect/wiki/How-to-Report-A-Bug).
|
||||
|
||||
|
||||
### What could be in the pipeline for future development?
|
||||
|
||||
- Plex channels
|
||||
- Movie extras (trailers already work)
|
||||
- Playlists
|
||||
- Music Videos
|
||||
- Deleting PMS items from Kodi
|
||||
- TV Shows Theme Music (ultra-low prio)
|
||||
|
||||
|
||||
### Important note about MySQL database in Kodi
|
||||
|
||||
The addon is not (and will not be) compatible with the MySQL database replacement in Kodi. In fact, PlexKodiConnect takes over the point of having a MySQL database because it acts as a "man in the middle" for your entire media library.
|
||||
|
||||
### Checkout the PKC Wiki
|
||||
The [Wiki can be found here](https://github.com/croneter/PlexKodiConnect/wiki) and will hopefully answer all your questions. You can even edit the wiki yourself!
|
||||
|
||||
### Credits
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.plexkodiconnect"
|
||||
name="PlexKodiConnect"
|
||||
version="1.4.8"
|
||||
version="1.5.1"
|
||||
provider-name="croneter">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
|
|
|
@ -1,3 +1,37 @@
|
|||
version 1.5.1 (beta only)
|
||||
- Fix playstate and PMS item changes not working/not propagating anymore (caused by a change Plex made with the websocket interface). UPGRADE YOUR PMS!!
|
||||
- Improvements to the way PKC behaves if the PMS goes offline
|
||||
- New setting to always transcode if the video bitrate is above a certain threshold (will not work with direct paths)
|
||||
- Be smarter when deciding when to transcode
|
||||
- Only sign the user out if the PMS says so
|
||||
- Improvements to PMS on/offline notifications
|
||||
- Note to PLEASE read the Wiki if one is using several Plex libraries (shows on first PKC install only)
|
||||
- Get rid of low powered device option (always use low powered option)
|
||||
- Don't show a notification when searching for PMS
|
||||
- Combine h265 und HEVC into one setting
|
||||
- Less traffic when PKC is checking whether a PMS is still offline
|
||||
- Improve logging
|
||||
|
||||
version 1.5.0
|
||||
Out for everyone:
|
||||
- reatly speed up the database sync. Please report if you experience any issues!
|
||||
- Only show database sync progress for NEW PMS items
|
||||
- Speed up the pathname verifications
|
||||
- Update readme to reflect the advent of the official Plex for Kodi
|
||||
- Fix for not getting tv show additional fanart
|
||||
- Fix for fanart url containing spaces
|
||||
- Fix library AttributeError
|
||||
- Catch websocket handshake errors correctly
|
||||
|
||||
version 1.4.10 (beta only)
|
||||
- Fix library AttributeError
|
||||
|
||||
version 1.4.9 (beta only)
|
||||
- Greatly speed up the database sync. Please report if you experience any issues!
|
||||
- Only show database sync progress for NEW PMS items
|
||||
- Speed up the pathname verifications
|
||||
- Update readme to reflect the advent of the official Plex for Kodi
|
||||
|
||||
version 1.4.8 (beta only)
|
||||
- Fix for not getting tv show additional fanart
|
||||
- Fix for fanart url containing spaces
|
||||
|
|
|
@ -126,6 +126,8 @@
|
|||
<string id="30140"> - Loop Theme Music</string>
|
||||
<string id="30141">Enable Background Image (Requires Restart)</string>
|
||||
<string id="30142">Services</string>
|
||||
|
||||
<string id="30143">Always transcode if video bitrate is above</string>
|
||||
|
||||
<string id="30150">Skin does not support setting views</string>
|
||||
<string id="30151">Select item action (Requires Restart)</string>
|
||||
|
@ -294,7 +296,7 @@
|
|||
<string id="30519">Ask to play trailers</string>
|
||||
<string id="30520">Skip Plex delete confirmation for the context menu (use at your own risk)</string>
|
||||
<string id="30521">Jump back on resume (in seconds)</string>
|
||||
<string id="30522">Force transcode H265</string>
|
||||
<string id="30522">Force transcode h265/HEVC</string>
|
||||
<string id="30523">Music metadata options (not compatible with direct stream)</string>
|
||||
<string id="30524">Import music song rating directly from files</string>
|
||||
<string id="30525">Convert music song rating to Emby rating</string>
|
||||
|
@ -423,7 +425,6 @@
|
|||
<string id="39062">Sync when screensaver is deactivated</string>
|
||||
<string id="39063">Force Transcode Hi10P</string>
|
||||
<string id="39064">Recently Added: Also show already watched episodes</string>
|
||||
<string id="39065">Force Transcode HEVC</string>
|
||||
<string id="39066">Recently Added: Also show already watched movies (Refresh Plex playlist/nodes!)</string>
|
||||
<string id="39067">Your current Plex Media Server:</string>
|
||||
<string id="39068">[COLOR yellow]Manually enter Plex Media Server address[/COLOR]</string>
|
||||
|
@ -434,6 +435,7 @@
|
|||
<string id="39073">Appearance Tweaks</string>
|
||||
<string id="39074">TV Shows</string>
|
||||
<string id="39075">Always use default Plex subtitle if possible</string>
|
||||
<string id="39076">If you use several Plex libraries of one kind, e.g. "Kids Movies" and "Parents Movies", be sure to check the Wiki: https://goo.gl/JFtQV9</string>
|
||||
|
||||
<!-- Plex Entrypoint.py -->
|
||||
<string id="39200">Log-out Plex Home User </string>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<string id="30509">Plex Musik-Bibliotheken aktivieren</string>
|
||||
<string id="30518">Plex Trailer aktivieren (Plexpass benötigt)</string>
|
||||
<string id="30519">Nachfragen, ob Trailer gespielt werden sollen</string>
|
||||
<string id="30522">H265 Codec Transkodierung erzwingen</string>
|
||||
<string id="30522">h265/HEVC Codec Transkodierung erzwingen</string>
|
||||
<string id="30517">Netzwerk Credentials eingeben</string>
|
||||
<string id="30529">PlexKodiConnect Start Verzögerung (in Sekunden)</string>
|
||||
<string id="30527">Extras ignorieren, wenn Nächste Episode gespielt wird</string>
|
||||
|
@ -151,7 +151,8 @@
|
|||
<string id="30140"> - Themen-Musik in Schleife abspielen</string>
|
||||
<string id="30141">Laden im Hintergrund aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30142">Dienste</string>
|
||||
<string id="30143">Info-Loader aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30143">Immer transkodieren falls Bitrate höher als</string>
|
||||
|
||||
<string id="30144">Menü-Loader aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30145">WebSocket Fernbedienung aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30146">'Laufende Medien'-Loader aktivieren (Erfordert Neustart)</string>
|
||||
|
@ -373,7 +374,6 @@
|
|||
<string id="39062">Sync wenn Bildschirmschoner deaktiviert wird</string>
|
||||
<string id="39063">Hi10p Codec Transkodierung erzwingen</string>
|
||||
<string id="39064">"Zuletzt hinzugefügt": gesehene Folgen anzeigen</string>
|
||||
<string id="39065">HEVC Codec Transkodierung erzwingen</string>
|
||||
<string id="39066">"Zuletzt hinzugefügt": gesehene Filme anzeigen (Plex Playlisten und Nodes zurücksetzen!)</string>
|
||||
<string id="39067">Aktueller Plex Media Server:</string>
|
||||
<string id="39068">[COLOR yellow]Plex Media Server Adresse manuell eingeben[/COLOR]</string>
|
||||
|
@ -384,6 +384,7 @@
|
|||
<string id="39073">Tweaks Aussehen</string>
|
||||
<string id="39074">TV Serien</string>
|
||||
<string id="39075">Falls möglich, Plex Standard-Untertitel anzeigen</string>
|
||||
<string id="39076">Falls du mehrere Plex Bibliotheken einer Art nutzt, z.B. "Filme Kinder" und "Filme Eltern", lies unbedingt das Wiki unter https://goo.gl/JFtQV9</string>
|
||||
|
||||
<!-- Plex Entrypoint.py -->
|
||||
<string id="39200">Plex Home Benutzer abmelden: </string>
|
||||
|
|
|
@ -49,7 +49,8 @@ import clientinfo
|
|||
import downloadutils
|
||||
from utils import window, settings, language as lang, tryDecode, tryEncode, \
|
||||
DateToKodi, KODILANGUAGE
|
||||
from PlexFunctions import PLEX_TO_KODI_TIMEFACTOR, PMSHttpsEnabled
|
||||
from PlexFunctions import PLEX_TO_KODI_TIMEFACTOR, PMSHttpsEnabled, \
|
||||
REMAP_TYPE_FROM_PLEXTYPE
|
||||
import embydb_functions as embydb
|
||||
|
||||
###############################################################################
|
||||
|
@ -288,18 +289,18 @@ class PlexAPI():
|
|||
url = 'https://plex.tv/api/home/users'
|
||||
else:
|
||||
url = url + '/library/onDeck'
|
||||
log.info("Checking connection to server %s with verifySSL=%s"
|
||||
% (url, verifySSL))
|
||||
log.debug("Checking connection to server %s with verifySSL=%s"
|
||||
% (url, verifySSL))
|
||||
# Check up to 3 times before giving up
|
||||
count = 0
|
||||
while count < 3:
|
||||
while count < 1:
|
||||
answer = self.doUtils(url,
|
||||
authenticate=False,
|
||||
headerOptions=headerOptions,
|
||||
verifySSL=verifySSL,
|
||||
timeout=4)
|
||||
if answer is None:
|
||||
log.info("Could not connect to %s" % url)
|
||||
log.debug("Could not connect to %s" % url)
|
||||
count += 1
|
||||
xbmc.sleep(500)
|
||||
continue
|
||||
|
@ -316,7 +317,7 @@ class PlexAPI():
|
|||
# We could connect but maybe were not authenticated. No worries
|
||||
log.debug("Checking connection successfull. Answer: %s" % answer)
|
||||
return answer
|
||||
log.info('Failed to connect to %s too many times. PMS is dead' % url)
|
||||
log.debug('Failed to connect to %s too many times. PMS is dead' % url)
|
||||
return False
|
||||
|
||||
def GetgPMSKeylist(self):
|
||||
|
@ -510,13 +511,6 @@ class PlexAPI():
|
|||
self.g_PMS dict set
|
||||
"""
|
||||
self.g_PMS = {}
|
||||
# "Searching for Plex Server"
|
||||
xbmcgui.Dialog().notification(
|
||||
heading=addonName,
|
||||
message=lang(39055),
|
||||
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png",
|
||||
time=4000,
|
||||
sound=False)
|
||||
|
||||
# Look first for local PMS in the LAN
|
||||
pmsList = self.PlexGDM()
|
||||
|
@ -2515,29 +2509,17 @@ class API():
|
|||
"""
|
||||
if path is None:
|
||||
return None
|
||||
types = {
|
||||
'movie': 'movie',
|
||||
'show': 'tv',
|
||||
'season': 'tv',
|
||||
'episode': 'tv',
|
||||
'artist': 'music',
|
||||
'album': 'music',
|
||||
'song': 'music',
|
||||
'track': 'music',
|
||||
'clip': 'clip',
|
||||
'photo': 'photo'
|
||||
}
|
||||
typus = types[typus]
|
||||
if settings('remapSMB') == 'true':
|
||||
path = path.replace(settings('remapSMB%sOrg' % typus),
|
||||
settings('remapSMB%sNew' % typus),
|
||||
typus = REMAP_TYPE_FROM_PLEXTYPE[typus]
|
||||
if window('remapSMB') == 'true':
|
||||
path = path.replace(window('remapSMB%sOrg' % typus),
|
||||
window('remapSMB%sNew' % typus),
|
||||
1)
|
||||
# There might be backslashes left over:
|
||||
path = path.replace('\\', '/')
|
||||
elif settings('replaceSMB') == 'true':
|
||||
elif window('replaceSMB') == 'true':
|
||||
if path.startswith('\\\\'):
|
||||
path = 'smb:' + path.replace('\\', '/')
|
||||
if settings('plex_pathverified') == 'true' and forceCheck is False:
|
||||
if window('plex_pathverified') == 'true' and forceCheck is False:
|
||||
return path
|
||||
|
||||
# exist() needs a / or \ at the end to work for directories
|
||||
|
@ -2558,12 +2540,12 @@ class API():
|
|||
if self.askToValidate(path):
|
||||
window('plex_shouldStop', value="true")
|
||||
path = None
|
||||
settings('plex_pathverified', value='true')
|
||||
window('plex_pathverified', value='true')
|
||||
else:
|
||||
path = None
|
||||
elif forceCheck is False:
|
||||
if settings('plex_pathverified') != 'true':
|
||||
settings('plex_pathverified', value='true')
|
||||
if window('plex_pathverified') != 'true':
|
||||
window('plex_pathverified', value='true')
|
||||
return path
|
||||
|
||||
def askToValidate(self, url):
|
||||
|
|
|
@ -72,6 +72,20 @@ KODIAUDIOVIDEO_FROM_MEDIA_TYPE = {
|
|||
}
|
||||
|
||||
|
||||
REMAP_TYPE_FROM_PLEXTYPE = {
|
||||
'movie': 'movie',
|
||||
'show': 'tv',
|
||||
'season': 'tv',
|
||||
'episode': 'tv',
|
||||
'artist': 'music',
|
||||
'album': 'music',
|
||||
'song': 'music',
|
||||
'track': 'music',
|
||||
'clip': 'clip',
|
||||
'photo': 'photo'
|
||||
}
|
||||
|
||||
|
||||
def ConvertPlexToKodiTime(plexTime):
|
||||
"""
|
||||
Converts Plextime to Koditime. Returns an int (in seconds).
|
||||
|
|
|
@ -131,13 +131,9 @@ 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)
|
||||
# Potentially issues with limited number of threads
|
||||
# Hence let Kodi wait till download is successful
|
||||
timeout = (35.1, 35.1)
|
||||
|
||||
def __init__(self, queue):
|
||||
self.queue = queue
|
||||
|
|
|
@ -215,12 +215,12 @@ class DownloadUtils():
|
|||
# THE EXCEPTIONS
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
# Connection error
|
||||
log.warn("Server unreachable at: %s" % url)
|
||||
log.warn(e)
|
||||
log.debug("Server unreachable at: %s" % url)
|
||||
log.debug(e)
|
||||
|
||||
except requests.exceptions.ConnectTimeout as e:
|
||||
log.warn("Server timeout at: %s" % url)
|
||||
log.warn(e)
|
||||
except requests.exceptions.Timeout as e:
|
||||
log.debug("Server timeout at: %s" % url)
|
||||
log.debug(e)
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
log.warn('HTTP Error at %s' % url)
|
||||
|
|
|
@ -242,14 +242,6 @@ class InitialSetup():
|
|||
log.warn('The PMS you have used before with a unique '
|
||||
'machineIdentifier of %s and name %s is '
|
||||
'offline' % (self.serverid, name))
|
||||
# "PMS xyz offline"
|
||||
if settings('show_pms_offline') == 'true':
|
||||
self.dialog.notification(addonName,
|
||||
'%s %s'
|
||||
% (name, lang(39213)),
|
||||
xbmcgui.NOTIFICATION_ERROR,
|
||||
7000,
|
||||
False)
|
||||
return
|
||||
chk = self._checkServerCon(server)
|
||||
if chk == 504 and httpsUpdated is False:
|
||||
|
@ -441,15 +433,6 @@ 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
|
||||
|
@ -496,6 +479,9 @@ class InitialSetup():
|
|||
log.debug("User opted to use FanArtTV")
|
||||
settings('FanartTV', value="true")
|
||||
|
||||
# If you use several Plex libraries of one kind, e.g. "Kids Movies" and
|
||||
# "Parents Movies", be sure to check https://goo.gl/JFtQV9
|
||||
dialog.ok(heading=addonName, line1=lang(39076))
|
||||
# Make sure that we only ask these questions upon first installation
|
||||
settings('InstallQuestionsAnswered', value='true')
|
||||
|
||||
|
|
|
@ -7,12 +7,11 @@ from urllib import urlencode
|
|||
from ntpath import dirname
|
||||
from datetime import datetime
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
import artwork
|
||||
from utils import tryEncode, tryDecode, settings, window, kodiSQL, \
|
||||
CatchExceptions
|
||||
CatchExceptions, KODIVERSION
|
||||
import embydb_functions as embydb
|
||||
import kodidb_functions as kodidb
|
||||
|
||||
|
@ -36,7 +35,6 @@ class Items(object):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
||||
self.directpath = window('useDirectPaths') == 'true'
|
||||
|
||||
self.artwork = artwork.Artwork()
|
||||
|
@ -435,7 +433,7 @@ class Movies(Items):
|
|||
% (itemid, title))
|
||||
|
||||
# Update the movie entry
|
||||
if self.kodiversion > 16:
|
||||
if KODIVERSION > 16:
|
||||
query = ' '.join((
|
||||
"UPDATE movie",
|
||||
"SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?,"
|
||||
|
@ -466,7 +464,7 @@ class Movies(Items):
|
|||
##### OR ADD THE MOVIE #####
|
||||
else:
|
||||
log.info("ADD movie itemid: %s - Title: %s" % (itemid, title))
|
||||
if self.kodiversion > 16:
|
||||
if KODIVERSION > 16:
|
||||
query = (
|
||||
'''
|
||||
INSERT INTO movie( idMovie, idFile, c00, c01, c02, c03,
|
||||
|
@ -985,7 +983,7 @@ class TVShows(Items):
|
|||
log.info("UPDATE episode itemid: %s" % (itemid))
|
||||
|
||||
# Update the movie entry
|
||||
if self.kodiversion in (16, 17):
|
||||
if KODIVERSION in (16, 17):
|
||||
# Kodi Jarvis, Krypton
|
||||
query = ' '.join((
|
||||
"UPDATE episode",
|
||||
|
@ -1018,7 +1016,7 @@ class TVShows(Items):
|
|||
else:
|
||||
log.info("ADD episode itemid: %s - Title: %s" % (itemid, title))
|
||||
# Create the episode entry
|
||||
if self.kodiversion in (16, 17):
|
||||
if KODIVERSION in (16, 17):
|
||||
# Kodi Jarvis, Krypton
|
||||
query = (
|
||||
'''
|
||||
|
@ -1318,7 +1316,7 @@ class Music(Items):
|
|||
itemid, artistid, artisttype, "artist", checksum=checksum)
|
||||
|
||||
# Process the artist
|
||||
if self.kodiversion in (16, 17):
|
||||
if KODIVERSION in (16, 17):
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE artist",
|
||||
|
@ -1411,7 +1409,7 @@ class Music(Items):
|
|||
itemid, albumid, "MusicAlbum", "album", checksum=checksum)
|
||||
|
||||
# Process the album info
|
||||
if self.kodiversion == 17:
|
||||
if KODIVERSION == 17:
|
||||
# Kodi Krypton
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -1424,7 +1422,7 @@ class Music(Items):
|
|||
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
|
||||
rating, lastScraped, "album", studio,
|
||||
albumid))
|
||||
elif self.kodiversion == 16:
|
||||
elif KODIVERSION == 16:
|
||||
# Kodi Jarvis
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -1437,7 +1435,7 @@ class Music(Items):
|
|||
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
|
||||
rating, lastScraped, "album", studio,
|
||||
albumid))
|
||||
elif self.kodiversion == 15:
|
||||
elif KODIVERSION == 15:
|
||||
# Kodi Isengard
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -1679,7 +1677,7 @@ class Music(Items):
|
|||
log.info("Failed to add album. Creating singles.")
|
||||
kodicursor.execute("select coalesce(max(idAlbum),0) from album")
|
||||
albumid = kodicursor.fetchone()[0] + 1
|
||||
if self.kodiversion == 16:
|
||||
if KODIVERSION == 16:
|
||||
# Kodi Jarvis
|
||||
query = (
|
||||
'''
|
||||
|
@ -1689,7 +1687,7 @@ class Music(Items):
|
|||
'''
|
||||
)
|
||||
kodicursor.execute(query, (albumid, genre, year, "single"))
|
||||
elif self.kodiversion == 15:
|
||||
elif KODIVERSION == 15:
|
||||
# Kodi Isengard
|
||||
query = (
|
||||
'''
|
||||
|
@ -1767,7 +1765,7 @@ class Music(Items):
|
|||
artist_edb = emby_db.getItem_byId(artist_eid)
|
||||
artistid = artist_edb[0]
|
||||
finally:
|
||||
if self.kodiversion >= 17:
|
||||
if KODIVERSION >= 17:
|
||||
# Kodi Krypton
|
||||
query = (
|
||||
'''
|
||||
|
@ -1842,11 +1840,11 @@ class Music(Items):
|
|||
result = kodicursor.fetchone()
|
||||
if result and result[0] != album_artists:
|
||||
# Field is empty
|
||||
if self.kodiversion in (16, 17):
|
||||
if KODIVERSION in (16, 17):
|
||||
# Kodi Jarvis, Krypton
|
||||
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
|
||||
kodicursor.execute(query, (album_artists, albumid))
|
||||
elif self.kodiversion == 15:
|
||||
elif KODIVERSION == 15:
|
||||
# Kodi Isengard
|
||||
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
|
||||
kodicursor.execute(query, (album_artists, albumid))
|
||||
|
|
|
@ -3,13 +3,10 @@
|
|||
###############################################################################
|
||||
|
||||
import logging
|
||||
|
||||
import xbmc
|
||||
from ntpath import dirname
|
||||
|
||||
import artwork
|
||||
import clientinfo
|
||||
from utils import settings, kodiSQL
|
||||
from utils import kodiSQL, KODIVERSION
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -43,13 +40,8 @@ class GetKodiDB():
|
|||
|
||||
|
||||
class Kodidb_Functions():
|
||||
|
||||
kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
||||
|
||||
def __init__(self, cursor):
|
||||
self.cursor = cursor
|
||||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.artwork = artwork.Artwork()
|
||||
|
||||
def pathHack(self):
|
||||
|
@ -212,8 +204,7 @@ class Kodidb_Functions():
|
|||
self.cursor.execute(query, (pathid, filename,))
|
||||
|
||||
def addCountries(self, kodiid, countries, mediatype):
|
||||
|
||||
if self.kodiversion in (15, 16, 17):
|
||||
if KODIVERSION > 14:
|
||||
# Kodi Isengard, Jarvis, Krypton
|
||||
for country in countries:
|
||||
query = ' '.join((
|
||||
|
@ -284,85 +275,74 @@ class Kodidb_Functions():
|
|||
)
|
||||
self.cursor.execute(query, (idCountry, kodiid))
|
||||
|
||||
def _getactorid(self, name):
|
||||
"""
|
||||
Crucial für sync speed!
|
||||
"""
|
||||
query = ' '.join((
|
||||
"SELECT actor_id",
|
||||
"FROM actor",
|
||||
"WHERE name = ?",
|
||||
"LIMIT 1"
|
||||
))
|
||||
self.cursor.execute(query, (name,))
|
||||
try:
|
||||
actorid = self.cursor.fetchone()[0]
|
||||
except TypeError:
|
||||
# Cast entry does not exists
|
||||
self.cursor.execute("select coalesce(max(actor_id),0) from actor")
|
||||
actorid = self.cursor.fetchone()[0] + 1
|
||||
query = "INSERT INTO actor(actor_id, name) VALUES (?, ?)"
|
||||
self.cursor.execute(query, (actorid, name))
|
||||
return actorid
|
||||
|
||||
def _addPerson(self, role, person_type, actorid, kodiid, mediatype,
|
||||
castorder):
|
||||
if "Actor" == person_type:
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO actor_link(
|
||||
actor_id, media_id, media_type, role, cast_order)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
'''
|
||||
self.cursor.execute(query, (actorid, kodiid, mediatype, role,
|
||||
castorder))
|
||||
castorder += 1
|
||||
elif "Director" == person_type:
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO director_link(
|
||||
actor_id, media_id, media_type)
|
||||
VALUES (?, ?, ?)
|
||||
'''
|
||||
self.cursor.execute(query, (actorid, kodiid, mediatype))
|
||||
elif person_type == "Writer":
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO writer_link(
|
||||
actor_id, media_id, media_type)
|
||||
VALUES (?, ?, ?)
|
||||
'''
|
||||
self.cursor.execute(query, (actorid, kodiid, mediatype))
|
||||
elif "Artist" == person_type:
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO actor_link(
|
||||
actor_id, media_id, media_type)
|
||||
VALUES (?, ?, ?)
|
||||
'''
|
||||
self.cursor.execute(query, (actorid, kodiid, mediatype))
|
||||
return castorder
|
||||
|
||||
def addPeople(self, kodiid, people, mediatype):
|
||||
|
||||
castorder = 1
|
||||
for person in people:
|
||||
|
||||
name = person['Name']
|
||||
person_type = person['Type']
|
||||
thumb = person['imageurl']
|
||||
|
||||
# Kodi Isengard, Jarvis, Krypton
|
||||
if self.kodiversion in (15, 16, 17):
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT actor_id",
|
||||
"FROM actor",
|
||||
"WHERE name = ?",
|
||||
"COLLATE NOCASE"
|
||||
))
|
||||
self.cursor.execute(query, (name,))
|
||||
|
||||
try:
|
||||
actorid = self.cursor.fetchone()[0]
|
||||
|
||||
except TypeError:
|
||||
# Cast entry does not exists
|
||||
self.cursor.execute("select coalesce(max(actor_id),0) from actor")
|
||||
actorid = self.cursor.fetchone()[0] + 1
|
||||
|
||||
query = "INSERT INTO actor(actor_id, name) values(?, ?)"
|
||||
self.cursor.execute(query, (actorid, name))
|
||||
log.debug("Add people to media, processing: %s" % name)
|
||||
|
||||
finally:
|
||||
# Link person to content
|
||||
if "Actor" in person_type:
|
||||
role = person.get('Role')
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO actor_link(
|
||||
actor_id, media_id, media_type, role, cast_order)
|
||||
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
self.cursor.execute(query, (actorid, kodiid, mediatype, role, castorder))
|
||||
castorder += 1
|
||||
|
||||
elif "Director" in person_type:
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO director_link(
|
||||
actor_id, media_id, media_type)
|
||||
|
||||
VALUES (?, ?, ?)
|
||||
'''
|
||||
)
|
||||
self.cursor.execute(query, (actorid, kodiid, mediatype))
|
||||
|
||||
elif person_type in ("Writing", "Writer"):
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO writer_link(
|
||||
actor_id, media_id, media_type)
|
||||
|
||||
VALUES (?, ?, ?)
|
||||
'''
|
||||
)
|
||||
self.cursor.execute(query, (actorid, kodiid, mediatype))
|
||||
|
||||
elif "Artist" in person_type:
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO actor_link(
|
||||
actor_id, media_id, media_type)
|
||||
|
||||
VALUES (?, ?, ?)
|
||||
'''
|
||||
)
|
||||
self.cursor.execute(query, (actorid, kodiid, mediatype))
|
||||
if KODIVERSION > 14:
|
||||
actorid = self._getactorid(person['Name'])
|
||||
# Link person to content
|
||||
castorder = self._addPerson(person.get('Role'),
|
||||
person['Type'],
|
||||
actorid,
|
||||
kodiid,
|
||||
mediatype,
|
||||
castorder)
|
||||
# Kodi Helix
|
||||
else:
|
||||
query = ' '.join((
|
||||
|
@ -372,23 +352,19 @@ class Kodidb_Functions():
|
|||
"WHERE strActor = ?",
|
||||
"COLLATE NOCASE"
|
||||
))
|
||||
self.cursor.execute(query, (name,))
|
||||
|
||||
self.cursor.execute(query, (person['Name'],))
|
||||
try:
|
||||
actorid = self.cursor.fetchone()[0]
|
||||
|
||||
except TypeError:
|
||||
# Cast entry does not exists
|
||||
self.cursor.execute("select coalesce(max(idActor),0) from actors")
|
||||
actorid = self.cursor.fetchone()[0] + 1
|
||||
|
||||
query = "INSERT INTO actors(idActor, strActor) values(?, ?)"
|
||||
self.cursor.execute(query, (actorid, name))
|
||||
log.debug("Add people to media, processing: %s" % name)
|
||||
|
||||
self.cursor.execute(query, (actorid, person['Name']))
|
||||
finally:
|
||||
# Link person to content
|
||||
if "Actor" in person_type:
|
||||
if "Actor" == person['Type']:
|
||||
role = person.get('Role')
|
||||
|
||||
if "movie" in mediatype:
|
||||
|
@ -418,12 +394,13 @@ class Kodidb_Functions():
|
|||
VALUES (?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
else: return # Item is invalid
|
||||
|
||||
else:
|
||||
# Item is invalid
|
||||
return
|
||||
self.cursor.execute(query, (actorid, kodiid, role, castorder))
|
||||
castorder += 1
|
||||
|
||||
elif "Director" in person_type:
|
||||
elif "Director" == person['Type']:
|
||||
if "movie" in mediatype:
|
||||
query = (
|
||||
'''
|
||||
|
@ -465,7 +442,7 @@ class Kodidb_Functions():
|
|||
|
||||
self.cursor.execute(query, (actorid, kodiid))
|
||||
|
||||
elif person_type in ("Writing", "Writer"):
|
||||
elif person['Type'] == "Writer":
|
||||
if "movie" in mediatype:
|
||||
query = (
|
||||
'''
|
||||
|
@ -484,29 +461,25 @@ class Kodidb_Functions():
|
|||
VALUES (?, ?)
|
||||
'''
|
||||
)
|
||||
else: return # Item is invalid
|
||||
|
||||
else:
|
||||
# Item is invalid
|
||||
return
|
||||
self.cursor.execute(query, (actorid, kodiid))
|
||||
|
||||
elif "Artist" in person_type:
|
||||
elif "Artist" == person['Type']:
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO artistlinkmusicvideo(
|
||||
idArtist, idMVideo)
|
||||
|
||||
VALUES (?, ?)
|
||||
'''
|
||||
)
|
||||
self.cursor.execute(query, (actorid, kodiid))
|
||||
|
||||
# Add person image to art table
|
||||
if thumb:
|
||||
arttype = person_type.lower()
|
||||
|
||||
if "writing" in arttype:
|
||||
arttype = "writer"
|
||||
|
||||
self.artwork.addOrUpdateArt(thumb, actorid, arttype, "thumb", self.cursor)
|
||||
if person['imageurl']:
|
||||
self.artwork.addOrUpdateArt(person['imageurl'], actorid,
|
||||
person['Type'].lower(), "thumb",
|
||||
self.cursor)
|
||||
|
||||
def existingArt(self, kodiId, mediaType, refresh=False):
|
||||
"""
|
||||
|
@ -554,7 +527,7 @@ class Kodidb_Functions():
|
|||
|
||||
|
||||
# Kodi Isengard, Jarvis, Krypton
|
||||
if self.kodiversion in (15, 16, 17):
|
||||
if KODIVERSION > 14:
|
||||
# Delete current genres for clean slate
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -667,10 +640,8 @@ class Kodidb_Functions():
|
|||
self.cursor.execute(query, (idGenre, kodiid))
|
||||
|
||||
def addStudios(self, kodiid, studios, mediatype):
|
||||
|
||||
for studio in studios:
|
||||
|
||||
if self.kodiversion in (15, 16, 17):
|
||||
if KODIVERSION > 14:
|
||||
# Kodi Isengard, Jarvis, Krypton
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -989,9 +960,8 @@ class Kodidb_Functions():
|
|||
"DVDPlayer", 1))
|
||||
|
||||
def addTags(self, kodiid, tags, mediatype):
|
||||
|
||||
# First, delete any existing tags associated to the id
|
||||
if self.kodiversion in (15, 16, 17):
|
||||
if KODIVERSION > 14:
|
||||
# Kodi Isengard, Jarvis, Krypton
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -1016,8 +986,7 @@ class Kodidb_Functions():
|
|||
self.addTag(kodiid, tag, mediatype)
|
||||
|
||||
def addTag(self, kodiid, tag, mediatype):
|
||||
|
||||
if self.kodiversion in (15, 16, 17):
|
||||
if KODIVERSION > 14:
|
||||
# Kodi Isengard, Jarvis, Krypton
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -1077,9 +1046,8 @@ class Kodidb_Functions():
|
|||
self.cursor.execute(query, (tag_id, kodiid, mediatype))
|
||||
|
||||
def createTag(self, name):
|
||||
|
||||
# This will create and return the tag_id
|
||||
if self.kodiversion in (15, 16, 17):
|
||||
if KODIVERSION > 14:
|
||||
# Kodi Isengard, Jarvis, Krypton
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -1123,12 +1091,9 @@ class Kodidb_Functions():
|
|||
return tag_id
|
||||
|
||||
def updateTag(self, oldtag, newtag, kodiid, mediatype):
|
||||
|
||||
log.debug("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid))
|
||||
|
||||
if self.kodiversion in (15, 16, 17):
|
||||
if KODIVERSION > 14:
|
||||
# Kodi Isengard, Jarvis, Krypton
|
||||
try:
|
||||
try:
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE tag_link",
|
||||
|
@ -1174,8 +1139,7 @@ class Kodidb_Functions():
|
|||
self.cursor.execute(query, (kodiid, mediatype, oldtag,))
|
||||
|
||||
def removeTag(self, kodiid, tagname, mediatype):
|
||||
|
||||
if self.kodiversion in (15, 16, 17):
|
||||
if KODIVERSION > 14:
|
||||
# Kodi Isengard, Jarvis, Krypton
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -1349,7 +1313,7 @@ class Kodidb_Functions():
|
|||
# Create the album
|
||||
self.cursor.execute("select coalesce(max(idAlbum),0) from album")
|
||||
albumid = self.cursor.fetchone()[0] + 1
|
||||
if self.kodiversion in (15, 16, 17):
|
||||
if KODIVERSION > 14:
|
||||
query = (
|
||||
'''
|
||||
INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType)
|
||||
|
|
|
@ -13,7 +13,7 @@ import embydb_functions as embydb
|
|||
import kodidb_functions as kodidb
|
||||
import playbackutils as pbutils
|
||||
from utils import window, settings, CatchExceptions, tryDecode, tryEncode
|
||||
from PlexFunctions import scrobble
|
||||
from PlexFunctions import scrobble, REMAP_TYPE_FROM_PLEXTYPE
|
||||
from playlist import Playlist
|
||||
|
||||
###############################################################################
|
||||
|
@ -51,8 +51,17 @@ class KodiMonitor(xbmc.Monitor):
|
|||
items = {
|
||||
'logLevel': 'plex_logLevel',
|
||||
'enableContext': 'plex_context',
|
||||
'plex_restricteduser': 'plex_restricteduser'
|
||||
'plex_restricteduser': 'plex_restricteduser',
|
||||
'dbSyncIndicator': 'dbSyncIndicator',
|
||||
'remapSMB': 'remapSMB',
|
||||
'replaceSMB': 'replaceSMB',
|
||||
}
|
||||
# Path replacement
|
||||
for typus in REMAP_TYPE_FROM_PLEXTYPE.values():
|
||||
for arg in ('Org', 'New'):
|
||||
key = 'remapSMB%s%s' % (typus, arg)
|
||||
items[key] = key
|
||||
# Reset the window variables from the settings variables
|
||||
for settings_value, window_value in items.iteritems():
|
||||
if window(window_value) != settings(settings_value):
|
||||
log.debug('PKC settings changed: %s is now %s'
|
||||
|
|
|
@ -15,7 +15,7 @@ from utils import window, settings, getUnixTimestamp, kodiSQL, sourcesXML,\
|
|||
ThreadMethods, ThreadMethodsAdditionalStop, LogTime, getScreensaver,\
|
||||
setScreensaver, playlistXSP, language as lang, DateToKodi, reset,\
|
||||
advancedSettingsXML, getKodiVideoDBPath, tryDecode, deletePlaylists,\
|
||||
deleteNodes, ThreadMethodsAdditionalSuspend
|
||||
deleteNodes, ThreadMethodsAdditionalSuspend, create_actor_db_index
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import itemtypes
|
||||
|
@ -386,12 +386,19 @@ class LibrarySync(Thread):
|
|||
|
||||
self.syncThreadNumber = int(settings('syncThreadNumber'))
|
||||
self.installSyncDone = settings('SyncInstallRunDone') == 'true'
|
||||
self.showDbSync = settings('dbSyncIndicator') == 'true'
|
||||
window('dbSyncIndicator', value=settings('dbSyncIndicator'))
|
||||
self.enableMusic = settings('enableMusic') == "true"
|
||||
self.enableBackgroundSync = settings(
|
||||
'enableBackgroundSync') == "true"
|
||||
self.limitindex = int(settings('limitindex'))
|
||||
|
||||
# Init for replacing paths
|
||||
window('remapSMB', value=settings('remapSMB'))
|
||||
window('replaceSMB', value=settings('replaceSMB'))
|
||||
for typus in PF.REMAP_TYPE_FROM_PLEXTYPE.values():
|
||||
for arg in ('Org', 'New'):
|
||||
key = 'remapSMB%s%s' % (typus, arg)
|
||||
window(key, value=settings(key))
|
||||
# Just in case a time sync goes wrong
|
||||
self.timeoffset = int(settings('kodiplextimeoffset'))
|
||||
window('kodiplextimeoffset', value=str(self.timeoffset))
|
||||
|
@ -407,7 +414,7 @@ class LibrarySync(Thread):
|
|||
|
||||
forced: always show popup, even if user setting to off
|
||||
"""
|
||||
if not self.showDbSync:
|
||||
if settings('dbSyncIndicator') != 'true':
|
||||
if not forced:
|
||||
return
|
||||
if icon == "plex":
|
||||
|
@ -551,6 +558,9 @@ class LibrarySync(Thread):
|
|||
# content sync: movies, tvshows, musicvideos, music
|
||||
embyconn.close()
|
||||
|
||||
# Create an index for actors to speed up sync
|
||||
create_actor_db_index()
|
||||
|
||||
@LogTime
|
||||
def fullSync(self, repair=False):
|
||||
"""
|
||||
|
@ -560,18 +570,32 @@ class LibrarySync(Thread):
|
|||
# True: we're syncing only the delta, e.g. different checksum
|
||||
self.compare = not repair
|
||||
|
||||
self.new_items_only = True
|
||||
log.info('Running fullsync for NEW PMS items with rapair=%s' % repair)
|
||||
if self._fullSync() is False:
|
||||
return False
|
||||
self.new_items_only = False
|
||||
log.info('Running fullsync for CHANGED PMS items with repair=%s'
|
||||
% repair)
|
||||
if self._fullSync() is False:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _fullSync(self):
|
||||
xbmc.executebuiltin('InhibitIdleShutdown(true)')
|
||||
screensaver = getScreensaver()
|
||||
setScreensaver(value="")
|
||||
|
||||
# Add sources
|
||||
sourcesXML()
|
||||
if self.new_items_only is True:
|
||||
# Only do the following once for new items
|
||||
# Add sources
|
||||
sourcesXML()
|
||||
|
||||
# Set views. Abort if unsuccessful
|
||||
if not self.maintainViews():
|
||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
||||
setScreensaver(value=screensaver)
|
||||
return False
|
||||
# Set views. Abort if unsuccessful
|
||||
if not self.maintainViews():
|
||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
||||
setScreensaver(value=screensaver)
|
||||
return False
|
||||
|
||||
process = {
|
||||
'movies': self.PlexMovies,
|
||||
|
@ -583,6 +607,8 @@ class LibrarySync(Thread):
|
|||
# Do the processing
|
||||
for itemtype in process:
|
||||
if self.threadStopped():
|
||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
||||
setScreensaver(value=screensaver)
|
||||
return False
|
||||
if not process[itemtype]():
|
||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
||||
|
@ -862,14 +888,34 @@ class LibrarySync(Thread):
|
|||
self.allPlexElementsId APPENDED(!!) dict
|
||||
= {itemid: checksum}
|
||||
"""
|
||||
if self.new_items_only is True:
|
||||
# Only process Plex items that Kodi does not already have in lib
|
||||
for item in xml:
|
||||
itemId = item.attrib.get('ratingKey')
|
||||
if not itemId:
|
||||
# Skipping items 'title=All episodes' without a 'ratingKey'
|
||||
continue
|
||||
self.allPlexElementsId[itemId] = ("K%s%s" %
|
||||
(itemId, item.attrib.get('updatedAt', '')))
|
||||
if itemId not in self.allKodiElementsId:
|
||||
self.updatelist.append({
|
||||
'itemId': itemId,
|
||||
'itemType': itemType,
|
||||
'method': method,
|
||||
'viewName': viewName,
|
||||
'viewId': viewId,
|
||||
'title': item.attrib.get('title', 'Missing Title'),
|
||||
'mediaType': item.attrib.get('type')
|
||||
})
|
||||
return
|
||||
|
||||
if self.compare:
|
||||
# Only process the delta - new or changed items
|
||||
for item in xml:
|
||||
itemId = item.attrib.get('ratingKey')
|
||||
# Skipping items 'title=All episodes' without a 'ratingKey'
|
||||
if not itemId:
|
||||
# Skipping items 'title=All episodes' without a 'ratingKey'
|
||||
continue
|
||||
title = item.attrib.get('title', 'Missing Title Name')
|
||||
plex_checksum = ("K%s%s"
|
||||
% (itemId, item.attrib.get('updatedAt', '')))
|
||||
self.allPlexElementsId[itemId] = plex_checksum
|
||||
|
@ -883,31 +929,29 @@ class LibrarySync(Thread):
|
|||
'method': method,
|
||||
'viewName': viewName,
|
||||
'viewId': viewId,
|
||||
'title': title,
|
||||
'title': item.attrib.get('title', 'Missing Title'),
|
||||
'mediaType': item.attrib.get('type')
|
||||
})
|
||||
else:
|
||||
# Initial or repair sync: get all Plex movies
|
||||
for item in xml:
|
||||
itemId = item.attrib.get('ratingKey')
|
||||
# Skipping items 'title=All episodes' without a 'ratingKey'
|
||||
if not itemId:
|
||||
# Skipping items 'title=All episodes' without a 'ratingKey'
|
||||
continue
|
||||
title = item.attrib.get('title', 'Missing Title Name')
|
||||
plex_checksum = ("K%s%s"
|
||||
% (itemId, item.attrib.get('updatedAt', '')))
|
||||
self.allPlexElementsId[itemId] = plex_checksum
|
||||
self.allPlexElementsId[itemId] = ("K%s%s"
|
||||
% (itemId, item.attrib.get('updatedAt', '')))
|
||||
self.updatelist.append({
|
||||
'itemId': itemId,
|
||||
'itemType': itemType,
|
||||
'method': method,
|
||||
'viewName': viewName,
|
||||
'viewId': viewId,
|
||||
'title': title,
|
||||
'title': item.attrib.get('title', 'Missing Title'),
|
||||
'mediaType': item.attrib.get('type')
|
||||
})
|
||||
|
||||
def GetAndProcessXMLs(self, itemType, showProgress=True):
|
||||
def GetAndProcessXMLs(self, itemType):
|
||||
"""
|
||||
Downloads all XMLs for itemType (e.g. Movies, TV-Shows). Processes them
|
||||
by then calling itemtypes.<itemType>()
|
||||
|
@ -959,19 +1003,18 @@ class LibrarySync(Thread):
|
|||
thread.start()
|
||||
threads.append(thread)
|
||||
log.info("Processing thread spawned")
|
||||
# Start one thread to show sync progress
|
||||
if showProgress:
|
||||
if self.showDbSync:
|
||||
dialog = xbmcgui.DialogProgressBG()
|
||||
thread = ThreadedShowSyncInfo(
|
||||
dialog,
|
||||
[getMetadataLock, processMetadataLock],
|
||||
itemNumber,
|
||||
itemType)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
log.info("Kodi Infobox thread spawned")
|
||||
# Start one thread to show sync progress ONLY for new PMS items
|
||||
if self.new_items_only is True and window('dbSyncIndicator') == 'true':
|
||||
dialog = xbmcgui.DialogProgressBG()
|
||||
thread = ThreadedShowSyncInfo(
|
||||
dialog,
|
||||
[getMetadataLock, processMetadataLock],
|
||||
itemNumber,
|
||||
itemType)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
log.info("Kodi Infobox thread spawned")
|
||||
|
||||
# Wait until finished
|
||||
getMetadataQueue.join()
|
||||
|
@ -1349,9 +1392,9 @@ class LibrarySync(Thread):
|
|||
"""
|
||||
typus = message.get('type')
|
||||
if typus == 'playing':
|
||||
self.process_playing(message['_children'])
|
||||
self.process_playing(message['PlaySessionStateNotification'])
|
||||
elif typus == 'timeline':
|
||||
self.process_timeline(message['_children'])
|
||||
self.process_timeline(message['TimelineEntry'])
|
||||
|
||||
def multi_delete(self, liste, deleteListe):
|
||||
"""
|
||||
|
|
|
@ -59,8 +59,8 @@ class PlayUtils():
|
|||
playurl = tryEncode(self.API.getTranscodeVideoPath(
|
||||
'Transcode',
|
||||
quality={
|
||||
'maxVideoBitrate': self.getBitrate(),
|
||||
'videoResolution': self.getResolution(),
|
||||
'maxVideoBitrate': self.get_bitrate(),
|
||||
'videoResolution': self.get_resolution(),
|
||||
'videoQuality': '100'
|
||||
}))
|
||||
# Set playmethod property
|
||||
|
@ -157,34 +157,45 @@ class PlayUtils():
|
|||
- HEVC codec
|
||||
- window variable 'plex_forcetranscode' set to 'true'
|
||||
(excepting trailers etc.)
|
||||
- video bitrate above specified settings bitrate
|
||||
if the corresponding file settings are set to 'true'
|
||||
"""
|
||||
videoCodec = self.API.getVideoCodec()
|
||||
log.info("videoCodec: %s" % videoCodec)
|
||||
if self.API.getType() in ('clip', 'track'):
|
||||
log.info('Plex clip or music track, not transcoding')
|
||||
return False
|
||||
if window('plex_forcetranscode') == 'true':
|
||||
log.info('User chose to force-transcode')
|
||||
return True
|
||||
if (settings('transcodeHi10P') == 'true' and
|
||||
videoCodec['bitDepth'] == '10'):
|
||||
log.info('Option to transcode 10bit video content enabled.')
|
||||
return True
|
||||
codec = videoCodec['videocodec']
|
||||
if (settings('transcodeHEVC') == 'true' and codec == 'hevc'):
|
||||
log.info('Option to transcode HEVC video codec enabled.')
|
||||
return True
|
||||
if codec is None:
|
||||
# e.g. trailers. Avoids TypeError with "'h265' in codec"
|
||||
log.info('No codec from PMS, not transcoding.')
|
||||
return False
|
||||
if window('plex_forcetranscode') == 'true':
|
||||
log.info('User chose to force-transcode')
|
||||
try:
|
||||
bitrate = int(videoCodec['bitrate'])
|
||||
except (TypeError, ValueError):
|
||||
log.info('No video bitrate from PMS, not transcoding.')
|
||||
return False
|
||||
if bitrate > self.get_max_bitrate():
|
||||
log.info('Video bitrate of %s is higher than the maximal video'
|
||||
'bitrate of %s that the user chose. Transcoding'
|
||||
% (bitrate, self.get_max_bitrate()))
|
||||
return True
|
||||
try:
|
||||
resolution = int(videoCodec['resolution'])
|
||||
except (TypeError, ValueError):
|
||||
log.info('No video resolution from PMS, not transcoding.')
|
||||
return False
|
||||
if 'h265' in codec:
|
||||
if 'h265' in codec or 'hevc' in codec:
|
||||
if resolution >= self.getH265():
|
||||
log.info("Option to transcode h265 enabled. Resolution of "
|
||||
"the media: %s, transcoding limit resolution: %s"
|
||||
log.info("Option to transcode h265/HEVC enabled. Resolution "
|
||||
"of the media: %s, transcoding limit resolution: %s"
|
||||
% (str(resolution), str(self.getH265())))
|
||||
return True
|
||||
return False
|
||||
|
@ -200,32 +211,47 @@ class PlayUtils():
|
|||
return False
|
||||
if self.mustTranscode():
|
||||
return False
|
||||
# Verify the bitrate
|
||||
if not self.isNetworkSufficient():
|
||||
log.info("The network speed is insufficient to direct stream "
|
||||
"file. Transcoding")
|
||||
return False
|
||||
return True
|
||||
|
||||
def isNetworkSufficient(self):
|
||||
"""
|
||||
Returns True if the network is sufficient (set in file settings)
|
||||
"""
|
||||
try:
|
||||
sourceBitrate = int(self.API.getDataFromPartOrMedia('bitrate'))
|
||||
except:
|
||||
log.info('Could not detect source bitrate. It is assumed to be'
|
||||
'sufficient')
|
||||
return True
|
||||
settings = self.getBitrate()
|
||||
log.info("The add-on settings bitrate is: %s, the video bitrate"
|
||||
"required is: %s" % (settings, sourceBitrate))
|
||||
if settings < sourceBitrate:
|
||||
return False
|
||||
return True
|
||||
|
||||
def getBitrate(self):
|
||||
def get_max_bitrate(self):
|
||||
# get the addon video quality
|
||||
videoQuality = settings('maxVideoQualities')
|
||||
bitrate = {
|
||||
'0': 320,
|
||||
'1': 720,
|
||||
'2': 1500,
|
||||
'3': 2000,
|
||||
'4': 3000,
|
||||
'5': 4000,
|
||||
'6': 8000,
|
||||
'7': 10000,
|
||||
'8': 12000,
|
||||
'9': 20000,
|
||||
'10': 40000,
|
||||
'11': 99999999 # deactivated
|
||||
}
|
||||
# max bit rate supported by server (max signed 32bit integer)
|
||||
return bitrate.get(videoQuality, 2147483)
|
||||
|
||||
def getH265(self):
|
||||
"""
|
||||
Returns the user settings for transcoding h265: boundary resolutions
|
||||
of 480, 720 or 1080 as an int
|
||||
|
||||
OR 9999999 (int) if user chose not to transcode
|
||||
"""
|
||||
H265 = {
|
||||
'0': 99999999,
|
||||
'1': 480,
|
||||
'2': 720,
|
||||
'3': 1080
|
||||
}
|
||||
return H265[settings('transcodeH265')]
|
||||
|
||||
def get_bitrate(self):
|
||||
"""
|
||||
Get the desired transcoding bitrate from the settings
|
||||
"""
|
||||
videoQuality = settings('transcoderVideoQualities')
|
||||
bitrate = {
|
||||
'0': 320,
|
||||
|
@ -243,22 +269,10 @@ class PlayUtils():
|
|||
# max bit rate supported by server (max signed 32bit integer)
|
||||
return bitrate.get(videoQuality, 2147483)
|
||||
|
||||
def getH265(self):
|
||||
def get_resolution(self):
|
||||
"""
|
||||
Returns the user settings for transcoding h265: boundary resolutions
|
||||
of 480, 720 or 1080 as an int
|
||||
|
||||
OR 9999999 (int) if user chose not to transcode
|
||||
Get the desired transcoding resolutions from the settings
|
||||
"""
|
||||
H265 = {
|
||||
'0': 9999999,
|
||||
'1': 480,
|
||||
'2': 720,
|
||||
'3': 1080
|
||||
}
|
||||
return H265[settings('transcodeH265')]
|
||||
|
||||
def getResolution(self):
|
||||
chosen = settings('transcoderVideoQualities')
|
||||
res = {
|
||||
'0': '420x420',
|
||||
|
|
|
@ -91,7 +91,7 @@ class UserClient(threading.Thread):
|
|||
if self.machineIdentifier is None:
|
||||
self.machineIdentifier = ''
|
||||
settings('plex_machineIdentifier', value=self.machineIdentifier)
|
||||
log.info('Returning active server: %s' % server)
|
||||
log.debug('Returning active server: %s' % server)
|
||||
return server
|
||||
|
||||
def getSSLverify(self):
|
||||
|
@ -104,7 +104,7 @@ class UserClient(threading.Thread):
|
|||
else settings('sslcert')
|
||||
|
||||
def setUserPref(self):
|
||||
log.info('Setting user preferences')
|
||||
log.debug('Setting user preferences')
|
||||
# Only try to get user avatar if there is a token
|
||||
if self.currToken:
|
||||
url = PlexAPI.PlexAPI().GetUserArtworkURL(self.currUser)
|
||||
|
@ -138,7 +138,7 @@ class UserClient(threading.Thread):
|
|||
lang(33007))
|
||||
|
||||
def loadCurrUser(self, username, userId, usertoken, authenticated=False):
|
||||
log.info('Loading current user')
|
||||
log.debug('Loading current user')
|
||||
doUtils = self.doUtils
|
||||
|
||||
self.currUserId = userId
|
||||
|
@ -148,16 +148,16 @@ class UserClient(threading.Thread):
|
|||
self.sslcert = self.getSSL()
|
||||
|
||||
if authenticated is False:
|
||||
log.info('Testing validity of current token')
|
||||
log.debug('Testing validity of current token')
|
||||
res = PlexAPI.PlexAPI().CheckConnection(self.currServer,
|
||||
token=self.currToken,
|
||||
verifySSL=self.ssl)
|
||||
if res is False:
|
||||
log.error('Answer from PMS is not as expected. Retrying')
|
||||
# PMS probably offline
|
||||
return False
|
||||
elif res == 401:
|
||||
log.warn('Token is no longer valid')
|
||||
return False
|
||||
log.error('Token is no longer valid')
|
||||
return 401
|
||||
elif res >= 400:
|
||||
log.error('Answer from PMS is not as expected. Retrying')
|
||||
return False
|
||||
|
@ -190,23 +190,10 @@ class UserClient(threading.Thread):
|
|||
settings('username', value=username)
|
||||
settings('userid', value=userId)
|
||||
settings('accessToken', value=usertoken)
|
||||
|
||||
dialog = xbmcgui.Dialog()
|
||||
if settings('connectMsg') == "true":
|
||||
if username:
|
||||
dialog.notification(
|
||||
heading=addonName,
|
||||
message="Welcome " + username,
|
||||
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png")
|
||||
else:
|
||||
dialog.notification(
|
||||
heading=addonName,
|
||||
message="Welcome",
|
||||
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png")
|
||||
return True
|
||||
|
||||
def authenticate(self):
|
||||
log.info('Authenticating user')
|
||||
log.debug('Authenticating user')
|
||||
dialog = xbmcgui.Dialog()
|
||||
|
||||
# Give attempts at entering password / selecting user
|
||||
|
@ -243,19 +230,22 @@ class UserClient(threading.Thread):
|
|||
enforceLogin = settings('enforceUserLogin')
|
||||
# Found a user in the settings, try to authenticate
|
||||
if username and enforceLogin == 'false':
|
||||
log.info('Trying to authenticate with old settings')
|
||||
if self.loadCurrUser(username,
|
||||
userId,
|
||||
usertoken,
|
||||
authenticated=False):
|
||||
log.debug('Trying to authenticate with old settings')
|
||||
answ = self.loadCurrUser(username,
|
||||
userId,
|
||||
usertoken,
|
||||
authenticated=False)
|
||||
if answ is True:
|
||||
# SUCCESS: loaded a user from the settings
|
||||
return True
|
||||
else:
|
||||
# Failed to use the settings - delete them!
|
||||
log.info("Failed to use settings credentials. Deleting them")
|
||||
elif answ == 401:
|
||||
log.error("User token no longer valid. Sign user out")
|
||||
settings('username', value='')
|
||||
settings('userid', value='')
|
||||
settings('accessToken', value='')
|
||||
else:
|
||||
log.debug("Could not yet authenticate user")
|
||||
return False
|
||||
|
||||
plx = PlexAPI.PlexAPI()
|
||||
|
||||
|
@ -288,7 +278,7 @@ class UserClient(threading.Thread):
|
|||
return False
|
||||
|
||||
def resetClient(self):
|
||||
log.info("Reset UserClient authentication.")
|
||||
log.debug("Reset UserClient authentication.")
|
||||
self.doUtils.stopSession()
|
||||
|
||||
window('plex_authenticated', clear=True)
|
||||
|
@ -365,7 +355,7 @@ class UserClient(threading.Thread):
|
|||
# Or retried too many times
|
||||
if server and status != "Stop":
|
||||
# Only if there's information found to login
|
||||
log.info("Server found: %s" % server)
|
||||
log.debug("Server found: %s" % server)
|
||||
self.auth = True
|
||||
|
||||
# Minimize CPU load
|
||||
|
|
|
@ -29,6 +29,7 @@ WINDOW = xbmcgui.Window(10000)
|
|||
ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||
|
||||
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
|
||||
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
||||
|
||||
###############################################################################
|
||||
# Main methods
|
||||
|
@ -227,6 +228,25 @@ def getKodiVideoDBPath():
|
|||
% dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")))
|
||||
return dbPath
|
||||
|
||||
|
||||
def create_actor_db_index():
|
||||
"""
|
||||
Index the "actors" because we got a TON - speed up SELECT and WHEN
|
||||
"""
|
||||
conn = kodiSQL('video')
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
cursor.execute("""
|
||||
CREATE UNIQUE INDEX index_name
|
||||
ON actor (name);
|
||||
""")
|
||||
except sqlite3.OperationalError:
|
||||
# Index already exists
|
||||
pass
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def getKodiMusicDBPath():
|
||||
|
||||
dbVersion = {
|
||||
|
@ -402,7 +422,7 @@ def profiling(sortby="cumulative"):
|
|||
s = StringIO.StringIO()
|
||||
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
|
||||
ps.print_stats()
|
||||
log.debug(s.getvalue())
|
||||
log.info(s.getvalue())
|
||||
|
||||
return result
|
||||
|
||||
|
@ -835,7 +855,8 @@ def LogTime(func):
|
|||
starttotal = datetime.now()
|
||||
result = func(*args, **kwargs)
|
||||
elapsedtotal = datetime.now() - starttotal
|
||||
log.debug('It took %s to run the function.' % (elapsedtotal))
|
||||
log.info('It took %s to run the function %s'
|
||||
% (elapsedtotal, func.__name__))
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import xbmc
|
|||
import xbmcvfs
|
||||
|
||||
from utils import window, settings, language as lang, IfExists, tryDecode, \
|
||||
tryEncode, indent, normalize_nodes
|
||||
tryEncode, indent, normalize_nodes, KODIVERSION
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -21,9 +21,6 @@ log = logging.getLogger("PLEX."+__name__)
|
|||
|
||||
class VideoNodes(object):
|
||||
|
||||
def __init__(self):
|
||||
self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
|
||||
|
||||
def commonRoot(self, order, label, tagname, roottype=1):
|
||||
|
||||
if roottype == 0:
|
||||
|
@ -235,7 +232,7 @@ class VideoNodes(object):
|
|||
# Custom query
|
||||
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=recentepisodes&type=%s&tagname=%s&limit=%s"
|
||||
% (viewid, mediatype, tagname, limit))
|
||||
elif self.kodiversion == 14 and nodetype == "inprogressepisodes":
|
||||
elif KODIVERSION == 14 and nodetype == "inprogressepisodes":
|
||||
# Custom query
|
||||
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=inprogressepisodes&limit=%s" % (tagname, limit)
|
||||
elif nodetype == 'ondeck':
|
||||
|
@ -252,7 +249,7 @@ class VideoNodes(object):
|
|||
if mediatype == "photos":
|
||||
windowpath = "ActivateWindow(Pictures,%s,return)" % path
|
||||
else:
|
||||
if self.kodiversion >= 17:
|
||||
if KODIVERSION >= 17:
|
||||
# Krypton
|
||||
windowpath = "ActivateWindow(Videos,%s,return)" % path
|
||||
else:
|
||||
|
@ -374,7 +371,7 @@ class VideoNodes(object):
|
|||
"special://profile/library/video/"))
|
||||
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
|
||||
path = "library://video/plex_%s.xml" % cleantagname
|
||||
if self.kodiversion >= 17:
|
||||
if KODIVERSION >= 17:
|
||||
# Krypton
|
||||
windowpath = "ActivateWindow(Videos,%s,return)" % path
|
||||
else:
|
||||
|
|
|
@ -41,7 +41,11 @@ class WebSocket(threading.Thread):
|
|||
log.error('Error decoding message from websocket: %s' % ex)
|
||||
log.error(message)
|
||||
return False
|
||||
|
||||
try:
|
||||
message = message['NotificationContainer']
|
||||
except KeyError:
|
||||
log.error('Could not parse PMS message: %s' % message)
|
||||
return False
|
||||
# Triage
|
||||
typus = message.get('type')
|
||||
if typus is None:
|
||||
|
@ -139,7 +143,7 @@ class WebSocket(threading.Thread):
|
|||
log.info("Error connecting")
|
||||
self.ws = None
|
||||
counter += 1
|
||||
if counter > 10:
|
||||
if counter > 3:
|
||||
log.warn("Repeatedly could not connect to PMS, "
|
||||
"declaring the connection dead")
|
||||
window('plex_online', value='false')
|
||||
|
|
|
@ -49,7 +49,6 @@
|
|||
<setting id="syncEmptyShows" type="bool" label="30508" default="false" visible="false"/>
|
||||
<setting id="dbSyncIndicator" label="30507" type="bool" default="true" /><!-- 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 type="lsep" label="39052" /><!-- Background Sync -->
|
||||
|
@ -68,7 +67,6 @@
|
|||
<setting id="enableExportSongRating" type="bool" label="30525" default="false" visible="false" />
|
||||
<setting id="kodiplextimeoffset" type="number" label="Time difference in seconds (Koditime - Plextime)" default="0" visible="false" option="int" />
|
||||
<setting id="enableUpdateSongRating" type="bool" label="30526" default="false" visible="false" />
|
||||
<setting id="plex_pathverified" type="bool" default="false" visible="false" /> <!-- If 'false': one single warning message pops up if PKC cannot verify direct paths -->
|
||||
<setting id="themoviedbAPIKey" type="text" default="ae06df54334aa653354e9a010f4b81cb" visible="false"/>
|
||||
<setting id="FanArtTVAPIKey" type="text" default="639191cb0774661597f28a47e7e2bad5" visible="false"/>
|
||||
</category>
|
||||
|
@ -100,10 +98,10 @@
|
|||
<setting id="resumeJumpBack" type="slider" label="30521" default="10" range="0,1,120" option="int" />
|
||||
<setting type="sep" />
|
||||
<setting id="playType" type="enum" label="30002" values="Direct Play (default)|Direct Stream|Force Transcode" default="0" />
|
||||
<setting id="transcoderVideoQualities" type="enum" label="30160" values="420x420, 320Kbps|576x320, 720Kbps|720x480, 1.5Mbps|1024x768, 2Mbps|1280x720, 3Mbps|1280x720, 4Mbps|1920x1080, 8Mbps|1920x1080, 10Mbps|1920x1080, 12Mbps|1920x1080, 20Mbps|1920x1080, 40Mbps" default="10" />
|
||||
<setting id="transcodeH265" type="enum" label="30522" default="0" values="Disabled (default)|480p (and higher)|720p (and higher)|1080p" />
|
||||
<setting id="transcoderVideoQualities" type="enum" label="30160" values="420x420, 320kbps|576x320, 720kbps|720x480, 1.5Mbps|1024x768, 2Mbps|1280x720, 3Mbps|1280x720, 4Mbps|1920x1080, 8Mbps|1920x1080, 10Mbps|1920x1080, 12Mbps|1920x1080, 20Mbps|1920x1080, 40Mbps" default="10" /><!-- Video Quality if Transcoding necessary -->
|
||||
<setting id="maxVideoQualities" type="enum" label="30143" values="320kbps|720kbps|1.5Mbps|2Mbps|3Mbps|4Mbps|8Mbps|10Mbps|12Mbps|20Mbps|40Mbps|deactivated" default="11" /><!-- Always transcode if video bitrate is above -->
|
||||
<setting id="transcodeH265" type="enum" label="30522" default="0" values="Disabled (default)|480p (and higher)|720p (and higher)|1080p" /><!-- Force transcode h265/HEVC -->
|
||||
<setting id="transcodeHi10P" type="bool" label="39063" default="false"/>
|
||||
<setting id="transcodeHEVC" type="bool" label="39065" default="false"/>
|
||||
<setting id="audioBoost" type="slider" label="39001" default="0" range="0,10,100" option="int"/>
|
||||
<setting id="subtitleSize" label="39002" type="slider" option="int" range="0,30,300" default="100" />
|
||||
<setting id="failedCount" type="number" visible="false" default="0" />
|
||||
|
@ -131,7 +129,6 @@
|
|||
-->
|
||||
|
||||
<category label="39073"><!-- Appearance Tweaks -->
|
||||
<setting id="connectMsg" type="bool" label="30249" default="true" />
|
||||
<setting type="lsep" label="39074" /><!-- TV Shows -->
|
||||
<setting id="OnDeckTVextended" type="bool" label="39058" default="true" /><!-- Extend Plex TV Series "On Deck" view to all shows -->
|
||||
<setting id="OnDeckTvAppendShow" type="bool" label="39047" default="false" /><!--On Deck view: Append show title to episode-->
|
||||
|
|
55
service.py
55
service.py
|
@ -58,7 +58,6 @@ addonName = 'PlexKodiConnect'
|
|||
|
||||
class Service():
|
||||
|
||||
welcome_msg = True
|
||||
server_online = True
|
||||
warn_auth = True
|
||||
|
||||
|
@ -87,8 +86,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("Number of sync threads: %s"
|
||||
% settings('syncThreadNumber'))
|
||||
log.warn("Log Level: %s" % logLevel)
|
||||
|
||||
# Reset window props for profile switch
|
||||
|
@ -133,14 +132,13 @@ class Service():
|
|||
# Queue for background sync
|
||||
queue = Queue.Queue()
|
||||
|
||||
connectMsg = True if settings('connectMsg') == 'true' else False
|
||||
|
||||
# Initialize important threads
|
||||
user = userclient.UserClient()
|
||||
ws = wsc.WebSocket(queue)
|
||||
library = librarysync.LibrarySync(queue)
|
||||
plx = PlexAPI.PlexAPI()
|
||||
|
||||
welcome_msg = True
|
||||
counter = 0
|
||||
while not monitor.abortRequested():
|
||||
|
||||
|
@ -163,9 +161,9 @@ class Service():
|
|||
if not self.kodimonitor_running:
|
||||
# Start up events
|
||||
self.warn_auth = True
|
||||
if connectMsg and self.welcome_msg:
|
||||
if welcome_msg is True:
|
||||
# Reset authentication warnings
|
||||
self.welcome_msg = False
|
||||
welcome_msg = False
|
||||
xbmcgui.Dialog().notification(
|
||||
heading=addonName,
|
||||
message="%s %s" % (lang(33000), user.currUser),
|
||||
|
@ -221,21 +219,22 @@ class Service():
|
|||
# Server is offline or cannot be reached
|
||||
# Alert the user and suppress future warning
|
||||
if self.server_online:
|
||||
log.error("Server is offline.")
|
||||
self.server_online = False
|
||||
window('plex_online', value="false")
|
||||
# Suspend threads
|
||||
window('suspend_LibraryThread', value='true')
|
||||
xbmcgui.Dialog().notification(
|
||||
heading=lang(33001),
|
||||
message="%s %s"
|
||||
% (addonName, lang(33002)),
|
||||
icon="special://home/addons/plugin.video."
|
||||
"plexkodiconnect/icon.png",
|
||||
sound=False)
|
||||
self.server_online = False
|
||||
log.error("Plex Media Server went offline")
|
||||
if settings('show_pms_offline') == 'true':
|
||||
xbmcgui.Dialog().notification(
|
||||
heading=lang(33001),
|
||||
message="%s %s"
|
||||
% (addonName, lang(33002)),
|
||||
icon="special://home/addons/plugin.video."
|
||||
"plexkodiconnect/icon.png",
|
||||
sound=False)
|
||||
counter += 1
|
||||
# Periodically check if the IP changed, e.g. per minute
|
||||
if counter > 30:
|
||||
if counter > 20:
|
||||
counter = 0
|
||||
setup = initialsetup.InitialSetup()
|
||||
tmp = setup.PickPMS()
|
||||
|
@ -250,16 +249,18 @@ class Service():
|
|||
if monitor.waitForAbort(5):
|
||||
# Abort was requested while waiting.
|
||||
break
|
||||
self.server_online = True
|
||||
# Alert the user that server is online.
|
||||
xbmcgui.Dialog().notification(
|
||||
heading=addonName,
|
||||
message=lang(33003),
|
||||
icon="special://home/addons/plugin.video."
|
||||
"plexkodiconnect/icon.png",
|
||||
time=5000,
|
||||
sound=False)
|
||||
self.server_online = True
|
||||
log.warn("Server %s is online and ready." % server)
|
||||
if (welcome_msg is False and
|
||||
settings('show_pms_offline') == 'true'):
|
||||
xbmcgui.Dialog().notification(
|
||||
heading=addonName,
|
||||
message=lang(33003),
|
||||
icon="special://home/addons/plugin.video."
|
||||
"plexkodiconnect/icon.png",
|
||||
time=5000,
|
||||
sound=False)
|
||||
log.info("Server %s is online and ready." % server)
|
||||
window('plex_online', value="true")
|
||||
if window('plex_authenticated') == 'true':
|
||||
# Server got offline when we were authenticated.
|
||||
|
@ -273,7 +274,7 @@ class Service():
|
|||
|
||||
break
|
||||
|
||||
if monitor.waitForAbort(2):
|
||||
if monitor.waitForAbort(3):
|
||||
# Abort was requested while waiting.
|
||||
break
|
||||
|
||||
|
|
Loading…
Reference in a new issue