diff --git a/default.py b/default.py
index ddc36b00..2c067b39 100644
--- a/default.py
+++ b/default.py
@@ -70,7 +70,8 @@ class Main:
'reConnect': entrypoint.reConnect,
'delete': entrypoint.deleteItem,
'browseplex': entrypoint.BrowsePlexContent,
- 'ondeck': entrypoint.getOnDeck
+ 'ondeck': entrypoint.getOnDeck,
+ 'chooseServer': entrypoint.chooseServer
}
if "/extrafanart" in sys.argv[0]:
diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml
index 6c2eeace..25c80998 100644
--- a/resources/language/English/strings.xml
+++ b/resources/language/English/strings.xml
@@ -297,7 +297,7 @@
Duration of the video library pop up (in seconds)
Duration of the music library pop up (in seconds)
Server messages
- Generate a new device Id
+ [COLOR yellow]Generate a new unique device Id (e.g. when cloning Kodi)[/COLOR]
Users must log in every time when Kodi restarts
Restart Kodi if you make changes
Complete Re-Sync necessary
@@ -392,6 +392,7 @@
On Deck: Append show title to episode
On Deck: Append season- and episode-number (e.g. S3E2)
Nothing works? Try a full reset!
+ [COLOR yellow]Choose Plex Server from a list[/COLOR]
diff --git a/resources/language/German/strings.xml b/resources/language/German/strings.xml
index a8d107bd..c13163ef 100644
--- a/resources/language/German/strings.xml
+++ b/resources/language/German/strings.xml
@@ -24,6 +24,7 @@
Bei Wiederaufnahme zurückspulen (in Sekunden)
[COLOR yellow]Anzahl Login-Versuche zurücksetzen[/COLOR]
+ [COLOR yellow]Neue einzigartige Geräte-ID generieren (z.B. wenn Kodi geklont wurde)[/COLOR]
Benutzer müssen sich bei jedem Neustart von Kodi neu anmelden
Bei Änderungen Kodi neu starten
Komplette Neusynchronisierung nötig
@@ -329,6 +330,7 @@
"Aktuell": Serien- an Episoden-Titel anfügen
"Aktuell": Staffel und Episode anfügen (z.B. S3E2)
Nichts funktioniert? Setze mal alles zurück!
+ [COLOR yellow]Plex Server aus Liste auswählen[/COLOR]
Plex Home Benutzer abmelden:
diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py
index f46ea86d..81df5a42 100644
--- a/resources/lib/PlexAPI.py
+++ b/resources/lib/PlexAPI.py
@@ -29,17 +29,6 @@ http://stackoverflow.com/questions/2407126/python-urllib2-basic-auth-problem
http://stackoverflow.com/questions/111945/is-there-any-way-to-do-http-put-in-python
(and others...)
"""
-
-
-# Specific to PlexDB:
-import clientinfo
-import utils
-import downloadutils
-import xbmcaddon
-import xbmcgui
-import xbmc
-import xbmcvfs
-
import struct
import time
import urllib2
@@ -52,13 +41,21 @@ import Queue
import traceback
import requests
import xml.etree.ElementTree as etree
+from uuid import uuid4
import re
import json
from urllib import urlencode, quote_plus, unquote
-from PlexFunctions import PlexToKodiTimefactor, PMSHttpsEnabled
+import clientinfo
+import utils
+import downloadutils
+import xbmcaddon
+import xbmcgui
+import xbmc
+import xbmcvfs
+from PlexFunctions import PlexToKodiTimefactor, PMSHttpsEnabled
# Disable requests logging
from requests.packages.urllib3.exceptions import InsecureRequestWarning
@@ -835,8 +832,6 @@ class PlexAPI():
'X-Plex-Version': self.plexversion,
'X-Plex-Client-Identifier': self.clientId,
'X-Plex-Provides': 'player',
- 'X-Plex-Client-Capabilities': 'protocols=shoutcast,http-video;videoDecoders=h264{profile:high&resolution:1080&level:51};audioDecoders=mp3,aac,dts{bitrate:800000&channels:8},ac3{bitrate:800000&channels:8}',
- 'X-Plex-Client-Profile-Extra': 'add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=*&audioCodec=dca,ac3)',
}
if self.token:
@@ -2173,15 +2168,17 @@ class API():
transcodePath = self.server + \
'/video/:/transcode/universal/start.m3u8?'
args = {
- 'copyts': 1,
+ 'protocol': 'hls', # seen in the wild: 'dash', 'http', 'hls'
+ 'session': str(uuid4()),
+ 'fastSeek': 1,
'path': path,
'mediaIndex': 0, # Probably refering to XML reply sheme
'partIndex': self.part,
- 'protocol': 'hls', # seen in the wild: 'dash', 'http', 'hls'
- 'session': self.clientId,
+ # 'copyts': 1,
# 'offset': 0, # Resume point
- 'fastSeek': 1
}
+ # Seem like PHT to let the PMS use the transcoding profile
+ xargs['X-Plex-Device'] = 'Plex Home Theater'
# Currently not used!
if action == "DirectStream":
@@ -2199,9 +2196,7 @@ class API():
args.update(quality)
args.update(argsUpdate)
- url = transcodePath + \
- urlencode(xargs) + '&' + \
- urlencode(args)
+ url = transcodePath + urlencode(xargs) + '&' + urlencode(args)
return url
def externalSubs(self, playurl):
diff --git a/resources/lib/PlexFunctions.py b/resources/lib/PlexFunctions.py
index 6e048df3..ab50e509 100644
--- a/resources/lib/PlexFunctions.py
+++ b/resources/lib/PlexFunctions.py
@@ -383,7 +383,7 @@ def GetPlexPlaylist(itemid, librarySectionUUID, mediatype='movie'):
try:
xml[0].tag
except (IndexError, TypeError, AttributeError):
- logMsg("Error retrieving metadata for %s" % url, -1)
+ logMsg(title, "Error retrieving metadata for %s" % url, -1)
return None
return xml
@@ -425,16 +425,16 @@ def PMSHttpsEnabled(url):
headers={},
timeout=(3, 10))
except requests.exceptions.ConnectionError as e:
- logMsg("Server is offline or cannot be reached. Url: %s, "
- "Error message: %s" % (url, e), -1)
+ logMsg(title, "Server is offline or cannot be reached. Url: %s"
+ ", Error message: %s" % (url, e), -1)
return None
except requests.exceptions.ReadTimeout:
- logMsg("Server timeout reached for Url %s" % url, -1)
+ logMsg(title, "Server timeout reached for Url %s" % url, -1)
return None
else:
answer = False
except requests.exceptions.ReadTimeout:
- logMsg("Server timeout reached for Url %s" % url, -1)
+ logMsg(title, "Server timeout reached for Url %s" % url, -1)
return None
if res.status_code == requests.codes.ok:
return answer
@@ -442,6 +442,26 @@ def PMSHttpsEnabled(url):
return None
+def GetMachineIdentifier(url):
+ """
+ Returns the unique PMS machine identifier of url
+
+ Returns None if something went wrong
+ """
+ xml = downloadutils.DownloadUtils().downloadUrl(
+ url + '/identity', type="GET")
+ try:
+ xml.attrib
+ except:
+ logMsg(title, 'Could not get the PMS machineIdentifier for %s'
+ % url, -1)
+ return None
+ machineIdentifier = xml.attrib.get('machineIdentifier')
+ logMsg(title, 'Found machineIdentifier %s for %s'
+ % (machineIdentifier, url), 1)
+ return machineIdentifier
+
+
def scrobble(ratingKey, state):
"""
Tells the PMS to set an item's watched state to state="watched" or
@@ -458,4 +478,4 @@ def scrobble(ratingKey, state):
else:
return
downloadutils.DownloadUtils().downloadUrl(url, type="GET")
- logMsg("Toggled watched state for Plex item %s" % ratingKey, 1)
+ logMsg(title, "Toggled watched state for Plex item %s" % ratingKey, 1)
diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py
index 2596abaf..10752714 100644
--- a/resources/lib/clientinfo.py
+++ b/resources/lib/clientinfo.py
@@ -69,7 +69,7 @@ class ClientInfo():
If id does not exist, create one and save in Kodi settings file.
"""
- if reset:
+ if reset is True:
utils.window('plex_client_Id', clear=True)
utils.settings('plex_client_Id', value="")
@@ -78,7 +78,8 @@ class ClientInfo():
return clientId
clientId = utils.settings('plex_client_Id')
- if clientId:
+ # Because Kodi appears to cache file settings!!
+ if clientId != "" and reset is False:
utils.window('plex_client_Id', value=clientId)
self.logMsg("Unique device Id plex_client_Id loaded: %s" % clientId, 1)
return clientId
diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py
index 946b3345..9253d688 100644
--- a/resources/lib/entrypoint.py
+++ b/resources/lib/entrypoint.py
@@ -73,36 +73,100 @@ def plexCompanion(fullurl, params):
title, "Not knowing what to do for now - no playQueue sent", -1)
-def reConnect():
+def chooseServer():
"""
- Triggers login to plex.tv and re-authorization
+ Lets user choose from list of PMS (signs out & signs in)
"""
string = xbmcaddon.Addon().getLocalizedString
- utils.logMsg("entrypoint reConnect",
- "Connection resets requested", 0)
+ utils.logMsg(title, "Choosing PMS server requested, starting", 0)
dialog = xbmcgui.Dialog()
# Resetting, please wait
dialog.notification(
heading=addonName,
message=string(39207),
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png",
- time=2000,
+ time=3000,
sound=False)
# Pause library sync thread - user needs to be auth in order to sync
utils.window('suspend_LibraryThread', value='true')
- # Wait max for 5 seconds for all lib scans to finish
+ # Wait max for 25 seconds for all lib scans to finish
counter = 0
while utils.window('emby_dbScan') == 'true':
if counter > 500:
# Failed to reset PMS and plex.tv connects. Try to restart Kodi.
- dialog.ok(heading=addonName,
- message=string(39208))
+ dialog.ok(addonName,
+ string(39208))
# Resuming threads, just in case
utils.window('suspend_LibraryThread', clear=True)
- # Abort reConnection
+ utils.logMsg(title, "Could not stop library sync, aborting", -1)
return
counter += 1
xbmc.sleep(50)
+ utils.logMsg(title, "Successfully stopped library sync", 0)
+
+ # Reset connection details
+ utils.settings('plex_machineIdentifier', value="")
+ utils.settings('plex_servername', value="")
+ utils.settings('https', value="")
+ utils.settings('ipaddress', value="")
+ utils.settings('port', value="")
+
+ # Log out currently signed in user:
+ utils.window('emby_serverStatus', value="401")
+
+ # Above method needs to have run its course! Hence wait
+ counter = 0
+ while utils.window('emby_serverStatus') == "401":
+ if counter > 100:
+ dialog.ok(addonName,
+ string(39208))
+ utils.logMsg(title, "Could not sign out, aborting", -1)
+ return
+ counter += 1
+ xbmc.sleep(50)
+ # Suspend the user client during procedure
+ utils.window('suspend_Userclient', value='true')
+
+ import initialsetup
+ initialsetup.InitialSetup().setup(chooseServer=True)
+ # Request lib sync to get user view data (e.g. watched/unwatched)
+ utils.window('plex_runLibScan', value='full')
+ # Restart user client
+ utils.window('suspend_Userclient', clear=True)
+ utils.logMsg(title, "Choosing new PMS complete", 0)
+
+
+def reConnect():
+ """
+ Triggers login to plex.tv and re-authorization
+ """
+ string = xbmcaddon.Addon().getLocalizedString
+ utils.logMsg(title, "Connection resets requested", 0)
+ dialog = xbmcgui.Dialog()
+ # Resetting, please wait
+ dialog.notification(
+ heading=addonName,
+ message=string(39207),
+ icon="special://home/addons/plugin.video.plexkodiconnect/icon.png",
+ time=3000,
+ sound=False)
+ # Pause library sync thread - user needs to be auth in order to sync
+ utils.window('suspend_LibraryThread', value='true')
+ # Wait max for 25 seconds for all lib scans to finish
+ counter = 0
+ while utils.window('emby_dbScan') == 'true':
+ if counter > 500:
+ # Failed to reset PMS and plex.tv connects. Try to restart Kodi.
+ dialog.ok(addonName,
+ string(39208))
+ # Resuming threads, just in case
+ utils.window('suspend_LibraryThread', clear=True)
+ utils.logMsg(title, "Could not stop library sync, aborting", -1)
+ return
+ counter += 1
+ xbmc.sleep(50)
+
+ utils.logMsg(title, "Successfully stopped library sync", 0)
# Delete plex credentials in settings
utils.settings('myplexlogin', value="true")
@@ -126,9 +190,9 @@ def reConnect():
counter = 0
while utils.window('emby_serverStatus') == "401":
if counter > 100:
- dialog.ok(heading=addonName,
- message=string(39208))
- # Abort reConnection
+ dialog.ok(addonName,
+ string(39208))
+ utils.logMsg(title, "Could not sign out, aborting", -1)
return
counter += 1
xbmc.sleep(50)
@@ -141,6 +205,7 @@ def reConnect():
utils.window('plex_runLibScan', value='full')
# Restart user client
utils.window('suspend_Userclient', clear=True)
+ utils.logMsg(title, "Complete reconnection to plex.tv and PMS complete", 0)
def PassPlaylist(xml, resume=None):
@@ -288,9 +353,8 @@ def resetDeviceId():
dialog = xbmcgui.Dialog()
language = utils.language
- deviceId_old = utils.window('emby_deviceId')
+ deviceId_old = utils.window('plex_client_Id')
try:
- utils.window('emby_deviceId', clear=True)
deviceId = clientinfo.ClientInfo().getDeviceId(reset=True)
except Exception as e:
utils.logMsg(addonName,
diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py
index 7d1557a6..559fe2f1 100644
--- a/resources/lib/initialsetup.py
+++ b/resources/lib/initialsetup.py
@@ -26,7 +26,7 @@ class InitialSetup():
self.userClient = userclient.UserClient()
self.plx = PlexAPI.PlexAPI()
- def setup(self, forcePlexTV=False):
+ def setup(self, forcePlexTV=False, chooseServer=False):
"""
Initial setup. Run once upon startup.
Check server, user, direct paths, music, direct stream if not direct
@@ -51,7 +51,8 @@ class InitialSetup():
# Optionally sign into plex.tv. Will not be called on very first run
# as plexToken will be ''
- if (plexToken and myplexlogin == 'true' and forcePlexTV is False):
+ if (plexToken and myplexlogin == 'true' and forcePlexTV is False
+ and chooseServer is False):
chk = self.plx.CheckConnection('plex.tv', plexToken)
# HTTP Error: unauthorized. Token is no longer valid
if chk == 401 or chk == 403:
@@ -92,7 +93,7 @@ class InitialSetup():
self.logMsg('Failed to update Plex info from plex.tv', -1)
# If a Plex server IP has already been set, return.
- if server and forcePlexTV is False:
+ if server and forcePlexTV is False and chooseServer is False:
self.logMsg("Server is already set.", 0)
self.logMsg("url: %s, Plex machineIdentifier: %s"
% (server, serverid), 0)
@@ -100,7 +101,8 @@ class InitialSetup():
# If not already retrieved myplex info, optionally let user sign in
# to plex.tv. This DOES get called on very first install run
- if ((not plexToken and myplexlogin == 'true') or forcePlexTV):
+ if ((not plexToken and myplexlogin == 'true' and chooseServer is False)
+ or forcePlexTV):
result = self.plx.PlexTvSignInWithPin()
if result:
plexLogin = result['username']
@@ -231,7 +233,7 @@ class InitialSetup():
# self.logMsg("User opted to use direct paths.", 1)
# utils.settings('useDirectPaths', value="1")
- if forcePlexTV:
+ if forcePlexTV is True or chooseServer is True:
return
goToSettings = False
diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py
index a03be48a..bbd29b68 100644
--- a/resources/lib/playbackutils.py
+++ b/resources/lib/playbackutils.py
@@ -184,7 +184,7 @@ class PlaybackUtils():
# For transcoding only, ask for audio/subs pref
if window('emby_%s.playmethod' % playurl) == "Transcode":
window('emby_%s.playmethod' % playurl, clear=True)
- playurl = playutils.audioSubsPref(playurl, listitem)
+ playurl = playutils.audioSubsPref(listitem, playurl)
window('emby_%s.playmethod' % playurl, value="Transcode")
listitem.setPath(playurl)
diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py
index 8e4647ff..61823444 100644
--- a/resources/lib/playutils.py
+++ b/resources/lib/playutils.py
@@ -321,22 +321,24 @@ class PlayUtils():
# Set part where we're at
self.API.setPartNumber(part)
+ if part is None:
+ part = 0
try:
mediastreams = self.item[0][part]
except (TypeError, KeyError, IndexError):
- return
+ return url
audioNum = 0
# Remember 'no subtitles'
subNum = 1
for stream in mediastreams:
# Since Emby returns all possible tracks together, have to sort them.
- index = stream.attrib['id']
- type = stream.attrib['streamType']
+ index = stream.attrib.get('id')
+ type = stream.attrib.get('streamType')
# Audio
if type == "2":
- codec = stream.attrib['codec']
+ codec = stream.attrib.get('codec')
channelLayout = stream.attrib.get('audioChannelLayout', "")
try:
@@ -356,7 +358,7 @@ class PlayUtils():
try:
track = "%s %s" % (subNum+1, stream.attrib['language'])
except:
- track = "%s 'unknown' (%s)" % (subNum+1, stream.attrib['codec'])
+ track = "%s 'unknown' (%s)" % (subNum+1, stream.attrib.get('codec'))
default = stream.attrib.get('default')
forced = stream.attrib.get('forced')
diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py
index ea624fde..842bcf87 100644
--- a/resources/lib/userclient.py
+++ b/resources/lib/userclient.py
@@ -13,6 +13,7 @@ import utils
import downloadutils
import PlexAPI
+from PlexFunctions import GetMachineIdentifier
###############################################################################
@@ -76,11 +77,11 @@ class UserClient(threading.Thread):
settings = utils.settings
# Original host
- self.machineIdentifier = settings('plex_machineIdentifier')
self.servername = settings('plex_servername')
HTTPS = settings('https') == "true"
host = settings('ipaddress')
port = settings('port')
+ self.machineIdentifier = settings('plex_machineIdentifier')
server = host + ":" + port
@@ -94,6 +95,12 @@ class UserClient(threading.Thread):
# If https is false
elif prefix and not HTTPS:
server = "http://%s" % server
+ # User entered IP; we need to get the machineIdentifier
+ if self.machineIdentifier == '' and prefix is True:
+ self.machineIdentifier = GetMachineIdentifier(server)
+ if self.machineIdentifier is None:
+ self.machineIdentifier = ''
+ settings('plex_machineIdentifier', value=self.machineIdentifier)
self.logMsg('Returning active server: %s' % server)
return server
diff --git a/resources/settings.xml b/resources/settings.xml
index edb9f19a..329175ce 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -2,6 +2,7 @@
+
@@ -122,6 +123,7 @@
+