Merge branch 'develop'

This commit is contained in:
tomkat83 2016-04-22 15:28:10 +02:00
commit 9afa19d83a
21 changed files with 597 additions and 234 deletions

View file

@ -29,28 +29,30 @@ This addon synchronizes your media on your Plex server to the native Kodi databa
**Installation in Kodi**
Check out the [Wiki](https://github.com/croneter/PlexKodiConnect/wiki)
Check out the [Wiki for installation instructions](https://github.com/croneter/PlexKodiConnect/wiki)
**What is currently supported ?**
**What is currently supported?**
Currently these features are working:
- Movies
- Movies and Home Videos
- TV Shows
- Full sync at first run, then periodic delta syncs every 30min (customizable)
- [Plex Watch Later / Plex It!](https://support.plex.tv/hc/en-us/sections/200211783-Plex-It-)
- Full sync at first run, then periodic delta syncs every 60min (customizable)
- Instant watched state/resume status sync: This is a 2-way synchronisation. Any watched state or resume status will be instantly (within seconds) reflected to or from Kodi and the server
- Plex Companion: fling Plex media (or anything else) from other Plex devices to PlexKodiConnect
- Play directly from network paths (e.g. "\\\\server\\Plex\\movie.mkv" or "smb://server/Plex/movie.mkv") instead of slow HTTP (e.g. "192.168.1.1:32400"). You have to setup all your Plex libraries to point to such network paths
- Transcoding
- [Plex Companion](https://support.plex.tv/hc/en-us/sections/200276908-Plex-Companion): fling Plex media (or anything else) from other Plex devices to PlexKodiConnect
- Play directly from network paths (e.g. "\\\\server\\Plex\\movie.mkv" or "smb://server/Plex/movie.mkv") instead of 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)
- [Plex Transcoding](https://support.plex.tv/hc/en-us/articles/200250377-Transcoding-Media)
**Known Issues:**
Solutions are unlikely due to the nature of these issues
- *Plex Music:* Kodi tries to scan every(!) single Plex song on startup. This leads to errors in the Kodi log file and potentially even crashes. (Plex puts each song in a "dedicated folder", e.g. 'http://192.168.1.1:32400/library/parts/749450/'. Kodi unsuccessfully tries to scan these folders)
- *Plex Music:* You must have a static IP address for your Plex media server if you plan to use Plex Music features. This is due to the way Kodi works and cannot be helped.
- 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.
- External Plex subtitles (separate file, e.g. mymovie.srt) can be used, but it is impossible to label them correctly/tell what language they are in. However, this is not the case if you use direct paths
- *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. (Plex puts each song in a "dedicated folder", e.g. 'http://192.168.1.1:32400/library/parts/749450/'. Kodi unsuccessfully tries to scan these folders)
- *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
- External Plex subtitles (in separate files, e.g. mymovie.srt) can be used, but it is impossible to label them correctly/tell what language they are in. However, this is not the case if you use direct paths
- If using Addon Paths: In the TV show video nodes On Deck and Recently Added, Kodi will not display the Episode Information screen if you push "i". This is a Kodi issue. It does work if you use Direct Paths
*Background Sync:*
The Plex Server does not tell anyone of the following changes. Hence PKC cannot detect these changes instantly but will notice them on full/delta syncs.
@ -60,13 +62,11 @@ However, some changes to individual items are instantly detected, e.g. if you ma
**Known Bugs:**
- Plex Music for direct paths does not work yet. Items on Kodi get deleted instantly.
- Resume a video does not work yet with Plex Watch Later
**What could be in the pipeline for future development?**
- Watch Later
- Playlists
- Homevideos
- Pictures
- Music Videos
- Automatic updates

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect"
name="PlexKodiConnect"
version="1.1.3"
version="1.1.4"
provider-name="croneter">
<requires>
<import addon="xbmc.python" version="2.1.0"/>

View file

@ -1,3 +1,16 @@
version 1.1.4
(you will need to rescan your library)
- Plex Watch Later available as a separate Video Node!
- Fix Kodi Movie Info showing instead of Episode Info
- New settings option: use default Plex On Deck TV Shows
- Fix TV show on deck view
- Enable playstate update if Kodi does not give us a Kodi DB id
- First startup: Add warning for large music libraries and not using Direct Paths
- Fix KeyError: 'librarySectionID' during syncPMStime
- Retrieve banner art when available
- New setting: I own this PMS (needed if you manually set your IP)
- Increased default full sync frequency to 60 minutes
version 1.1.3
YOU WILL NEED TO RECONNECT TO YOUR PMS: PKC Settings -> Connection -> 'Choose Plex Server from a list'. Just repick your PMS
- 'true' awesome & fast direct play without 'direct paths' if your Plex library points to paths that are accessible to Kodi. Customize paths in the settings

View file

@ -71,7 +71,8 @@ class Main:
'delete': entrypoint.deleteItem,
'browseplex': entrypoint.BrowsePlexContent,
'ondeck': entrypoint.getOnDeck,
'chooseServer': entrypoint.chooseServer
'chooseServer': entrypoint.chooseServer,
'watchlater': entrypoint.watchlater
}
if "/extrafanart" in sys.argv[0]:

View file

@ -20,6 +20,7 @@
<string id="30024">Username</string><!-- Verified -->
<string id="30030">Port Number</string><!-- Verified -->
<string id="30031">I own this Plex Media Server</string>
<string id="30036">Number of recent Movies to show:</string>
<string id="30037">Number of recent TV episodes to show:</string>
<string id="30035">Number of recent Music Albums to show:</string>
@ -356,7 +357,7 @@
<string id="39013">Not yet authorized for Plex server </string>
<string id="39014">Please sign in to plex.tv.</string>
<string id="39015">Problems connecting to server. Pick another server?</string>
<string id="39016">Disable Plex music library?</string>
<string id="39016">Disable Plex music library? (It is HIGHLY recommended to use Plex music only with direct paths for large music libraries. Kodi might crash otherwise)</string>
<string id="39017">Would you now like to go to the plugin's settings to fine-tune PKC? You will need to RESTART Kodi!</string>
<string id="39018">[COLOR yellow]Repair local database (force update all content)[/COLOR]</string>
@ -400,6 +401,7 @@
<string id="39055">Searching for Plex Server</string>
<string id="39056">Used by Sync and when attempting to Direct Play</string>
<string id="39057">Customize Paths</string>
<string id="39058">Extend Plex TV Series "On Deck" view to all shows</string>
<!-- Plex Entrypoint.py -->
<string id="39200">Log-out Plex Home User </string>
@ -413,6 +415,7 @@
<string id="39208">Failed to reset PMS and plex.tv connects. Try to restart Kodi.</string>
<string id="39209">[COLOR yellow]Log-in to plex.tv[/COLOR]</string>
<string id="39210">Not yet connected to Plex Server</string>
<string id="39211">Watch later</string>
<!-- Plex Artwork.py -->

View file

@ -41,6 +41,7 @@
<string id="30026">Benutze 'SIMPLEJSON' anstelle von 'JSON'</string>
<string id="30030">Portnummer:</string>
<string id="30031">Dieser Plex Media Server gehört mir</string>
<string id="30036">Anzahl der zuletzt hinzugefügten Filme:</string>
<string id="30037">Anzahl der zuletzt hinzugefügten Episoden:</string>
<string id="30035">Anzahl der zuletzt hinzugefügten Alben:</string>
@ -294,7 +295,7 @@
<string id="39013">Noch nicht authorisiert für Plex Server </string>
<string id="39014">Bitte loggen Sie sich in plex.tv ein.</string>
<string id="39015">Beim Verbinden mit dem Server sind Probleme aufgetreten. Mit einem anderen Server versuchen?</string>
<string id="39016">Plex Musik Bibliotheken deaktivieren?</string>
<string id="39016">Plex Musik Bibliotheken deaktivieren? (Es wird dringend empfohlen, Musik bei grossen Bibliotheken nur mit "Direct Paths" zu nutzen. Kodi könnte ansonsten abstürzen)</string>
<string id="39017">Möchten Sie nun die Einstellungen des Plugins öffnen? Kodi muss anschliessend neu gestartet werden!</string>
<string id="39018">[COLOR yellow]Lokale Datenbank reparieren (allen Inhalt aktualisieren)[/COLOR]</string>
@ -338,6 +339,7 @@
<string id="39055">Suche Plex Server</string>
<string id="39056">Verwendet für Synchronisierung sowie beim Versuch, Direct Play zu nutzen</string>
<string id="39057">Pfade ändern</string>
<string id="39058">Standard Plex Ansicht "Aktuell" auf alle TV Shows erweitern</string>
@ -353,6 +355,7 @@
<string id="39208">PMS und plex.tv Verbindungen konnten nicht zurückgesetzt werden. Bitte versuchen Sie, Kodi neu zu starten, um das Problem zu beheben.</string>
<string id="39209">[COLOR yellow]Bei plex.tv einloggen[/COLOR]</string>
<string id="39210">Noch nicht mit Plex Server verbunden</string>
<string id="39211">Später ansehen</string>
<!-- Plex Artwork.py -->
<string id="39250">Alle Plex Bilder in Kodi zwischenzuspeichern kann sehr lange dauern. Möchten Sie wirklich fortfahren?</string>

View file

@ -46,7 +46,9 @@ import xbmcvfs
import clientinfo
import utils
import downloadutils
import requests
from PlexFunctions import PlexToKodiTimefactor, PMSHttpsEnabled
import embydb_functions as embydb
@utils.logging
@ -915,12 +917,15 @@ class PlexAPI():
username = answer.attrib.get('title', '')
token = answer.attrib.get('authenticationToken', '')
userid = answer.attrib.get('id', '')
# Write to settings file
utils.settings('username', username)
utils.settings('userid', userid)
utils.settings('accessToken', token)
utils.settings('userid',
answer.attrib.get('id', ''))
utils.settings('plex_restricteduser',
'true' if answer.attrib.get('restricted', '0') == '1'
else 'false')
# Get final token to the PMS we've chosen
url = 'https://plex.tv/api/resources?includeHttps=1'
@ -1256,11 +1261,15 @@ class API():
def getDateCreated(self):
"""
Returns the date when this library item was created or None
Returns the date when this library item was created.
If not found, returns 2000-01-01 10:00:00
"""
res = self.item.attrib.get('addedAt')
if res is not None:
res = utils.DateToKodi(res)
else:
res = '2000-01-01 10:00:00'
return res
def getUserData(self):
@ -1471,8 +1480,11 @@ class API():
"""
res = self.item.attrib.get('audienceRating')
if res is None:
res = self.item.attrib.get('rating', 0.0)
res = float(res)
res = self.item.attrib.get('rating')
try:
res = float(res)
except (ValueError, TypeError):
res = 0.0
return res
def getYear(self):
@ -1494,11 +1506,11 @@ class API():
try:
runtime = float(item['duration'])
except KeyError:
except (KeyError, ValueError):
runtime = 0.0
try:
resume = float(item['viewOffset'])
except KeyError:
except (KeyError, ValueError):
resume = 0.0
runtime = int(runtime * PlexToKodiTimefactor())
@ -1792,33 +1804,38 @@ class API():
'subtitle': subtitlelanguages
}
def __getOneArtwork(self, entry):
try:
artwork = self.item.attrib[entry]
if artwork.startswith('http'):
pass
else:
artwork = "%s%s" % (self.server, artwork)
artwork = self.addPlexCredentialsToUrl(artwork)
except KeyError:
artwork = ""
return artwork
def getAllArtwork(self, parentInfo=False):
"""
Gets the URLs to the Plex artwork, or empty string if not found.
parentInfo=True will check for parent's artwork if None is found
Output:
{
'Primary': Plex key: "thumb". Only 1 pix
'Art':,
'Banner':,
'Logo':,
'Thumb':,
'Disc':,
'Backdrop': [] Plex key: "art". Only 1 pix
'Primary' : xml key 'thumb'
'Art' : always ''
'Banner' : xml key 'banner'
'Logo' : always ''
'Thumb' : xml key 'grandparentThumb'
'Disc' : always ''
'Backdrop' : LIST with ONE xml key "art"
}
"""
item = self.item.attrib
# maxHeight = 10000
# maxWidth = 10000
# customquery = ""
# if utils.settings('compressArt') == "true":
# customquery = "&Quality=90"
# if utils.settings('enableCoverArt') == "false":
# customquery += "&EnableImageEnhancers=false"
allartworks = {
'Primary': "",
'Art': "",
@ -1830,41 +1847,178 @@ class API():
}
# Process backdrops
# Get background artwork URL
try:
background = item['art']
background = "%s%s" % (self.server, background)
background = self.addPlexCredentialsToUrl(background)
except KeyError:
background = ""
allartworks['Backdrop'].append(background)
allartworks['Backdrop'].append(self.__getOneArtwork('art'))
# Get primary "thumb" pictures:
try:
primary = item['thumb']
primary = "%s%s" % (self.server, primary)
primary = self.addPlexCredentialsToUrl(primary)
except KeyError:
primary = ""
allartworks['Primary'] = primary
allartworks['Primary'] = self.__getOneArtwork('thumb')
# Banner (usually only on tv series level)
allartworks['Banner'] = self.__getOneArtwork('banner')
# For e.g. TV shows, get series thumb
allartworks['Thumb'] = self.__getOneArtwork('grandparentThumb')
# Process parent items if the main item is missing artwork
if parentInfo:
# Process parent backdrops
if not allartworks['Backdrop']:
background = item.get('parentArt')
if background:
background = "%s%s" % (self.server, background)
background = self.addPlexCredentialsToUrl(background)
allartworks['Backdrop'].append(background)
allartworks['Backdrop'].append(
self.__getOneArtwork('parentArt'))
if not allartworks['Primary']:
primary = item.get('parentThumb')
if primary:
primary = "%s%s" % (self.server, primary)
primary = self.addPlexCredentialsToUrl(primary)
allartworks['Primary'] = primary
allartworks['Primary'] = self.__getOneArtwork('parentThumb')
return allartworks
# TO BE DONE
# Plex does not get much artwork - go ahead and get the rest from fanart tv only for movie or tv show
type = item.get('type')
if type=='movie' or type=='show':
allartworks = self.getfanartTVimages(allartworks)
if allartworks == None:
self.logMsg('No artwork found for title%s' %str(item.get('title')))
return {}
else:
return allartworks
def getfanartTVimages(self,allartworks):
item = self.item.attrib
tmdb_apiKey = "ae06df54334aa653354e9a010f4b81cb"
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
media_id = None
media_type = None
type = item.get('type')
if type == 'show':
type = 'tv'
title = item.get('title')
# if the title has the year in remove it as tmdb cannot deal with it...making an assumption it is something like The Americans (2015)
if title.endswith(")"): title = title[:-6]
year = item.get('year')
if not type: type="multi"
try:
url = 'http://api.themoviedb.org/3/search/%s?api_key=%s&language=%s&query=%s' %(type,tmdb_apiKey,KODILANGUAGE,utils.try_encode(title))
response = requests.get(url, timeout=5)
if response.status_code == 200:
data = json.loads(response.content.decode('utf-8','replace'))
#find year match
if data and year and data.get("results"):
for item in data["results"]:
if item.get("first_air_date") and year in item.get("first_air_date"):
matchFound = item
break
elif item.get("release_date") and year in item.get("release_date"):
matchFound = item
break
#find exact match based on title
if not matchFound and data and data.get("results",None):
for item in data["results"]:
name = item.get("name")
if not name: name = item.get("title")
original_name = item.get("original_name","")
title_alt = title.lower().replace(" ","").replace("-","").replace(":","").replace("&","").replace(",","")
name_alt = name.lower().replace(" ","").replace("-","").replace(":","").replace("&","").replace(",","")
org_name_alt = original_name.lower().replace(" ","").replace("-","").replace(":","").replace("&","").replace(",","")
if name == title or original_name == title:
#match found for exact title name
matchFound = item
break
elif name.split(" (")[0] == title or title_alt == name_alt or title_alt == org_name_alt:
#match found with substituting some stuff
matchFound = item
break
#if a match was not found, we accept the closest match from TMDB
if not matchFound and len(data.get("results")) > 0 and not len(data.get("results")) > 5:
matchFound = item = data.get("results")[0]
if matchFound:
coverUrl = matchFound.get("poster_path","")
fanartUrl = matchFound.get("backdrop_path","")
id = str(matchFound.get("id",""))
media_type = type
if media_type == "multi" and matchFound.get("media_type"):
media_type = matchFound.get("media_type","")
name = item.get("name")
if not name: name = item.get("title")
#lookup external tmdb_id and perform artwork lookup on fanart.tv
if id:
languages = [KODILANGUAGE,"en"]
for language in languages:
if media_type == "movie":
url = 'http://api.themoviedb.org/3/movie/%s?api_key=%s&language=%s&append_to_response=videos' %(id,tmdb_apiKey,language)
elif media_type == "tv":
url = 'http://api.themoviedb.org/3/tv/%s?api_key=%s&append_to_response=external_ids,videos&language=%s' %(id,tmdb_apiKey,language)
response = requests.get(url)
data = json.loads(response.content.decode('utf-8','replace'))
if data:
if not media_id and data.get("imdb_id"):
media_id = str(data.get("imdb_id"))
if not media_id and data.get("external_ids"):
media_id = str(data["external_ids"].get("tvdb_id"))
#lookup artwork on fanart.tv
if media_id and media_type:
#gets fanart.tv images for given id
api_key = "639191cb0774661597f28a47e7e2bad5"
if type == "movie":
url = 'http://webservice.fanart.tv/v3/movies/%s?api_key=%s' %(media_id,api_key)
else:
url = 'http://webservice.fanart.tv/v3/tv/%s?api_key=%s' %(media_id,api_key)
try:
response = requests.get(url, timeout=15)
except Exception as e:
return allartworks
if response and response.content and response.status_code == 200:
data = json.loads(response.content.decode('utf-8','replace'))
else:
#not found
return allartworks
if data:
#we need to use a little mapping between fanart.tv arttypes and kodi artttypes
fanartTVTypes = [ ("logo","Logo"),("musiclogo","clearlogo"),("disc","Disc"),("clearart","Art"),("banner","Banner"),("clearlogo","Logo"),("background","fanart"),("showbackground","fanart"),("characterart","characterart")]
if type != "artist": fanartTVTypes.append( ("thumb","Thumb") )
if type == "artist": fanartTVTypes.append( ("thumb","folder") )
prefixes = ["",type,"hd","hd"+type]
for fanarttype in fanartTVTypes:
for prefix in prefixes:
fanarttvimage = prefix+fanarttype[0]
if data.has_key(fanarttvimage):
for item in data[fanarttvimage]:
if item.get("lang","") == KODILANGUAGE:
#select image in preferred language
if xbmcvfs.exists(item.get("url")):
allartworks[fanarttype[1]] = item.get("url")
break
if not allartworks.get(fanarttype[1]) or (not "http:" in allartworks.get(fanarttype[1])):
#just grab the first english one as fallback
for item in data[fanarttvimage]:
if item.get("lang","") == "en" or not item.get("lang"):
if xbmcvfs.exists(item.get("url")):
allartworks[fanarttype[1]] = item.get("url")
break
#grab extrafanarts in list
maxfanarts = 10
if "background" in fanarttvimage:
fanartcount = 0
for item in data[fanarttvimage]:
if fanartcount < maxfanarts:
if xbmcvfs.exists(item.get("url")):
allartworks['Backdrop'].append(item.get("url"))
fanartcount += 1
#save extrafanarts as string
return allartworks
except Exception as e:
#no artwork
self.logMsg('No extra artwork found')
return allartworks
def shouldStream(self):
"""
Returns True if the item's 'optimizedForStreaming' is set, False other-
wise
"""
return (True if self.item[0].attrib.get('optimizedForStreaming') == '1'
else False)
def getTranscodeVideoPath(self, action, quality={}):
"""
@ -1886,7 +2040,6 @@ class API():
TODO: mediaIndex
"""
xargs = clientinfo.ClientInfo().getXArgsDeviceInfo()
# For DirectPlay, path/key of PART is needed
if action == "DirectStream":
@ -1981,7 +2134,8 @@ class API():
title, sorttitle = self.getTitle()
if listItem is None:
listItem = xbmcgui.ListItem()
listItem = xbmcgui.ListItem(title)
listItem.setProperty('IsPlayable', 'true')
metadata = {
'genre': self.joinList(self.getGenres()),
@ -2004,17 +2158,34 @@ class API():
'aired': self.getPremiereDate()
}
if "episode" in self.getType():
if self.getType() == "episode":
# Only for tv shows
key, show, season, episode = self.getEpisodeDetails()
season = -1 if season is None else int(season)
episode = -1 if episode is None else int(episode)
metadata['episode'] = episode
metadata['season'] = season
metadata['tvshowtitle'] = show
if season and episode:
listItem.setProperty('episodeno',
"s%.2de%.2d" % (season, episode))
listItem.setIconImage('DefaultTVShows.png')
elif self.getType() == "movie":
listItem.setIconImage('DefaultMovies.png')
else:
listItem.setIconImage('DefaultVideo.png')
listItem.setProperty('IsPlayable', 'true')
listItem.setProperty('IsFolder', 'false')
listItem.setProperty('embyid', self.getRatingKey())
listItem.setLabel(title)
listItem.setProperty('resumetime', str(userdata['Resume']))
listItem.setProperty('totaltime', str(userdata['Runtime']))
plexId = self.getRatingKey()
listItem.setProperty('embyid', plexId)
with embydb.GetEmbyDB() as emby_db:
try:
listItem.setProperty('dbid',
str(emby_db.getItem_byId(plexId)[0]))
except TypeError:
pass
# Expensive operation
listItem.setInfo('video', infoLabels=metadata)
return listItem
@ -2057,6 +2228,7 @@ class API():
'album': 'music',
'song': 'music',
'track': 'music',
'clip': 'clip'
}
typus = types[typus]
if utils.window('remapSMB') == 'true':

View file

@ -312,7 +312,6 @@ class Artwork():
# Artwork is a dictionary
for art in artwork:
if art == "Backdrop":
# Backdrop entry is a list
# Process extra fanart for artwork downloader (fanart, fanart1, fanart2...)

View file

@ -322,10 +322,14 @@ class DownloadUtils():
# And now deal with the consequences of the exceptions
if authenticate is True:
# Make the addon aware of status
window('countError',
value=str(int(window('countError')) + 1))
if int(window('countError')) >= self.connectionAttempts:
self.logMsg('Failed to connect to %s too many times. Declare '
'PMS dead' % url, -1)
window('emby_online', value="false")
return False
try:
window('countError',
value=str(int(window('countError')) + 1))
if int(window('countError')) >= self.connectionAttempts:
self.logMsg('Failed to connect to %s too many times. '
'Declare PMS dead' % url, -1)
window('emby_online', value="false")
except:
# 'countError' not yet set
pass
return None

View file

@ -5,6 +5,7 @@
import json
import os
import sys
import urllib
import xbmc
import xbmcaddon
@ -235,6 +236,16 @@ def doPlayback(itemid, dbid):
Always to return with a "setResolvedUrl"
"""
if dbid == 'plexnode':
# Plex redirect, e.g. watch later. Need to get actual URLs
xml = downloadutils.DownloadUtils().downloadUrl(itemid,
authenticate=False)
if xml in (None, 401):
utils.logMsg(title, "Could not resolve url %s" % itemid, -1)
return xbmcplugin.setResolvedUrl(
int(sys.argv[1]), False, xbmcgui.ListItem())
return pbutils.PlaybackUtils(xml).play(None, dbid)
if utils.window('plex_authenticated') != "true":
utils.logMsg('doPlayback', 'Not yet authenticated for a PMS, abort '
'starting playback', -1)
@ -249,12 +260,12 @@ def doPlayback(itemid, dbid):
return xbmcplugin.setResolvedUrl(
int(sys.argv[1]), False, xbmcgui.ListItem())
item = PlexFunctions.GetPlexMetadata(itemid)
if item is None or item == 401:
xml = PlexFunctions.GetPlexMetadata(itemid)
if xml in (None, 401):
return xbmcplugin.setResolvedUrl(
int(sys.argv[1]), False, xbmcgui.ListItem())
# Everything OK
return pbutils.PlaybackUtils(item).play(itemid, dbid)
return pbutils.PlaybackUtils(xml).play(itemid, dbid)
# utils.logMsg(title, "doPlayback called with itemid=%s, dbid=%s"
# % (itemid, dbid), 1)
@ -327,6 +338,9 @@ def doMainListing():
elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"):
addDirectoryItem(label, path)
# Plex Watch later
addDirectoryItem(string(39211),
"plugin://plugin.video.plexkodiconnect/?mode=watchlater")
# Plex user switch
addDirectoryItem(string(39200) + utils.window('plex_username'),
"plugin://plugin.video.plexkodiconnect/"
@ -1467,9 +1481,42 @@ def getOnDeck(viewid, mediatype, tagname, limit):
tagname: Name of the Plex library, e.g. "My Movies"
limit: Max. number of items to retrieve, e.g. 50
"""
xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
if utils.settings('OnDeckTVextended') == 'false':
# Chances are that this view is used on Kodi startup
# Wait till we've connected to a PMS. At most 30s
counter = 0
while utils.window('plex_authenticated') != 'true':
counter += 1
if counter >= 300:
break
xbmc.sleep(100)
xml = downloadutils.DownloadUtils().downloadUrl(
'{server}/library/sections/%s/onDeck' % viewid)
if xml in (None, 401):
return xbmcplugin.endOfDirectory(int(sys.argv[1]))
url = "plugin://plugin.video.plexkodiconnect.tvshows/"
params = {
'mode': "play"
}
for item in xml:
API = PlexAPI.API(item)
listitem = API.CreateListItemFromPlexItem()
API.AddStreamInfo(listitem)
pbutils.PlaybackUtils(item).setArtwork(listitem)
params['id'] = API.getRatingKey()
params['dbid'] = listitem.getProperty('dbid')
xbmcplugin.addDirectoryItem(
handle=int(sys.argv[1]),
url="%s?%s" % (url, urllib.urlencode(params)),
listitem=listitem)
return xbmcplugin.endOfDirectory(
handle=int(sys.argv[1]),
cacheToDisc=True if utils.settings('enableTextureCache') == 'true'
else False)
# if the addon is called with nextup parameter,
# we return the nextepisodes list of the given tagname
xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
# First we get a list of all the TV shows - filtered by tag
query = {
'jsonrpc': "2.0",
@ -1582,3 +1629,47 @@ def getOnDeck(viewid, mediatype, tagname, limit):
break
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
def watchlater():
"""
Listing for plex.tv Watch Later section (if signed in to plex.tv)
"""
if utils.window('plex_token') == '':
utils.logMsg(title, 'No watch later - not signed in to plex.tv', -1)
return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
if utils.settings('plex_restricteduser') == 'true':
utils.logMsg(title, 'No watch later - restricted user', -1)
return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
xml = downloadutils.DownloadUtils().downloadUrl(
'https://plex.tv/pms/playlists/queue/all',
authenticate=False,
headerOptions={'X-Plex-Token': utils.window('plex_token')})
if xml in (None, 401):
utils.logMsg(title,
'Could not download watch later list from plex.tv', -1)
return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
utils.logMsg(title, 'Displaying watch later plex.tv items', 1)
xbmcplugin.setContent(int(sys.argv[1]), 'movies')
url = "plugin://plugin.video.plexkodiconnect.movies/"
params = {
'mode': "play",
'dbid': 'plexnode'
}
for item in xml:
API = PlexAPI.API(item)
listitem = API.CreateListItemFromPlexItem()
API.AddStreamInfo(listitem)
pbutils.PlaybackUtils(item).setArtwork(listitem)
params['id'] = item.attrib.get('key')
xbmcplugin.addDirectoryItem(
handle=int(sys.argv[1]),
url="%s?%s" % (url, urllib.urlencode(params)),
listitem=listitem)
xbmcplugin.endOfDirectory(
handle=int(sys.argv[1]),
cacheToDisc=True if utils.settings('enableTextureCache') == 'true'
else False)

View file

@ -217,7 +217,9 @@ class InitialSetup():
# Write to Kodi settings file
utils.settings('plex_machineIdentifier', activeServer)
utils.settings('plex_servername', server['name'])
utils.settings('plex_serverowned', server['owned'])
utils.settings('plex_serverowned',
'true' if server['owned'] == '1'
else 'false')
if server['local'] == '1':
scheme = server['scheme']
utils.settings('ipaddress', server['ip'])
@ -279,14 +281,18 @@ class InitialSetup():
line1=string(39016)):
self.logMsg("User opted to disable Plex music library.", 1)
utils.settings('enableMusic', value="false")
else:
utils.advancedSettingsXML()
if goToSettings is False:
# Open Settings page now? You will need to restart!
goToSettings = dialog.yesno(heading=self.addonName,
line1=string(39017))
if goToSettings:
utils.window('emby_serverStatus', value="Stop")
xbmc.executebuiltin(
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
else:
# Open Settings page now? You will need to restart!
if dialog.yesno(heading=self.addonName,
line1=string(39017)):
utils.window('emby_serverStatus', value="Stop")
xbmc.executebuiltin(
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
xbmc.executebuiltin('RestartApp')
# We should always restart to ensure e.g. Kodi settings for Music
# are in use!

View file

@ -1040,6 +1040,8 @@ class TVShows(Items):
toplevelpath = "plugin://plugin.video.plexkodiconnect.tvshows/"
path = "%s%s/" % (toplevelpath, itemid)
# Add top path
toppathid = kodi_db.addPath(toplevelpath)
# UPDATE THE TVSHOW #####
if update_item:
self.logMsg("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1)
@ -1062,8 +1064,6 @@ class TVShows(Items):
else:
self.logMsg("ADD tvshow itemid: %s - Title: %s" % (itemid, title), 1)
# Add top path
toppathid = kodi_db.addPath(toplevelpath)
query = ' '.join((
"UPDATE path",
@ -1098,10 +1098,11 @@ class TVShows(Items):
query = ' '.join((
"UPDATE path",
"SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?",
"SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?, ",
"idParentPath = ?"
"WHERE idPath = ?"
))
kodicursor.execute(query, (path, None, None, 1, pathid))
kodicursor.execute(query, (path, None, None, 1, toppathid, pathid))
# Process cast
people = API.getPeopleList()
@ -1123,6 +1124,8 @@ class TVShows(Items):
self.logMsg("Repairing episodes for showid: %s %s" % (showid, title), 1)
all_episodes = embyserver.getEpisodesbyShow(itemid)
self.added_episode(all_episodes['Items'], None)
self.kodiconn.commit()
self.embyconn.commit()
def add_updateSeason(self, item, viewtag=None, viewid=None):
try:
@ -1180,6 +1183,8 @@ class TVShows(Items):
else:
# Create the reference in emby table
emby_db.addReference(itemid, seasonid, "Season", "season", parentid=viewid, checksum=checksum)
self.kodiconn.commit()
self.embyconn.commit()
def add_updateEpisode(self, item, viewtag=None, viewid=None):
try:
@ -1260,6 +1265,8 @@ class TVShows(Items):
if season is None:
season = -1
if episode is None:
episode = -1
# if item.get('AbsoluteEpisodeNumber'):
# # Anime scenario
# season = 1
@ -1301,9 +1308,9 @@ class TVShows(Items):
# GET THE FILE AND PATH #####
doIndirect = not self.directpath
playurl = API.getFilePath()
if self.directpath:
# Direct paths is set the Kodi way
playurl = API.getFilePath()
if playurl is None:
# Something went wrong, trying to use non-direct paths
doIndirect = True
@ -1318,16 +1325,26 @@ class TVShows(Items):
# Network share
filename = playurl.rsplit("/", 1)[1]
path = playurl.replace(filename, "")
parentPathId = kodi_db.getParentPathId(path)
if doIndirect:
# Set plugin path and media flags using real filename
path = "plugin://plugin.video.plexkodiconnect.movies/"
if playurl is not None:
if '\\' in playurl:
filename = playurl.rsplit('\\', 1)[1]
else:
filename = playurl.rsplit('/', 1)[1]
else:
filename = 'file_not_found'
path = "plugin://plugin.video.plexkodiconnect.tvshows/%s/" % seriesId
params = {
'filename': API.getKey().encode('utf-8'),
'filename': filename.encode('utf-8'),
'id': itemid,
'dbid': episodeid,
'mode': "play"
}
filename = "%s?%s" % (path, urllib.urlencode(params))
parentPathId = kodi_db.addPath(
'plugin://plugin.video.plexkodiconnect.tvshows/')
# UPDATE THE EPISODE #####
if update_item:
@ -1408,10 +1425,11 @@ class TVShows(Items):
query = ' '.join((
"UPDATE path",
"SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?",
"SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?, ",
"idParentPath = ?"
"WHERE idPath = ?"
))
kodicursor.execute(query, (path, None, None, 1, pathid))
kodicursor.execute(query, (path, None, None, 1, parentPathId, pathid))
# Update the file
query = ' '.join((
@ -1456,6 +1474,8 @@ class TVShows(Items):
))
kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid))
kodi_db.addPlaystate(tempfileid, resume, runtime, playcount, dateplayed)
self.kodiconn.commit()
self.embyconn.commit()
def remove(self, itemid):
# Remove showid, fileid, pathid, emby reference

View file

@ -3,6 +3,7 @@
###############################################################################
import xbmc
from ntpath import dirname
import artwork
import clientinfo
@ -56,17 +57,44 @@ class Kodidb_Functions():
For some reason, Kodi ignores this if done via itemtypes while e.g.
adding or updating items. (addPath method does NOT work)
"""
types = ['movies', 'tvshows']
query = ' '.join((
"UPDATE path",
"SET strContent = ?, strScraper = ?",
"WHERE strPath LIKE ?"
))
for typus in types:
self.cursor.execute(
query, (typus,
'metadata.local',
'plugin://plugin.video.plexkodiconnect.%s%%' % typus))
self.cursor.execute(
query, ('movies',
'metadata.local',
'plugin://plugin.video.plexkodiconnect.movies%%'))
def getParentPathId(self, path):
"""
Video DB: Adds all subdirectories to SQL path while setting a "trail"
of parentPathId
"""
if "\\" in path:
# Local path
parentpath = "%s\\" % dirname(dirname(path))
else:
# Network path
parentpath = "%s/" % dirname(dirname(path))
pathid = self.getPath(parentpath)
if pathid is None:
self.cursor.execute("select coalesce(max(idPath),0) from path")
pathid = self.cursor.fetchone()[0] + 1
query = ' '.join((
"INSERT INTO path(idPath, strPath)",
"VALUES (?, ?)"
))
self.cursor.execute(query, (pathid, parentpath))
parentPathid = self.getParentPathId(parentpath)
query = ' '.join((
"UPDATE path",
"SET idParentPath = ?",
"WHERE idPath = ?"
))
self.cursor.execute(query, (parentPathid, pathid))
return pathid
def addPath(self, path, strHash=None):
# SQL won't return existing paths otherwise
@ -780,7 +808,7 @@ class Kodidb_Functions():
"""
Returns the Kodi id (e.g. idMovie, idEpisode) from the item's
title (c00), if there is exactly ONE found for the itemtype.
(False otherwise)
(None otherwise)
itemdetails is the data['item'] response from Kodi
@ -801,11 +829,11 @@ class Kodidb_Functions():
}
"""
try:
type = itemdetails['type']
typus = itemdetails['type']
except:
return False
return
if type == 'movie':
if typus == 'movie':
query = ' '.join((
"SELECT idMovie",
"FROM movie",
@ -814,8 +842,8 @@ class Kodidb_Functions():
try:
rows = self.cursor.execute(query, (itemdetails['title'],))
except:
return False
elif type == 'episode':
return
elif typus == 'episode':
query = ' '.join((
"SELECT idShow",
"FROM tvshow",
@ -824,14 +852,13 @@ class Kodidb_Functions():
try:
rows = self.cursor.execute(query, (itemdetails['showtitle'],))
except:
return False
return
ids = []
for row in rows:
ids.append(row[0])
if len(ids) > 1:
# No unique match possible
return False
showid = ids[0]
return
query = ' '.join((
"SELECT idEpisode",
@ -843,11 +870,11 @@ class Kodidb_Functions():
query,
(itemdetails['season'],
itemdetails['episode'],
showid))
ids[0]))
except:
return False
return
else:
return False
return
ids = []
for row in rows:
@ -856,7 +883,7 @@ class Kodidb_Functions():
return ids[0]
else:
# No unique match possible
return False
return
def getUnplayedItems(self):
"""

View file

@ -156,6 +156,25 @@ class KodiMonitor(xbmc.Monitor):
log = self.logMsg
window = utils.window
# Get currently playing file - can take a while. Will be utf-8!
try:
currentFile = self.xbmcplayer.getPlayingFile()
except:
currentFile = None
count = 0
while currentFile is None:
xbmc.sleep(100)
try:
currentFile = self.xbmcplayer.getPlayingFile()
except:
pass
if count == 50:
log("No current File - Cancelling OnPlayBackStart...", -1)
return
else:
count += 1
log("Currently playing file is: %s" % currentFile.decode('utf-8'), 1)
# Try to get a Kodi ID
item = data.get('item')
try:
@ -166,8 +185,19 @@ class KodiMonitor(xbmc.Monitor):
try:
kodiid = item['id']
except (KeyError, TypeError):
log("Item is invalid for PMS playstate update.", 0)
return
itemType = window("emby_%s.type" % currentFile)
log("No kodi id passed. Playing itemtype is: %s" % itemType, 1)
if itemType in ('movie', 'episode'):
# Window was setup by PKC and is NOT a trailer ('clip')
with kodidb.GetKodiDB('video') as kodi_db:
kodiid = kodi_db.getIdFromTitle(data.get('item'))
if kodiid is None:
log("Skip playstate update. No unique Kodi title found"
" for %s" % data.get('item'), 0)
return
else:
log("Item is invalid for PMS playstate update.", 0)
return
# Get Plex' item id
with embydb.GetEmbyDB() as emby_db:
@ -179,26 +209,6 @@ class KodiMonitor(xbmc.Monitor):
return
log("Found Plex id %s for Kodi id %s" % (plexid, kodiid), 1)
# Get currently playing file - can take a while. Will be utf-8!
try:
currentFile = self.xbmcplayer.getPlayingFile()
xbmc.sleep(300)
except:
currentFile = ""
count = 0
while not currentFile:
xbmc.sleep(100)
try:
currentFile = self.xbmcplayer.getPlayingFile()
except:
pass
if count == 20:
log("No current File - Cancelling OnPlayBackStart...", -1)
return
else:
count += 1
log("Currently playing file is: %s" % currentFile, 1)
# Set some stuff if Kodi initiated playback
if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
(type == "song" and utils.settings('enableMusic') == "true")):

View file

@ -365,7 +365,8 @@ class LibrarySync(Thread):
break
if not view.attrib['type'] == mediatype:
continue
items = PF.GetAllPlexLeaves(view.attrib['key'],
libraryId = view.attrib['key']
items = PF.GetAllPlexLeaves(libraryId,
containerSize=self.limitindex)
if items in (None, 401):
self.logMsg("Could not download section %s"
@ -393,7 +394,6 @@ class LibrarySync(Thread):
self.logMsg("Could not download metadata, aborting time sync", -1)
return False
libraryId = xml[0].attrib['librarySectionID']
timestamp = xml[0].attrib.get('lastViewedAt')
if timestamp is None:
timestamp = xml[0].attrib.get('updatedAt')
@ -1453,7 +1453,7 @@ class LibrarySync(Thread):
sessionKey = item.get('sessionKey')
# Do we already have a sessionKey stored?
if sessionKey not in self.sessionKeys:
if utils.window('plex_serverowned') == '0':
if utils.window('plex_serverowned') == 'false':
# Not our PMS, we are not authorized to get the
# sessions
# On the bright side, it must be us playing :-)
@ -1472,7 +1472,7 @@ class LibrarySync(Thread):
continue
currSess = self.sessionKeys[sessionKey]
if utils.window('plex_serverowned') != '0':
if utils.window('plex_serverowned') != 'false':
# Identify the user - same one as signed on with PKC? Skip
# update if neither session's username nor userid match
# (Owner sometime's returns id '1', not always)
@ -1576,7 +1576,6 @@ class LibrarySync(Thread):
log("---===### Starting LibrarySync ###===---", 0)
if self.enableMusic:
# utils.musiclibXML()
utils.advancedSettingsXML()
while not threadStopped():

View file

@ -16,6 +16,7 @@ import playutils as putils
import playlist
import read_embyserver as embyserver
import utils
import downloadutils
import PlexAPI
import PlexFunctions as PF
@ -60,8 +61,24 @@ class PlaybackUtils():
if not playurl:
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
if dbid in (None, '999999999'):
# Item is not in Kodi database or is a trailer
if dbid in (None, '999999999', 'plexnode'):
# Item is not in Kodi database, is a trailer or plex redirect
# e.g. plex.tv watch later
API.CreateListItemFromPlexItem(listitem)
self.setArtwork(listitem)
if dbid == 'plexnode':
# Need to get yet another xml to get final url
window('emby_%s.playmethod' % playurl, clear=True)
xml = downloadutils.DownloadUtils().downloadUrl(
'{server}%s' % item[0][0][0].attrib.get('key'))
if xml in (None, 401):
log('Could not download %s'
% item[0][0][0].attrib.get('key'), -1)
return xbmcplugin.setResolvedUrl(
int(sys.argv[1]), False, listitem)
playurl = xml[0].attrib.get('key').encode('utf-8')
window('emby_%s.playmethod' % playurl, value='DirectStream')
playmethod = window('emby_%s.playmethod' % playurl)
if playmethod == "Transcode":
window('emby_%s.playmethod' % playurl, clear=True)
@ -69,8 +86,6 @@ class PlaybackUtils():
listitem, playurl.decode('utf-8')).encode('utf-8')
window('emby_%s.playmethod' % playurl, "Transcode")
listitem.setPath(playurl)
self.setArtwork(listitem)
API.CreateListItemFromPlexItem(listitem)
self.setProperties(playurl, listitem)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
@ -312,22 +327,9 @@ class PlaybackUtils():
def setArtwork(self, listItem):
allartwork = self.API.getAllArtwork(parentInfo=True)
# arttypes = {
# 'poster': "Primary",
# 'tvshow.poster': "Primary",
# 'clearart': "Art",
# 'tvshow.clearart': "Art",
# 'clearlogo': "Logo",
# 'tvshow.clearlogo': "Logo",
# 'discart': "Disc",
# 'fanart_image': "Backdrop",
# 'landscape': "Thumb"
# }
arttypes = {
'poster': "Primary",
'tvshow.poster': "Primary",
'tvshow.poster': "Thumb",
'clearart': "Art",
'tvshow.clearart': "Art",
'clearart': "Primary",
@ -336,25 +338,25 @@ class PlaybackUtils():
'tvshow.clearlogo': "Logo",
'discart': "Disc",
'fanart_image': "Backdrop",
'landscape': "Backdrop"
'landscape': "Backdrop",
"banner": "Banner"
}
for arttype in arttypes:
art = arttypes[arttype]
if art == "Backdrop":
try: # Backdrop is a list, grab the first backdrop
try:
# Backdrop is a list, grab the first backdrop
self.setArtProp(listItem, arttype, allartwork[art][0])
except: pass
except:
pass
else:
self.setArtProp(listItem, arttype, allartwork[art])
def setArtProp(self, listItem, arttype, path):
if arttype in (
'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
'medium_landscape', 'medium_poster', 'small_fanartimage',
'medium_fanartimage', 'fanart_noindicators'):
listItem.setProperty(arttype, path)
else:
listItem.setArt({arttype: path})

View file

@ -90,6 +90,10 @@ class PlayUtils():
"""
Returns the path/playurl if successful, False otherwise
"""
# True for e.g. plex.tv watch later
if self.API.shouldStream() is True:
self.logMsg("Plex item optimized for direct streaming", 1)
return False
# set to either 'Direct Stream=1' or 'Transcode=2'
if utils.settings('playType') != "0":

View file

@ -109,7 +109,7 @@ class UserClient(threading.Thread):
if self.currToken:
url = PlexAPI.PlexAPI().GetUserArtworkURL(self.currUser)
if url:
utils.window('EmbyUserImage', value=url)
utils.window('PlexUserImage', value=url)
# Set resume point max
# url = "{server}/emby/System/Configuration?format=json"
# result = doUtils.downloadUrl(url)

View file

@ -514,7 +514,7 @@ def reset():
settings('SyncInstallRunDone', value="false")
# Remove emby info
resp = dialog.yesno("Warning", "Reset all Emby Addon settings?")
resp = dialog.yesno("Warning", "Reset all Plex KodiConnect Addon settings?")
if resp:
# Delete the settings
addon = xbmcaddon.Addon()
@ -615,47 +615,6 @@ def indent(elem, level=0):
elem.tail = i
def musiclibXML():
"""
UNUSED - WORK IN PROGRESS
Deactivates Kodi trying to scan music library on startup
Changes guisettings.xml in Kodi userdata folder:
updateonstartup: set to "false"
"""
path = xbmc.translatePath("special://profile/").decode('utf-8')
xmlpath = "%sguisettings.xml" % path
try:
xmlparse = etree.parse(xmlpath)
except:
# Document is blank or missing
root = etree.Element('settings')
else:
root = xmlparse.getroot()
music = root.find('musiclibrary')
if music is None:
music = etree.SubElement(root, 'musiclibrary')
startup = music.find('updateonstartup')
if startup is None:
# Setting does not exist yet; create it
startup = etree.SubElement(music,
'updateonstartup',
attrib={'default': "true"}).text = "false"
else:
startup.text = "false"
# Prettify and write to file
try:
indent(root)
except:
pass
etree.ElementTree(root).write(xmlpath)
def guisettingsXML():
"""
Returns special://userdata/guisettings.xml as an etree xml root element
@ -673,14 +632,51 @@ def guisettingsXML():
return root
def __setXMLTag(element, tag, value, attrib=None):
"""
Looks for an element's subelement and sets its value.
If "subelement" does not exist, create it using attrib and value.
element : etree element
tag : string/unicode for subelement
value : string/unicode
attrib : dict; will use etree attrib method
Returns the subelement
"""
subelement = element.find(tag)
if subelement is None:
# Setting does not exist yet; create it
if attrib is None:
etree.SubElement(element, tag).text = value
else:
etree.SubElement(element, tag, attrib=attrib).text = value
else:
subelement.text = value
return subelement
def __setSubElement(element, subelement):
"""
Returns an etree element's subelement. Creates one if not exist
"""
answ = element.find(subelement)
if answ is None:
answ = etree.SubElement(element, subelement)
return answ
def advancedSettingsXML():
"""
UNUSED
Deactivates Kodi popup for scanning of music library
Kodi tweaks
Changes advancedsettings.xml, musiclibrary:
backgroundupdate set to "true"
Overrides guisettings.xml in Kodi userdata folder:
updateonstartup : set to "false"
usetags : set to "false"
findremotethumbs : set to "false"
"""
path = xbmc.translatePath("special://profile/").decode('utf-8')
xmlpath = "%sadvancedsettings.xml" % path
@ -693,18 +689,14 @@ def advancedSettingsXML():
else:
root = xmlparse.getroot()
music = root.find('musiclibrary')
if music is None:
music = etree.SubElement(root, 'musiclibrary')
music = __setSubElement(root, 'musiclibrary')
__setXMLTag(music, 'backgroundupdate', "true")
# __setXMLTag(music, 'updateonstartup', "false")
backgroundupdate = music.find('backgroundupdate')
if backgroundupdate is None:
# Setting does not exist yet; create it
backgroundupdate = etree.SubElement(
music,
'backgroundupdate').text = "true"
else:
backgroundupdate.text = "true"
# Subtag 'musicfiles'
# music = __setSubElement(root, 'musicfiles')
# __setXMLTag(music, 'usetags', "false")
# __setXMLTag(music, 'findremotethumbs', "false")
# Prettify and write to file
try:
@ -945,4 +937,16 @@ def deleteNodes():
try:
xbmcvfs.delete(("%s%s" % (path, file.decode('utf-8'))).encode('utf-8'))
except:
logMsg("PLEX", "Failed to file: %s" % file.decode('utf-8'))
logMsg("PLEX", "Failed to file: %s" % file.decode('utf-8'))
def try_encode(text, encoding="utf-8"):
try:
return text.encode(encoding,"ignore")
except:
return text
def try_decode(text, encoding="utf-8"):
try:
return text.decode(encoding,"ignore")
except:
return text

View file

@ -5,6 +5,7 @@
<setting label="39050" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=chooseServer)" option="close" /><!-- Choose Plex Server from a list -->
<setting id="ipaddress" label="30000" type="text" default="" />
<setting id="port" label="30030" type="number" default="32400" />
<setting id="plex_serverowned" label="30031" type="bool" default="true" /><!-- I own this PMS -->
<setting id="https" label="30243" type="bool" default="false" />
<setting id="sslverify" subsetting="true" label="30500" type="bool" default="false" visible="eq(-1,true)" />
<setting id="sslcert" subsetting="true" label="30501" type="file" default="None" visible="eq(-2,true)" />
@ -52,7 +53,7 @@
<setting type="lsep" label="39052" /><!-- Background Sync -->
<setting id="enableBackgroundSync" type="bool" label="39026" default="true" visible="true"/>
<setting id="saftyMargin" type="slider" label="39051" default="30" option="int" range="10,1,300" visible="eq(-1,true)"/>
<setting id="fullSyncInterval" type="number" label="39053" default="30" option="int" />
<setting id="fullSyncInterval" type="number" label="39053" default="60" option="int" />
<setting type="lsep" label="30538" /><!-- Complete Re-Sync necessary -->
<setting id="enableMusic" type="bool" label="30509" default="true" />
@ -119,11 +120,15 @@
<setting id="deviceNameOpt" label="30504" type="bool" default="false" />
<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="Kodi" />
<setting id="companionPort" label="39005" type="number" default="3005" option="int" visible="eq(-3,true)"/>
<setting id="plex_restricteduser" type="bool" default="false" visible="false"/>
</category>
<category label="39045"><!-- Appearance Tweaks -->
<setting id="connectMsg" type="bool" label="30249" default="true" />
<setting type="lsep" label="39046" />
<setting id="OnDeckTVextended" type="bool" label="39058" default="true" /><!-- Extend Plex TV Series "On Deck" view to all shows -->
<setting id="OnDeckTvAppendShow" type="bool" label="39047" default="false" /><!--On Deck view: Append show title to episode-->
<setting id="OnDeckTvAppendSeason" type="bool" label="39048" default="false" /><!--On Deck view: Append season number to episode-->
</category>

View file

@ -82,7 +82,7 @@ class Service():
"emby_initialScan", "emby_customplaylist", "emby_playbackProps",
"plex_runLibScan", "plex_username", "pms_token", "plex_token",
"pms_server", "plex_machineIdentifier", "plex_servername",
"plex_authenticated", "EmbyUserImage", "useDirectPaths",
"plex_authenticated", "PlexUserImage", "useDirectPaths",
"replaceSMB", "remapSMB", "remapSMBmovieOrg", "remapSMBtvOrg",
"remapSMBmusicOrg", "remapSMBmovieNew", "remapSMBtvNew",
"remapSMBmusicNew", "suspend_LibraryThread", "plex_terminateNow",
@ -95,7 +95,7 @@ class Service():
videonodes.VideoNodes().clearProperties()
# Set the minimum database version
window('emby_minDBVersion', value="1.1.1")
window('emby_minDBVersion', value="1.1.4")
def getLogLevel(self):
try: