Merge branch 'develop'
This commit is contained in:
commit
9afa19d83a
21 changed files with 597 additions and 234 deletions
28
README.md
28
README.md
|
@ -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
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]:
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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...)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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")):
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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>
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue