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**
|
**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
|
||||||
|
|
|
@ -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"/>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]:
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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':
|
||||||
|
|
|
@ -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...)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)')
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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")):
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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})
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -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>
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue