Merge Master

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

View file

@ -1,52 +1,65 @@
# PlexKodiConnect (PKC)
**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

View file

@ -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"/>

View file

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

View file

@ -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>

View file

@ -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>

View file

@ -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):

View file

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

View file

@ -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

View file

@ -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)

View file

@ -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')

View file

@ -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))

View file

@ -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)

View file

@ -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'

View file

@ -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):
"""

View file

@ -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',

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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')

View file

@ -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-->

View file

@ -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