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** **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: Currently these features are working:
- Movies - Movies and Home Videos
- TV Shows - 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 - 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 - [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 - 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)
- Transcoding - [Plex Transcoding](https://support.plex.tv/hc/en-us/articles/200250377-Transcoding-Media)
**Known Issues:** **Known Issues:**
Solutions are unlikely due to the nature of these 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 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:* 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. - *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. - 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 - 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:* *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. 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:** **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?** **What could be in the pipeline for future development?**
- Watch Later
- Playlists - Playlists
- Homevideos
- Pictures - Pictures
- Music Videos - Music Videos
- Automatic updates - Automatic updates

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" <addon id="plugin.video.plexkodiconnect"
name="PlexKodiConnect" name="PlexKodiConnect"
version="1.1.3" version="1.1.4"
provider-name="croneter"> provider-name="croneter">
<requires> <requires>
<import addon="xbmc.python" version="2.1.0"/> <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 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 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 - '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, 'delete': entrypoint.deleteItem,
'browseplex': entrypoint.BrowsePlexContent, 'browseplex': entrypoint.BrowsePlexContent,
'ondeck': entrypoint.getOnDeck, 'ondeck': entrypoint.getOnDeck,
'chooseServer': entrypoint.chooseServer 'chooseServer': entrypoint.chooseServer,
'watchlater': entrypoint.watchlater
} }
if "/extrafanart" in sys.argv[0]: if "/extrafanart" in sys.argv[0]:

View file

@ -20,6 +20,7 @@
<string id="30024">Username</string><!-- Verified --> <string id="30024">Username</string><!-- Verified -->
<string id="30030">Port Number</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="30036">Number of recent Movies to show:</string>
<string id="30037">Number of recent TV episodes 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> <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="39013">Not yet authorized for Plex server </string>
<string id="39014">Please sign in to plex.tv.</string> <string id="39014">Please sign in to plex.tv.</string>
<string id="39015">Problems connecting to server. Pick another server?</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="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> <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="39055">Searching for Plex Server</string>
<string id="39056">Used by Sync and when attempting to Direct Play</string> <string id="39056">Used by Sync and when attempting to Direct Play</string>
<string id="39057">Customize Paths</string> <string id="39057">Customize Paths</string>
<string id="39058">Extend Plex TV Series "On Deck" view to all shows</string>
<!-- Plex Entrypoint.py --> <!-- Plex Entrypoint.py -->
<string id="39200">Log-out Plex Home User </string> <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="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="39209">[COLOR yellow]Log-in to plex.tv[/COLOR]</string>
<string id="39210">Not yet connected to Plex Server</string> <string id="39210">Not yet connected to Plex Server</string>
<string id="39211">Watch later</string>
<!-- Plex Artwork.py --> <!-- Plex Artwork.py -->

View file

@ -41,6 +41,7 @@
<string id="30026">Benutze 'SIMPLEJSON' anstelle von 'JSON'</string> <string id="30026">Benutze 'SIMPLEJSON' anstelle von 'JSON'</string>
<string id="30030">Portnummer:</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="30036">Anzahl der zuletzt hinzugefügten Filme:</string>
<string id="30037">Anzahl der zuletzt hinzugefügten Episoden:</string> <string id="30037">Anzahl der zuletzt hinzugefügten Episoden:</string>
<string id="30035">Anzahl der zuletzt hinzugefügten Alben:</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="39013">Noch nicht authorisiert für Plex Server </string>
<string id="39014">Bitte loggen Sie sich in plex.tv ein.</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="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="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> <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="39055">Suche Plex Server</string>
<string id="39056">Verwendet für Synchronisierung sowie beim Versuch, Direct Play zu nutzen</string> <string id="39056">Verwendet für Synchronisierung sowie beim Versuch, Direct Play zu nutzen</string>
<string id="39057">Pfade ändern</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="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="39209">[COLOR yellow]Bei plex.tv einloggen[/COLOR]</string>
<string id="39210">Noch nicht mit Plex Server verbunden</string> <string id="39210">Noch nicht mit Plex Server verbunden</string>
<string id="39211">Später ansehen</string>
<!-- Plex Artwork.py --> <!-- Plex Artwork.py -->
<string id="39250">Alle Plex Bilder in Kodi zwischenzuspeichern kann sehr lange dauern. Möchten Sie wirklich fortfahren?</string> <string id="39250">Alle Plex Bilder in Kodi zwischenzuspeichern kann sehr lange dauern. Möchten Sie wirklich fortfahren?</string>

View file

@ -46,7 +46,9 @@ import xbmcvfs
import clientinfo import clientinfo
import utils import utils
import downloadutils import downloadutils
import requests
from PlexFunctions import PlexToKodiTimefactor, PMSHttpsEnabled from PlexFunctions import PlexToKodiTimefactor, PMSHttpsEnabled
import embydb_functions as embydb
@utils.logging @utils.logging
@ -915,12 +917,15 @@ class PlexAPI():
username = answer.attrib.get('title', '') username = answer.attrib.get('title', '')
token = answer.attrib.get('authenticationToken', '') token = answer.attrib.get('authenticationToken', '')
userid = answer.attrib.get('id', '')
# Write to settings file # Write to settings file
utils.settings('username', username) utils.settings('username', username)
utils.settings('userid', userid)
utils.settings('accessToken', token) 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 # Get final token to the PMS we've chosen
url = 'https://plex.tv/api/resources?includeHttps=1' url = 'https://plex.tv/api/resources?includeHttps=1'
@ -1256,11 +1261,15 @@ class API():
def getDateCreated(self): 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') res = self.item.attrib.get('addedAt')
if res is not None: if res is not None:
res = utils.DateToKodi(res) res = utils.DateToKodi(res)
else:
res = '2000-01-01 10:00:00'
return res return res
def getUserData(self): def getUserData(self):
@ -1471,8 +1480,11 @@ class API():
""" """
res = self.item.attrib.get('audienceRating') res = self.item.attrib.get('audienceRating')
if res is None: if res is None:
res = self.item.attrib.get('rating', 0.0) res = self.item.attrib.get('rating')
res = float(res) try:
res = float(res)
except (ValueError, TypeError):
res = 0.0
return res return res
def getYear(self): def getYear(self):
@ -1494,11 +1506,11 @@ class API():
try: try:
runtime = float(item['duration']) runtime = float(item['duration'])
except KeyError: except (KeyError, ValueError):
runtime = 0.0 runtime = 0.0
try: try:
resume = float(item['viewOffset']) resume = float(item['viewOffset'])
except KeyError: except (KeyError, ValueError):
resume = 0.0 resume = 0.0
runtime = int(runtime * PlexToKodiTimefactor()) runtime = int(runtime * PlexToKodiTimefactor())
@ -1792,33 +1804,38 @@ class API():
'subtitle': subtitlelanguages '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): def getAllArtwork(self, parentInfo=False):
""" """
Gets the URLs to the Plex artwork, or empty string if not found. 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: Output:
{ {
'Primary': Plex key: "thumb". Only 1 pix 'Primary' : xml key 'thumb'
'Art':, 'Art' : always ''
'Banner':, 'Banner' : xml key 'banner'
'Logo':, 'Logo' : always ''
'Thumb':, 'Thumb' : xml key 'grandparentThumb'
'Disc':, 'Disc' : always ''
'Backdrop': [] Plex key: "art". Only 1 pix 'Backdrop' : LIST with ONE xml key "art"
} }
""" """
item = self.item.attrib 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 = { allartworks = {
'Primary': "", 'Primary': "",
'Art': "", 'Art': "",
@ -1830,41 +1847,178 @@ class API():
} }
# Process backdrops # Process backdrops
# Get background artwork URL # Get background artwork URL
try: allartworks['Backdrop'].append(self.__getOneArtwork('art'))
background = item['art']
background = "%s%s" % (self.server, background)
background = self.addPlexCredentialsToUrl(background)
except KeyError:
background = ""
allartworks['Backdrop'].append(background)
# Get primary "thumb" pictures: # Get primary "thumb" pictures:
try: allartworks['Primary'] = self.__getOneArtwork('thumb')
primary = item['thumb'] # Banner (usually only on tv series level)
primary = "%s%s" % (self.server, primary) allartworks['Banner'] = self.__getOneArtwork('banner')
primary = self.addPlexCredentialsToUrl(primary) # For e.g. TV shows, get series thumb
except KeyError: allartworks['Thumb'] = self.__getOneArtwork('grandparentThumb')
primary = ""
allartworks['Primary'] = primary
# Process parent items if the main item is missing artwork # Process parent items if the main item is missing artwork
if parentInfo: if parentInfo:
# Process parent backdrops # Process parent backdrops
if not allartworks['Backdrop']: if not allartworks['Backdrop']:
background = item.get('parentArt') allartworks['Backdrop'].append(
if background: self.__getOneArtwork('parentArt'))
background = "%s%s" % (self.server, background)
background = self.addPlexCredentialsToUrl(background)
allartworks['Backdrop'].append(background)
if not allartworks['Primary']: if not allartworks['Primary']:
primary = item.get('parentThumb') allartworks['Primary'] = self.__getOneArtwork('parentThumb')
if primary:
primary = "%s%s" % (self.server, primary)
primary = self.addPlexCredentialsToUrl(primary)
allartworks['Primary'] = primary
return allartworks 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={}): def getTranscodeVideoPath(self, action, quality={}):
""" """
@ -1886,7 +2040,6 @@ class API():
TODO: mediaIndex TODO: mediaIndex
""" """
xargs = clientinfo.ClientInfo().getXArgsDeviceInfo() xargs = clientinfo.ClientInfo().getXArgsDeviceInfo()
# For DirectPlay, path/key of PART is needed # For DirectPlay, path/key of PART is needed
if action == "DirectStream": if action == "DirectStream":
@ -1981,7 +2134,8 @@ class API():
title, sorttitle = self.getTitle() title, sorttitle = self.getTitle()
if listItem is None: if listItem is None:
listItem = xbmcgui.ListItem() listItem = xbmcgui.ListItem(title)
listItem.setProperty('IsPlayable', 'true')
metadata = { metadata = {
'genre': self.joinList(self.getGenres()), 'genre': self.joinList(self.getGenres()),
@ -2004,17 +2158,34 @@ class API():
'aired': self.getPremiereDate() 'aired': self.getPremiereDate()
} }
if "episode" in self.getType(): if self.getType() == "episode":
# Only for tv shows # Only for tv shows
key, show, season, episode = self.getEpisodeDetails() 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['episode'] = episode
metadata['season'] = season metadata['season'] = season
metadata['tvshowtitle'] = show 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('resumetime', str(userdata['Resume']))
listItem.setProperty('IsFolder', 'false') listItem.setProperty('totaltime', str(userdata['Runtime']))
listItem.setProperty('embyid', self.getRatingKey()) plexId = self.getRatingKey()
listItem.setLabel(title) 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) listItem.setInfo('video', infoLabels=metadata)
return listItem return listItem
@ -2057,6 +2228,7 @@ class API():
'album': 'music', 'album': 'music',
'song': 'music', 'song': 'music',
'track': 'music', 'track': 'music',
'clip': 'clip'
} }
typus = types[typus] typus = types[typus]
if utils.window('remapSMB') == 'true': if utils.window('remapSMB') == 'true':

View file

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

View file

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

View file

@ -5,6 +5,7 @@
import json import json
import os import os
import sys import sys
import urllib
import xbmc import xbmc
import xbmcaddon import xbmcaddon
@ -235,6 +236,16 @@ def doPlayback(itemid, dbid):
Always to return with a "setResolvedUrl" 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": if utils.window('plex_authenticated') != "true":
utils.logMsg('doPlayback', 'Not yet authenticated for a PMS, abort ' utils.logMsg('doPlayback', 'Not yet authenticated for a PMS, abort '
'starting playback', -1) 'starting playback', -1)
@ -249,12 +260,12 @@ def doPlayback(itemid, dbid):
return xbmcplugin.setResolvedUrl( return xbmcplugin.setResolvedUrl(
int(sys.argv[1]), False, xbmcgui.ListItem()) int(sys.argv[1]), False, xbmcgui.ListItem())
item = PlexFunctions.GetPlexMetadata(itemid) xml = PlexFunctions.GetPlexMetadata(itemid)
if item is None or item == 401: if xml in (None, 401):
return xbmcplugin.setResolvedUrl( return xbmcplugin.setResolvedUrl(
int(sys.argv[1]), False, xbmcgui.ListItem()) int(sys.argv[1]), False, xbmcgui.ListItem())
# Everything OK # 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" # utils.logMsg(title, "doPlayback called with itemid=%s, dbid=%s"
# % (itemid, dbid), 1) # % (itemid, dbid), 1)
@ -327,6 +338,9 @@ def doMainListing():
elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"): elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"):
addDirectoryItem(label, path) addDirectoryItem(label, path)
# Plex Watch later
addDirectoryItem(string(39211),
"plugin://plugin.video.plexkodiconnect/?mode=watchlater")
# Plex user switch # Plex user switch
addDirectoryItem(string(39200) + utils.window('plex_username'), addDirectoryItem(string(39200) + utils.window('plex_username'),
"plugin://plugin.video.plexkodiconnect/" "plugin://plugin.video.plexkodiconnect/"
@ -1467,9 +1481,42 @@ def getOnDeck(viewid, mediatype, tagname, limit):
tagname: Name of the Plex library, e.g. "My Movies" tagname: Name of the Plex library, e.g. "My Movies"
limit: Max. number of items to retrieve, e.g. 50 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, # if the addon is called with nextup parameter,
# we return the nextepisodes list of the given tagname # 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 # First we get a list of all the TV shows - filtered by tag
query = { query = {
'jsonrpc': "2.0", 'jsonrpc': "2.0",
@ -1582,3 +1629,47 @@ def getOnDeck(viewid, mediatype, tagname, limit):
break break
xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) 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 # Write to Kodi settings file
utils.settings('plex_machineIdentifier', activeServer) utils.settings('plex_machineIdentifier', activeServer)
utils.settings('plex_servername', server['name']) 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': if server['local'] == '1':
scheme = server['scheme'] scheme = server['scheme']
utils.settings('ipaddress', server['ip']) utils.settings('ipaddress', server['ip'])
@ -279,14 +281,18 @@ class InitialSetup():
line1=string(39016)): line1=string(39016)):
self.logMsg("User opted to disable Plex music library.", 1) self.logMsg("User opted to disable Plex music library.", 1)
utils.settings('enableMusic', value="false") 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: if goToSettings:
utils.window('emby_serverStatus', value="Stop")
xbmc.executebuiltin( xbmc.executebuiltin(
'Addon.OpenSettings(plugin.video.plexkodiconnect)') 'Addon.OpenSettings(plugin.video.plexkodiconnect)')
else: else:
# Open Settings page now? You will need to restart! xbmc.executebuiltin('RestartApp')
if dialog.yesno(heading=self.addonName, # We should always restart to ensure e.g. Kodi settings for Music
line1=string(39017)): # are in use!
utils.window('emby_serverStatus', value="Stop")
xbmc.executebuiltin(
'Addon.OpenSettings(plugin.video.plexkodiconnect)')

View file

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

View file

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

View file

@ -156,6 +156,25 @@ class KodiMonitor(xbmc.Monitor):
log = self.logMsg log = self.logMsg
window = utils.window 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 # Try to get a Kodi ID
item = data.get('item') item = data.get('item')
try: try:
@ -166,8 +185,19 @@ class KodiMonitor(xbmc.Monitor):
try: try:
kodiid = item['id'] kodiid = item['id']
except (KeyError, TypeError): except (KeyError, TypeError):
log("Item is invalid for PMS playstate update.", 0) itemType = window("emby_%s.type" % currentFile)
return 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 # Get Plex' item id
with embydb.GetEmbyDB() as emby_db: with embydb.GetEmbyDB() as emby_db:
@ -179,26 +209,6 @@ class KodiMonitor(xbmc.Monitor):
return return
log("Found Plex id %s for Kodi id %s" % (plexid, kodiid), 1) 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 # Set some stuff if Kodi initiated playback
if ((utils.settings('useDirectPaths') == "1" and not type == "song") or if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
(type == "song" and utils.settings('enableMusic') == "true")): (type == "song" and utils.settings('enableMusic') == "true")):

View file

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

View file

@ -16,6 +16,7 @@ import playutils as putils
import playlist import playlist
import read_embyserver as embyserver import read_embyserver as embyserver
import utils import utils
import downloadutils
import PlexAPI import PlexAPI
import PlexFunctions as PF import PlexFunctions as PF
@ -60,8 +61,24 @@ class PlaybackUtils():
if not playurl: if not playurl:
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
if dbid in (None, '999999999'): if dbid in (None, '999999999', 'plexnode'):
# Item is not in Kodi database or is a trailer # 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) playmethod = window('emby_%s.playmethod' % playurl)
if playmethod == "Transcode": if playmethod == "Transcode":
window('emby_%s.playmethod' % playurl, clear=True) window('emby_%s.playmethod' % playurl, clear=True)
@ -69,8 +86,6 @@ class PlaybackUtils():
listitem, playurl.decode('utf-8')).encode('utf-8') listitem, playurl.decode('utf-8')).encode('utf-8')
window('emby_%s.playmethod' % playurl, "Transcode") window('emby_%s.playmethod' % playurl, "Transcode")
listitem.setPath(playurl) listitem.setPath(playurl)
self.setArtwork(listitem)
API.CreateListItemFromPlexItem(listitem)
self.setProperties(playurl, listitem) self.setProperties(playurl, listitem)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
@ -312,22 +327,9 @@ class PlaybackUtils():
def setArtwork(self, listItem): def setArtwork(self, listItem):
allartwork = self.API.getAllArtwork(parentInfo=True) 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 = { arttypes = {
'poster': "Primary", 'poster': "Primary",
'tvshow.poster': "Primary", 'tvshow.poster': "Thumb",
'clearart': "Art", 'clearart': "Art",
'tvshow.clearart': "Art", 'tvshow.clearart': "Art",
'clearart': "Primary", 'clearart': "Primary",
@ -336,25 +338,25 @@ class PlaybackUtils():
'tvshow.clearlogo': "Logo", 'tvshow.clearlogo': "Logo",
'discart': "Disc", 'discart': "Disc",
'fanart_image': "Backdrop", 'fanart_image': "Backdrop",
'landscape': "Backdrop" 'landscape': "Backdrop",
"banner": "Banner"
} }
for arttype in arttypes: for arttype in arttypes:
art = arttypes[arttype] art = arttypes[arttype]
if art == "Backdrop": 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]) self.setArtProp(listItem, arttype, allartwork[art][0])
except: pass except:
pass
else: else:
self.setArtProp(listItem, arttype, allartwork[art]) self.setArtProp(listItem, arttype, allartwork[art])
def setArtProp(self, listItem, arttype, path): def setArtProp(self, listItem, arttype, path):
if arttype in ( if arttype in (
'thumb', 'fanart_image', 'small_poster', 'tiny_poster', 'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
'medium_landscape', 'medium_poster', 'small_fanartimage', 'medium_landscape', 'medium_poster', 'small_fanartimage',
'medium_fanartimage', 'fanart_noindicators'): 'medium_fanartimage', 'fanart_noindicators'):
listItem.setProperty(arttype, path) listItem.setProperty(arttype, path)
else: else:
listItem.setArt({arttype: path}) listItem.setArt({arttype: path})

View file

@ -90,6 +90,10 @@ class PlayUtils():
""" """
Returns the path/playurl if successful, False otherwise 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' # set to either 'Direct Stream=1' or 'Transcode=2'
if utils.settings('playType') != "0": if utils.settings('playType') != "0":

View file

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

View file

@ -514,7 +514,7 @@ def reset():
settings('SyncInstallRunDone', value="false") settings('SyncInstallRunDone', value="false")
# Remove emby info # Remove emby info
resp = dialog.yesno("Warning", "Reset all Emby Addon settings?") resp = dialog.yesno("Warning", "Reset all Plex KodiConnect Addon settings?")
if resp: if resp:
# Delete the settings # Delete the settings
addon = xbmcaddon.Addon() addon = xbmcaddon.Addon()
@ -615,47 +615,6 @@ def indent(elem, level=0):
elem.tail = i 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(): def guisettingsXML():
""" """
Returns special://userdata/guisettings.xml as an etree xml root element Returns special://userdata/guisettings.xml as an etree xml root element
@ -673,14 +632,51 @@ def guisettingsXML():
return root 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(): def advancedSettingsXML():
""" """
UNUSED Kodi tweaks
Deactivates Kodi popup for scanning of music library
Changes advancedsettings.xml, musiclibrary: Changes advancedsettings.xml, musiclibrary:
backgroundupdate set to "true" 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') path = xbmc.translatePath("special://profile/").decode('utf-8')
xmlpath = "%sadvancedsettings.xml" % path xmlpath = "%sadvancedsettings.xml" % path
@ -693,18 +689,14 @@ def advancedSettingsXML():
else: else:
root = xmlparse.getroot() root = xmlparse.getroot()
music = root.find('musiclibrary') music = __setSubElement(root, 'musiclibrary')
if music is None: __setXMLTag(music, 'backgroundupdate', "true")
music = etree.SubElement(root, 'musiclibrary') # __setXMLTag(music, 'updateonstartup', "false")
backgroundupdate = music.find('backgroundupdate') # Subtag 'musicfiles'
if backgroundupdate is None: # music = __setSubElement(root, 'musicfiles')
# Setting does not exist yet; create it # __setXMLTag(music, 'usetags', "false")
backgroundupdate = etree.SubElement( # __setXMLTag(music, 'findremotethumbs', "false")
music,
'backgroundupdate').text = "true"
else:
backgroundupdate.text = "true"
# Prettify and write to file # Prettify and write to file
try: try:
@ -946,3 +938,15 @@ def deleteNodes():
xbmcvfs.delete(("%s%s" % (path, file.decode('utf-8'))).encode('utf-8')) xbmcvfs.delete(("%s%s" % (path, file.decode('utf-8'))).encode('utf-8'))
except: 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 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="ipaddress" label="30000" type="text" default="" />
<setting id="port" label="30030" type="number" default="32400" /> <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="https" label="30243" type="bool" default="false" />
<setting id="sslverify" subsetting="true" label="30500" type="bool" default="false" visible="eq(-1,true)" /> <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)" /> <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 type="lsep" label="39052" /><!-- Background Sync -->
<setting id="enableBackgroundSync" type="bool" label="39026" default="true" visible="true"/> <setting id="enableBackgroundSync" type="bool" label="39026" default="true" visible="true"/>
<setting id="saftyMargin" type="slider" label="39051" default="30" option="int" range="10,1,300" visible="eq(-1,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 type="lsep" label="30538" /><!-- Complete Re-Sync necessary -->
<setting id="enableMusic" type="bool" label="30509" default="true" /> <setting id="enableMusic" type="bool" label="30509" default="true" />
@ -119,11 +120,15 @@
<setting id="deviceNameOpt" label="30504" type="bool" default="false" /> <setting id="deviceNameOpt" label="30504" type="bool" default="false" />
<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="Kodi" /> <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="companionPort" label="39005" type="number" default="3005" option="int" visible="eq(-3,true)"/>
<setting id="plex_restricteduser" type="bool" default="false" visible="false"/>
</category> </category>
<category label="39045"><!-- Appearance Tweaks --> <category label="39045"><!-- Appearance Tweaks -->
<setting id="connectMsg" type="bool" label="30249" default="true" /> <setting id="connectMsg" type="bool" label="30249" default="true" />
<setting type="lsep" label="39046" /> <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="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--> <setting id="OnDeckTvAppendSeason" type="bool" label="39048" default="false" /><!--On Deck view: Append season number to episode-->
</category> </category>

View file

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