summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorshaun <shaun@bluebit.com.au>2018-01-25 08:19:32 +1100
committerMartijn Kaijser <martijn@xbmc.org>2018-01-31 20:19:15 +0100
commit091ca6e9975d8325e2fb46610e2761391f9a3998 (patch)
tree24102c5f0a2c83cccfa2db8b0fd8a37e6a3ee563
parentdf562079c5d2ca5f0c5db8b66cbb78c5d8527e30 (diff)
[plugin.video.embycon] 1.4.39
-rw-r--r--plugin.video.embycon/addon.xml14
-rw-r--r--plugin.video.embycon/resources/language/resource.language.en_gb/strings.po113
-rw-r--r--plugin.video.embycon/resources/lib/action_menu.py2
-rw-r--r--plugin.video.embycon/resources/lib/clientinfo.py14
-rw-r--r--plugin.video.embycon/resources/lib/datamanager.py142
-rw-r--r--plugin.video.embycon/resources/lib/downloadutils.py253
-rw-r--r--plugin.video.embycon/resources/lib/error.py152
-rw-r--r--plugin.video.embycon/resources/lib/functions.py974
-rw-r--r--plugin.video.embycon/resources/lib/item_functions.py556
-rw-r--r--plugin.video.embycon/resources/lib/kodi_utils.py10
-rw-r--r--plugin.video.embycon/resources/lib/menu_functions.py232
-rw-r--r--plugin.video.embycon/resources/lib/play_utils.py687
-rw-r--r--plugin.video.embycon/resources/lib/server_detect.py123
-rw-r--r--plugin.video.embycon/resources/lib/server_sessions.py17
-rw-r--r--plugin.video.embycon/resources/lib/simple_logging.py39
-rw-r--r--plugin.video.embycon/resources/lib/trakttokodi.py248
-rw-r--r--plugin.video.embycon/resources/lib/translation.py19
-rw-r--r--plugin.video.embycon/resources/lib/utils.py155
-rw-r--r--plugin.video.embycon/resources/lib/websocket.py930
-rw-r--r--plugin.video.embycon/resources/lib/websocket_client.py302
-rw-r--r--plugin.video.embycon/resources/lib/widgets.py155
-rw-r--r--plugin.video.embycon/resources/settings.xml18
-rw-r--r--plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.1.xml2
-rw-r--r--plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3-elec.xml2
-rw-r--r--plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3.xml2
-rw-r--r--plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.6.xml1080
-rw-r--r--plugin.video.embycon/service.py328
27 files changed, 4956 insertions, 1613 deletions
diff --git a/plugin.video.embycon/addon.xml b/plugin.video.embycon/addon.xml
index eb1e52f..908f69f 100644
--- a/plugin.video.embycon/addon.xml
+++ b/plugin.video.embycon/addon.xml
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.embycon"
name="EmbyCon"
- version="1.3.47"
+ version="1.4.43"
provider-name="Team B">
<requires>
- <import addon="xbmc.python" version="2.24.0"/>
+ <import addon="xbmc.python" version="2.25.0"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
- <provides>video</provides>
+ <provides>video audio</provides>
</extension>
<extension point="xbmc.service" library="service.py" start="login">
</extension>
@@ -15,10 +15,10 @@
<platform>all</platform>
<language>en</language>
<license>GNU GENERAL PUBLIC LICENSE. Version 2, June 1991</license>
- <forum>https://emby.media/community/index.php?/topic/46651-embycon/</forum>
- <website>https://emby.media/community/index.php?/topic/46651-embycon/</website>
+ <forum>https://emby.media/community/index.php?/forum/99-kodi/</forum>
+ <website>https://faush01.github.io/plugin.video.embycon/</website>
<source>https://github.com/faush01/plugin.video.embycon</source>
- <summary lang="en_GB">View and play your Emby media library.</summary>
- <description lang="en_GB">An addon to allow you to view and playback your Emby (www.emby.media) Movie and TV Show collection.</description>
+ <summary lang="en_GB">Browse and play your Emby server media library.</summary>
+ <description lang="en_GB">An addon to allow you to browse and playback your Emby (www.emby.media) Movie, TV Show and Music collections.</description>
</extension>
</addon>
diff --git a/plugin.video.embycon/resources/language/resource.language.en_gb/strings.po b/plugin.video.embycon/resources/language/resource.language.en_gb/strings.po
index 4647f75..8222ecb 100644
--- a/plugin.video.embycon/resources/language/resource.language.en_gb/strings.po
+++ b/plugin.video.embycon/resources/language/resource.language.en_gb/strings.po
@@ -47,7 +47,7 @@ msgid "Enable performance profiling"
msgstr ""
msgctxt "#30011"
-msgid "Detect Server"
+msgid "Detect Local Server"
msgstr ""
msgctxt "#30012"
@@ -82,10 +82,18 @@ msgctxt "#30020"
msgid "Flatten single season"
msgstr ""
+msgctxt "#30021"
+msgid "Show all episodes item"
+msgstr ""
+
msgctxt "#30022"
msgid "Advanced"
msgstr ""
+msgctxt "#30023"
+msgid "Hide unwatched episode details"
+msgstr ""
+
msgctxt "#30024"
msgid "Username:"
msgstr ""
@@ -159,19 +167,11 @@ msgid "On Resume Jump Back Seconds"
msgstr ""
msgctxt "#30116"
-msgid "Add Item and Played Counts"
-msgstr ""
-
-msgctxt "#30117"
-msgid "Background Art Refresh Rate (seconds)"
+msgid "Add unwatched counts to names"
msgstr ""
msgctxt "#30118"
-msgid "Add Resume Percent"
-msgstr ""
-
-msgctxt "#30119"
-msgid "Add Episode Number"
+msgid "Add resume percent to names"
msgstr ""
msgctxt "#30120"
@@ -210,10 +210,6 @@ msgctxt "#30137"
msgid "Please restart Kodi"
msgstr ""
-msgctxt "#30138"
-msgid "Cache Emby Data Locally"
-msgstr ""
-
msgctxt "#30139"
msgid "No Media Type Set"
msgstr ""
@@ -226,10 +222,6 @@ msgctxt "#30151"
msgid "Select item action (Requires Restart)"
msgstr ""
-msgctxt "#30162"
-msgid "Add Season Number"
-msgstr ""
-
msgctxt "#30163"
msgid "Add (cc) if subtitle is available"
msgstr ""
@@ -255,15 +247,15 @@ msgid "Select User"
msgstr ""
msgctxt "#30181"
-msgid "Include Overview"
+msgid "Include plot"
msgstr ""
msgctxt "#30182"
-msgid "Include Media Info"
+msgid "Include media stream info"
msgstr ""
msgctxt "#30183"
-msgid "Include People"
+msgid "Include people"
msgstr ""
msgctxt "#30200"
@@ -430,6 +422,10 @@ msgctxt "#30254"
msgid "Show Settings"
msgstr ""
+msgctxt "#30255"
+msgid "Movies (Year)"
+msgstr ""
+
msgctxt "#30256"
msgid "Movies - All"
msgstr ""
@@ -535,7 +531,7 @@ msgid "Your skin %s is currently not supported"
msgstr ""
msgctxt "#30282"
-msgid "No servers detected"
+msgid "No Emby servers on your local network detected"
msgstr ""
msgctxt "#30283"
@@ -626,3 +622,74 @@ msgctxt "#30304"
msgid "Loaded Textures: "
msgstr ""
+msgctxt "#30305"
+msgid "Not Found: %s"
+msgstr ""
+
+msgctxt "#30306"
+msgid "Playback starting: %s"
+msgstr ""
+
+msgctxt "#30307"
+msgid "Emby: Play Trailer"
+msgstr ""
+
+msgctxt "#30308"
+msgid "Select Trailer"
+msgstr ""
+
+msgctxt "#30309"
+msgid "Select Media Source"
+msgstr ""
+
+msgctxt "#30310"
+msgid "Enable Emby remote control"
+msgstr ""
+
+msgctxt "#30311"
+msgid "EmbyCon encountered an error!"
+msgstr ""
+
+msgctxt "#30312"
+msgid "Do you want to send this error to the devs?"
+msgstr ""
+
+msgctxt "#30313"
+msgid "Menu"
+msgstr ""
+
+msgctxt "#30314"
+msgid "Play"
+msgstr ""
+
+msgctxt "#30315"
+msgid "Suppress notifications for connection errors"
+msgstr ""
+
+msgctxt "#30316"
+msgid "Connection Error"
+msgstr ""
+
+msgctxt "#30317"
+msgid "Emby: Play All"
+msgstr ""
+
+msgctxt "#30318"
+msgid "Music - All Albums"
+msgstr ""
+
+msgctxt "#30319"
+msgid "Music - All Album Artists"
+msgstr ""
+
+msgctxt "#30320"
+msgid " - Albums"
+msgstr ""
+
+msgctxt "#30321"
+msgid " - Album Artists"
+msgstr ""
+
+msgctxt "#30322"
+msgid "Auto Resume"
+msgstr ""
diff --git a/plugin.video.embycon/resources/lib/action_menu.py b/plugin.video.embycon/resources/lib/action_menu.py
index 3521e44..96f099a 100644
--- a/plugin.video.embycon/resources/lib/action_menu.py
+++ b/plugin.video.embycon/resources/lib/action_menu.py
@@ -35,7 +35,7 @@ class ActionMenu(xbmcgui.WindowXMLDialog):
def onClick(self, controlID):
if (controlID == 3000):
self.selected_action = self.listControl.getSelectedItem()
- log.debug("ActionMenu: Selected Item:" + str(self.selected_action))
+ log.debug("ActionMenu: Selected Item: {0}", self.selected_action)
self.close()
def setActionItems(self, action_items):
diff --git a/plugin.video.embycon/resources/lib/clientinfo.py b/plugin.video.embycon/resources/lib/clientinfo.py
index 4d046a9..bf5ae99 100644
--- a/plugin.video.embycon/resources/lib/clientinfo.py
+++ b/plugin.video.embycon/resources/lib/clientinfo.py
@@ -9,10 +9,9 @@ from kodi_utils import HomeWindow
from simple_logging import SimpleLogging
log = SimpleLogging(__name__)
-__addon__ = xbmcaddon.Addon(id="plugin.video.embycon")
-
class ClientInformation():
+
def getDeviceId(self):
WINDOW = HomeWindow()
@@ -22,26 +21,27 @@ class ClientInformation():
return client_id
emby_guid_path = xbmc.translatePath("special://temp/embycon_guid").decode('utf-8')
- log.debug("emby_guid_path: " + emby_guid_path)
+ log.debug("emby_guid_path: {0}", emby_guid_path)
guid = xbmcvfs.File(emby_guid_path)
client_id = guid.read()
guid.close()
if not client_id:
client_id = str("%012X" % uuid4())
- log.debug("Generating a new guid: " + client_id)
+ log.debug("Generating a new guid: {0}", client_id)
guid = xbmcvfs.File(emby_guid_path, 'w')
guid.write(client_id)
guid.close()
- log.debug("emby_client_id (NEW): " + client_id)
+ log.debug("emby_client_id (NEW): {0}", client_id)
else:
- log.debug("emby_client_id: " + client_id)
+ log.debug("emby_client_id: {0}", client_id)
WINDOW.setProperty("client_id", client_id)
return client_id
def getVersion(self):
- version = __addon__.getAddonInfo("version")
+ addon = xbmcaddon.Addon(id="plugin.video.embycon")
+ version = addon.getAddonInfo("version")
return version
def getClient(self):
diff --git a/plugin.video.embycon/resources/lib/datamanager.py b/plugin.video.embycon/resources/lib/datamanager.py
index f5f69b5..feda9f0 100644
--- a/plugin.video.embycon/resources/lib/datamanager.py
+++ b/plugin.video.embycon/resources/lib/datamanager.py
@@ -1,22 +1,13 @@
# Gnu General Public License - see LICENSE.TXT
-import hashlib
-import os
-import threading
import json
-import encodings
-
-import xbmcaddon
-import xbmc
+from collections import defaultdict
from downloadutils import DownloadUtils
from simple_logging import SimpleLogging
-from utils import getChecksum
-from kodi_utils import HomeWindow
log = SimpleLogging(__name__)
-
class DataManager():
cacheDataResult = None
dataUrl = None
@@ -26,135 +17,12 @@ class DataManager():
def __init__(self, *args):
log.debug("DataManager __init__")
- def getCacheValidatorFromData(self, result):
- key = 'Items'
- results = result.get(key)
- if results is None:
- key = 'SearchHints'
- results = result.get(key)
- if results is None:
- results = []
-
- itemCount = 0
- dataHashString = ""
-
- for item in results:
- item_hash_string = getChecksum(item)
- item_hash_string = str(itemCount) + "_" + key + "_" + item.get("Name", "-") + "_" + item_hash_string + "|"
- log.debug("ITEM_HASH: " + item_hash_string)
- dataHashString += item_hash_string
- itemCount += 1
-
- # hash the data
- dataHashString = dataHashString.encode("UTF-8")
- m = hashlib.md5()
- m.update(dataHashString)
- validatorString = m.hexdigest()
-
- log.debug("getCacheValidatorFromData : RawData : " + dataHashString)
- log.debug("getCacheValidatorFromData : hashData : " + validatorString)
-
- return validatorString
-
def loadJasonData(self, jsonData):
- return json.loads(jsonData)
+ return json.loads(jsonData, object_hook=lambda d: defaultdict(lambda: None, d))
def GetContent(self, url):
+ jsonData = DownloadUtils().downloadUrl(url)
+ result = self.loadJasonData(jsonData)
+ return result
- __addon__ = xbmcaddon.Addon(id='plugin.video.embycon')
- use_cache_system = __addon__.getSetting('cacheEmbyData') == "true"
-
- if use_cache_system == False:
- # dont use cache system at all, just get the result and return
- log.debug("GetContent - Not using cache system")
- jsonData = DownloadUtils().downloadUrl(url, suppress=False, popup=1)
- result = self.loadJasonData(jsonData)
- log.debug("Returning Loaded Result")
- return result
-
- # first get the url hash
- m = hashlib.md5()
- m.update(url)
- urlHash = m.hexdigest()
-
- # build cache data path
-
- __addondir__ = xbmc.translatePath(__addon__.getAddonInfo('profile'))
- if not os.path.exists(os.path.join(__addondir__, "cache")):
- os.makedirs(os.path.join(__addondir__, "cache"))
- cacheDataPath = os.path.join(__addondir__, "cache", urlHash)
-
- log.debug("Cache_Data_Manager:" + cacheDataPath)
-
- # are we forcing a reload
- WINDOW = HomeWindow()
- force_data_reload = WINDOW.getProperty("force_data_reload") == "true"
- WINDOW.clearProperty("force_data_reload")
-
- if os.path.exists(cacheDataPath) and not force_data_reload:
- # load data from cache if it is available and trigger a background
- # verification process to test cache validity
- log.debug("Loading Cached File")
- with open(cacheDataPath, 'r') as f:
- result = self.loadJasonData(f.read())
-
- # start a worker thread to process the cache validity
- self.cacheDataResult = result
- self.dataUrl = url
- self.cacheDataPath = cacheDataPath
- actionThread = CacheManagerThread()
- actionThread.setCacheData(self)
- actionThread.start()
-
- log.debug("Returning Cached Result")
- return result
- else:
- # no cache data so load the url and save it
- jsonData = DownloadUtils().downloadUrl(url, suppress=False, popup=1)
- log.debug("Loading URL and saving to cache")
- with open(cacheDataPath, 'w') as f:
- f.write(jsonData)
- result = self.loadJasonData(jsonData)
- self.cacheManagerFinished = True
- log.debug("Returning Loaded Result")
- return result
-
-
-class CacheManagerThread(threading.Thread):
- dataManager = None
-
- def __init__(self, *args):
- threading.Thread.__init__(self, *args)
-
- def setCacheData(self, data):
- self.dataManager = data
-
- def run(self):
-
- log.debug("CacheManagerThread Started")
-
- cacheValidatorString = self.dataManager.getCacheValidatorFromData(self.dataManager.cacheDataResult)
- log.debug("Cache Validator String (" + cacheValidatorString + ")")
-
- jsonData = DownloadUtils().downloadUrl(self.dataManager.dataUrl, suppress=False, popup=1)
- loadedResult = self.dataManager.loadJasonData(jsonData)
- loadedValidatorString = self.dataManager.getCacheValidatorFromData(loadedResult)
- log.debug("Loaded Validator String (" + loadedValidatorString + ")")
-
- # if they dont match then save the data and trigger a content reload
- if (cacheValidatorString != loadedValidatorString):
- log.debug("CacheManagerThread Saving new cache data and reloading container")
- with open(self.dataManager.cacheDataPath, 'w') as f:
- f.write(jsonData)
-
- # we need to refresh but will wait until the main function has finished
- loops = 0
- while (self.dataManager.canRefreshNow == False and loops < 200 and not xbmc.Monitor().abortRequested()):
- log.debug("Cache_Data_Manager: Not finished yet")
- xbmc.sleep(100)
- loops = loops + 1
-
- log.debug("Sending container refresh (" + str(loops) + ")")
- xbmc.executebuiltin("Container.Refresh")
- log.debug("CacheManagerThread Exited")
diff --git a/plugin.video.embycon/resources/lib/downloadutils.py b/plugin.video.embycon/resources/lib/downloadutils.py
index 5bfce6e..6a52b62 100644
--- a/plugin.video.embycon/resources/lib/downloadutils.py
+++ b/plugin.video.embycon/resources/lib/downloadutils.py
@@ -10,6 +10,8 @@ import ssl
import StringIO
import gzip
import json
+from urlparse import urlparse
+import urllib
from kodi_utils import HomeWindow
from clientinfo import ClientInformation
@@ -18,6 +20,25 @@ from translation import i18n
log = SimpleLogging(__name__)
+def getDetailsString():
+
+ addonSettings = xbmcaddon.Addon(id='plugin.video.embycon')
+ include_media = addonSettings.getSetting("include_media") == "true"
+ include_people = addonSettings.getSetting("include_people") == "true"
+ include_overview = addonSettings.getSetting("include_overview") == "true"
+
+ detailsString = "DateCreated,EpisodeCount,SeasonCount,Path,Genres,Studios,Etag"
+
+ if include_media:
+ detailsString += ",MediaStreams"
+
+ if include_people:
+ detailsString += ",People"
+
+ if include_overview:
+ detailsString += ",Overview"
+
+ return detailsString
class DownloadUtils():
getString = None
@@ -33,6 +54,17 @@ class DownloadUtils():
if (len(host) == 0) or (host == "<none>") or (len(port) == 0):
return None
+ # if user entered a full path i.e. http://some_host:port
+ if host.lower().strip().startswith("http://") or host.lower().strip().startswith("https://"):
+ log.debug("Extracting host info from url: {0}", host)
+ url_bits = urlparse(host.strip())
+ if url_bits.hostname is not None and len(url_bits.hostname) > 0:
+ host = url_bits.hostname
+ settings.setSetting("ipaddress", host)
+ if url_bits.port is not None and url_bits.port > 0:
+ port = str(url_bits.port)
+ settings.setSetting("port", port)
+
server = host + ":" + port
use_https = settings.getSetting('use_https') == 'true'
if use_https:
@@ -42,61 +74,64 @@ class DownloadUtils():
return server
- def getArtwork(self, data, art_type, parent=False, index="0", width=10000, height=10000, server=None):
+ def getArtwork(self, data, art_type, parent=False, index=0, width=10000, height=10000, server=None):
- id = data.get("Id")
+ id = data["Id"]
+ item_type = data["Type"]
- if data.get("Type") in ["Episode", "Season"]:
+ if item_type in ["Episode", "Season"]:
if art_type != "Primary" or parent == True:
- id = data.get("SeriesId")
+ id = data["SeriesId"]
imageTag = ""
# "e3ab56fe27d389446754d0fb04910a34" # a place holder tag, needs to be in this format
- itemType = data.get("Type")
-
# for episodes always use the parent BG
- if (itemType == "Episode" and art_type == "Backdrop"):
- id = data.get("ParentBackdropItemId")
- bgItemTags = data.get("ParentBackdropImageTags")
- if (bgItemTags != None and len(bgItemTags) > 0):
+ if item_type == "Episode" and art_type == "Backdrop":
+ id = data["ParentBackdropItemId"]
+ bgItemTags = data["ParentBackdropImageTags"]
+ if bgItemTags is not None and len(bgItemTags) > 0:
imageTag = bgItemTags[0]
- elif (art_type == "Backdrop") and (parent == True):
- id = data.get("ParentBackdropItemId")
- bgItemTags = data.get("ParentBackdropImageTags")
- if (bgItemTags != None and len(bgItemTags) > 0):
+ elif art_type == "Backdrop" and parent is True:
+ id = data["ParentBackdropItemId"]
+ bgItemTags = data["ParentBackdropImageTags"]
+ if bgItemTags is not None and len(bgItemTags) > 0:
imageTag = bgItemTags[0]
- elif (art_type == "Backdrop"):
- BGTags = data.get("BackdropImageTags")
- if (BGTags != None and len(BGTags) > 0):
- bgIndex = int(index)
- imageTag = data.get("BackdropImageTags")[bgIndex]
- log.debug("Background Image Tag:" + imageTag)
- elif (parent == False):
- if (data.get("ImageTags") != None and data.get("ImageTags").get(art_type) != None):
- imageTag = data.get("ImageTags").get(art_type)
- log.debug("Image Tag:" + imageTag)
- elif (parent == True):
- if (itemType == "Episode" or itemType == "Season") and art_type == 'Primary':
+ elif art_type == "Backdrop":
+ BGTags = data["BackdropImageTags"]
+ if BGTags is not None and len(BGTags) > index:
+ imageTag = BGTags[index]
+ log.debug("Background Image Tag: {0}", imageTag)
+ elif parent is False:
+ image_tags = data["ImageTags"]
+ if image_tags is not None:
+ image_tag_type = image_tags[art_type]
+ if image_tag_type is not None:
+ imageTag = image_tag_type
+ log.debug("Image Tag: {0}", imageTag)
+ elif parent is True:
+ if (item_type == "Episode" or item_type == "Season") and art_type == 'Primary':
tagName = 'SeriesPrimaryImageTag'
idName = 'SeriesId'
else:
- tagName = 'Parent%sTag' % art_type
+ tagName = 'Parent%sImageTag' % art_type
idName = 'Parent%sItemId' % art_type
- if (data.get(idName) != None and data.get(tagName) != None):
- id = data.get(idName)
- imageTag = data.get(tagName)
- log.debug("Parent Image Tag:" + imageTag)
+ parent_image_id = data[idName]
+ parent_image_tag = data[tagName]
+ if parent_image_id is not None and parent_image_tag is not None:
+ id = parent_image_id
+ imageTag = parent_image_tag
+ log.debug("Parent Image Tag: {0}", imageTag)
if (imageTag == "" or imageTag == None) and (art_type != 'Banner'): # ParentTag not passed for Banner
- log.debug("No Image Tag for request:" + art_type + " item:" + itemType + " parent:" + str(parent))
+ log.debug("No Image Tag for request:{0} item:{1} parent:{2}", art_type, item_type, parent)
return ""
query = ""
artwork = "%s/emby/Items/%s/Images/%s/%s?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, id, art_type, index, width, height, imageTag, query)
- log.debug("getArtwork : " + artwork)
+ log.debug("getArtwork: {0}", artwork)
'''
# do not return non-existing images
@@ -119,13 +154,17 @@ class DownloadUtils():
artwork += '&MaxHeight=%s' % height
return artwork
+ def get_user_artwork(self, item_id, item_type):
+ # Load user information set by UserClient
+ return "%s/emby/Users/%s/Images/%s?Format=original" % (self.getServer(), item_id, item_type)
+
def getUserId(self):
WINDOW = HomeWindow()
userid = WINDOW.getProperty("userid")
if (userid != None and userid != ""):
- log.debug("EmbyCon DownloadUtils -> Returning saved UserID : " + userid)
+ log.debug("EmbyCon DownloadUtils -> Returning saved UserID: {0}", userid)
return userid
settings = xbmcaddon.Addon('plugin.video.embycon')
@@ -133,56 +172,63 @@ class DownloadUtils():
if not userName:
return ""
- log.debug("Looking for user name: " + userName)
+ log.debug("Looking for user name: {0}", userName)
jsonData = None
try:
jsonData = self.downloadUrl("{server}/emby/Users/Public?format=json", suppress=True, authenticate=False)
except Exception, msg:
- error = "Get User unable to connect: " + str(msg)
- log.error(error)
+ log.error("Get User unable to connect: {0}", msg)
return ""
- log.debug("GETUSER_JSONDATA_01:" + str(jsonData))
+ log.debug("GETUSER_JSONDATA_01: {0}", jsonData)
result = []
try:
result = json.loads(jsonData)
except Exception, e:
- log.debug("jsonload : " + str(e) + " (" + jsonData + ")")
+ log.debug("Could not load user data: {0}", e)
return ""
if result is None:
return ""
- log.debug("GETUSER_JSONDATA_02:" + str(result))
+ log.debug("GETUSER_JSONDATA_02: {0}", result)
userid = ""
+ userImage = ""
secure = False
for user in result:
- if (user.get("Name") == userName):
+ if (user.get("Name") == unicode(userName, "utf-8")):
userid = user.get("Id")
- log.debug("Username Found:" + user.get("Name"))
+ if "PrimaryImageTag" in user:
+ userImage = self.get_user_artwork(userid, 'Primary')
+ log.debug("Username Found: {0}", user.get("Name"))
if (user.get("HasPassword") == True):
secure = True
log.debug("Username Is Secure (HasPassword=True)")
break
- if (secure) or (not userid):
+ if secure or not userid:
authOk = self.authenticate()
- if (authOk == ""):
- xbmcgui.Dialog().ok(self.addon_name, i18n('incorrect_user_pass'))
+ if authOk == "":
+ xbmcgui.Dialog().notification(i18n("connection_error"),
+ i18n('incorrect_user_pass'),
+ icon="special://home/addons/plugin.video.embycon/icon.png")
return ""
if not userid:
userid = WINDOW.getProperty("userid")
if userid == "":
- xbmcgui.Dialog().ok(self.addon_name, i18n('username_not_found'))
+ xbmcgui.Dialog().notification(i18n("connection_error"),
+ i18n('username_not_found'),
+ icon="special://home/addons/plugin.video.embycon/icon.png")
- log.debug("userid : " + userid)
+ log.debug("userid: {0}", userid)
WINDOW.setProperty("userid", userid)
+ WINDOW.setProperty("userimage", userImage)
return userid
@@ -191,31 +237,27 @@ class DownloadUtils():
WINDOW = HomeWindow()
token = WINDOW.getProperty("AccessToken")
- if (token != None and token != ""):
- log.debug("EmbyCon DownloadUtils -> Returning saved AccessToken : " + token)
+ if token is not None and token != "":
+ log.debug("EmbyCon DownloadUtils -> Returning saved AccessToken: {0}", token)
return token
settings = xbmcaddon.Addon('plugin.video.embycon')
port = settings.getSetting("port")
host = settings.getSetting("ipaddress")
- if (host == None or host == "" or port == None or port == ""):
+ if host is None or host == "" or port is None or port == "":
return ""
url = "{server}/emby/Users/AuthenticateByName?format=json"
- clientInfo = ClientInformation()
- txt_mac = clientInfo.getDeviceId()
- version = clientInfo.getVersion()
- client = clientInfo.getClient()
-
- deviceName = settings.getSetting('deviceName')
- deviceName = deviceName.replace("\"", "_")
+ pwd_sha = hashlib.sha1(settings.getSetting('password')).hexdigest()
+ user_name = urllib.quote(settings.getSetting('username'))
+ pwd_text = urllib.quote(settings.getSetting('password'))
- authString = "Mediabrowser Client=\"" + client + "\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
- headers = {'Accept-encoding': 'gzip', 'Authorization': authString}
- sha1 = hashlib.sha1(settings.getSetting('password'))
+ messageData = "username=" + user_name + "&password=" + pwd_sha
- messageData = "username=" + settings.getSetting('username') + "&password=" + sha1.hexdigest()
+ use_https = settings.getSetting('use_https') == 'true'
+ if use_https:
+ messageData += "&pw=" + pwd_text
resp = self.downloadUrl(url, postBody=messageData, method="POST", suppress=True, authenticate=False)
@@ -228,8 +270,8 @@ class DownloadUtils():
except:
pass
- if (accessToken != None):
- log.debug("User Authenticated : " + accessToken)
+ if accessToken is not None:
+ log.debug("User Authenticated: {0}", accessToken)
WINDOW.setProperty("AccessToken", accessToken)
WINDOW.setProperty("userid", userid)
return accessToken
@@ -247,7 +289,12 @@ class DownloadUtils():
settings = xbmcaddon.Addon('plugin.video.embycon')
deviceName = settings.getSetting('deviceName')
+ # remove none ascii chars
+ deviceName = deviceName.decode("ascii", errors='ignore')
+ # remove some chars not valid for names
deviceName = deviceName.replace("\"", "_")
+ if len(deviceName) == 0:
+ deviceName = "EmbyCon"
headers = {}
headers["Accept-encoding"] = "gzip"
@@ -268,23 +315,34 @@ class DownloadUtils():
if (authToken != ""):
headers["X-MediaBrowser-Token"] = authToken
- log.debug("EmbyCon Authentication Header : " + str(headers))
+ log.debug("EmbyCon Authentication Header: {0}", headers)
return headers
- def downloadUrl(self, url, suppress=False, postBody=None, method="GET", popup=0, authenticate=True, headers=None):
+ def downloadUrl(self, url, suppress=False, postBody=None, method="GET", authenticate=True, headers=None):
log.debug("downloadUrl")
+
+ return_data = "null"
settings = xbmcaddon.Addon(id='plugin.video.embycon')
- log.debug(url)
+ if settings.getSetting("suppressErrors") == "true":
+ suppress = True
+
+ log.debug("Before: {0}", url)
+
if url.find("{server}") != -1:
server = self.getServer()
+ if server is None:
+ return return_data
url = url.replace("{server}", server)
+
if url.find("{userid}") != -1:
userid = self.getUserId()
url = url.replace("{userid}", userid)
+
if url.find("{ItemLimit}") != -1:
show_x_filtered_items = settings.getSetting("show_x_filtered_items")
url = url.replace("{ItemLimit}", show_x_filtered_items)
+
if url.find("{IsUnplayed}") != -1 or url.find("{,IsUnplayed}") != -1 or url.find("{IsUnplayed,}") != -1 \
or url.find("{,IsUnplayed,}") != -1:
show_latest_unplayed = settings.getSetting("show_latest_unplayed") == "true"
@@ -301,9 +359,13 @@ class DownloadUtils():
url = url.replace("{IsUnplayed,}", "IsUnplayed,")
elif url.find("{,IsUnplayed,}") != -1:
url = url.replace("{,IsUnplayed,}", ",IsUnplayed,")
- log.debug(url)
- return_data = "null"
+ if url.find("{field_filters}") != -1:
+ filter_string = getDetailsString()
+ url = url.replace("{field_filters}", filter_string)
+
+ log.debug("After: {0}", url)
+
try:
if url.startswith('http'):
serversplit = 2
@@ -315,9 +377,9 @@ class DownloadUtils():
server = url.split('/')[serversplit]
urlPath = "/" + "/".join(url.split('/')[urlsplit:])
- log.debug("DOWNLOAD_URL = " + url)
- log.debug("server = " + str(server))
- log.debug("urlPath = " + str(urlPath))
+ log.debug("DOWNLOAD_URL: {0}", url)
+ log.debug("server: {0}", server)
+ log.debug("urlPath: {0}", urlPath)
# check the server details
tokens = server.split(':')
@@ -326,7 +388,6 @@ class DownloadUtils():
if (host == "<none>" or host == "" or port == ""):
return ""
- settings = xbmcaddon.Addon('plugin.video.embycon')
use_https = settings.getSetting('use_https') == 'true'
verify_cert = settings.getSetting('verify_cert') == 'true'
@@ -341,7 +402,7 @@ class DownloadUtils():
conn = httplib.HTTPConnection(server, timeout=40)
head = self.getAuthHeader(authenticate)
- log.debug("HEADERS : " + str(head))
+ log.debug("HEADERS: {0}", head)
if (postBody != None):
if isinstance(postBody, dict):
@@ -351,20 +412,20 @@ class DownloadUtils():
content_type = "application/x-www-form-urlencoded"
head["Content-Type"] = content_type
- log.debug("Content-Type : " + content_type)
+ log.debug("Content-Type: {0}", content_type)
- log.debug("POST DATA : " + postBody)
+ log.debug("POST DATA: {0}", postBody)
conn.request(method=method, url=urlPath, body=postBody, headers=head)
else:
conn.request(method=method, url=urlPath, headers=head)
data = conn.getresponse()
- log.debug("GET URL HEADERS : " + str(data.getheaders()))
+ log.debug("GET URL HEADERS: {0}", data.getheaders())
if int(data.status) == 200:
retData = data.read()
contentType = data.getheader('content-encoding')
- log.debug("Data Len Before : " + str(len(retData)))
+ log.debug("Data Len Before: {0}", len(retData))
if (contentType == "gzip"):
retData = StringIO.StringIO(retData)
gzipper = gzip.GzipFile(fileobj=retData)
@@ -373,41 +434,29 @@ class DownloadUtils():
return_data = retData
if headers is not None and isinstance(headers, dict):
headers.update(data.getheaders())
- log.debug("Data Len After : " + str(len(return_data)))
+ log.debug("Data Len After: {0}", len(return_data))
log.debug("====== 200 returned =======")
- log.debug("Content-Type : " + str(contentType))
- log.debug(return_data)
+ log.debug("Content-Type: {0}", contentType)
+ log.debug("{0}", return_data)
log.debug("====== 200 finished ======")
- #elif (int(data.status) == 301) or (int(data.status) == 302):
- # try:
- # conn.close()
- # except:
- # pass
- # return data.getheader('Location')
-
elif int(data.status) >= 400:
- error = "HTTP response error: " + str(data.status) + " " + str(data.reason)
- log.error(error)
+ log.error("HTTP response error: {0} {1}", data.status, data.reason)
if suppress is False:
- if popup == 0:
- xbmcgui.Dialog().notification(self.addon_name, i18n('url_error_') % str(data.reason))
- else:
- xbmcgui.Dialog().ok(self.addon_name, i18n('url_error_') % str(data.reason))
- log.error(error)
+ xbmcgui.Dialog().notification(i18n("connection_error"),
+ i18n('url_error_') % str(data.reason),
+ icon="special://home/addons/plugin.video.embycon/icon.png")
except Exception, msg:
- error = "Unable to connect to " + str(server) + " : " + str(msg)
- log.error(error)
+ log.error("Unable to connect to {0} : {1}", server, msg)
if suppress is False:
- if popup == 0:
- xbmcgui.Dialog().notification(self.addon_name, i18n('url_error_') % str(msg))
- else:
- xbmcgui.Dialog().ok(self.addon_name, i18n('url_error_') % i18n('unable_connect_server'), str(msg))
- #raise
+ xbmcgui.Dialog().notification(i18n("connection_error"),
+ str(msg),
+ icon="special://home/addons/plugin.video.embycon/icon.png")
+
finally:
try:
- log.debug("Closing HTTP connection: " + str(conn))
+ log.debug("Closing HTTP connection: {0}", conn)
conn.close()
except:
pass
diff --git a/plugin.video.embycon/resources/lib/error.py b/plugin.video.embycon/resources/lib/error.py
new file mode 100644
index 0000000..903ad70
--- /dev/null
+++ b/plugin.video.embycon/resources/lib/error.py
@@ -0,0 +1,152 @@
+
+import traceback
+import sys
+import os
+import httplib
+import json
+
+import xbmcgui
+import xbmc
+
+from simple_logging import SimpleLogging
+from clientinfo import ClientInformation
+from translation import i18n
+
+log = SimpleLogging(__name__)
+
+def catch_except(errors=(Exception, ), default_value=False):
+ # Will wrap method with try/except and print parameters for easier debugging
+ def decorator(func):
+ def wrapper(*args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except errors as error:
+ if not (hasattr(error, 'quiet') and error.quiet):
+ return_value = xbmcgui.Dialog().yesno(i18n('error'), i18n('embycon_error'), i18n('embycon_error_submit'))
+ if return_value:
+ log.debug("Sending Error Data")
+ try:
+ submit_error_data()
+ except Exception as error:
+ log.debug("Sending Error Data Failed: {0}", error)
+ raise
+ return default_value
+ return wrapper
+ return decorator
+
+def submit_error_data():
+
+ data = {}
+
+ try:
+ error_type, error_short, error_stack, machine_state = format_exception()
+
+ data["error_stack"] = error_stack
+ data["error_type"] = error_type
+ data["error_short"] = error_short
+ data["machine_state"] = machine_state
+ data["sys.argv"] = sys.argv
+ data["kodi_version"] = xbmc.getInfoLabel("System.BuildVersion")
+
+ client_info = ClientInformation()
+ data["addon_version"] = client_info.getVersion()
+ data["device_id"] = client_info.getDeviceId()
+
+ except Exception as error:
+ exc_type, exc_obj, exc_tb = sys.exc_info()
+ stack_trace_data = traceback.format_tb(exc_tb)
+ data["report_error"] = str(error)
+ data["report_error_stack"] = str(stack_trace_data)
+
+ post_data = json.dumps(data)
+ log.debug("ERROR_DATA: {0}", post_data)
+
+ server = "allthedata.pythonanywhere.com"
+ url_path = "/submit"
+ conn = httplib.HTTPConnection(server, timeout=40)
+ head = {}
+ head["Content-Type"] = "application/json"
+ conn.request(method="POST", url=url_path, body=post_data, headers=head)
+ data = conn.getresponse()
+ log.debug("Submit Responce Code: {0}", data.status)
+
+
+def format_exception():
+
+ stack = traceback.extract_stack()
+ exc_type, exc_obj, exc_tb = sys.exc_info()
+
+ frames = []
+ tb = exc_tb
+ while tb:
+ frame = tb.tb_frame
+ frames.append(frame)
+ tb = tb.tb_next
+
+ machine_state = []
+ for frame in frames:
+ state = {}
+ state["filename"] = frame.f_code.co_filename
+ state["line"] = frame.f_lineno
+ state["function"] = frame.f_code.co_name
+ locals = {}
+ for key, value in frame.f_locals.items():
+ locals[key] = str(value)
+ state["locals"] = locals
+ machine_state.append(state)
+
+ stack_trace_data = traceback.format_tb(exc_tb)
+ tb = traceback.extract_tb(exc_tb)
+ full_tb = stack[:-1] + tb
+ # log.error(str(full_tb))
+
+ # get last stack frame
+ latestStackFrame = None
+ if (len(tb) > 0):
+ latestStackFrame = tb[-1]
+ # log.error(str(tb))
+
+ fileStackTrace = ""
+ try:
+ # get files from stack
+ stackFileList = []
+ for frame in full_tb:
+ # log.error(str(frame))
+ frameFile = (os.path.split(frame[0])[1])[:-3]
+ frameLine = frame[1]
+ if (len(stackFileList) == 0 or stackFileList[-1][0] != frameFile):
+ stackFileList.append([frameFile, [str(frameLine)]])
+ else:
+ file = stackFileList[-1][0]
+ lines = stackFileList[-1][1]
+ lines.append(str(frameLine))
+ stackFileList[-1] = [file, lines]
+ # log.error(str(stackFileList))
+
+ for item in stackFileList:
+ lines = ",".join(item[1])
+ fileStackTrace += item[0] + "," + lines + ":"
+ # log.error(str(fileStackTrace))
+ except Exception as e:
+ fileStackTrace = None
+ log.error("{0}", e)
+
+ errorType = "NA"
+ errorFile = "NA"
+
+ if latestStackFrame is not None:
+ if fileStackTrace is None:
+ fileStackTrace = os.path.split(latestStackFrame[0])[1] + ":" + str(latestStackFrame[1])
+
+ codeLine = "NA"
+ if (len(latestStackFrame) > 3 and latestStackFrame[3] != None):
+ codeLine = latestStackFrame[3].strip()
+
+ errorFile = "%s(%s)(%s)" % (fileStackTrace, exc_obj.message, codeLine)
+ errorFile = errorFile[0:499]
+ errorType = "%s" % (exc_type.__name__)
+ # log.error(errorType + " - " + errorFile)
+
+ del (exc_type, exc_obj, exc_tb)
+
+ return errorType, errorFile, stack_trace_data, machine_state
diff --git a/plugin.video.embycon/resources/lib/functions.py b/plugin.video.embycon/resources/lib/functions.py
index aa2953e..46dbf70 100644
--- a/plugin.video.embycon/resources/lib/functions.py
+++ b/plugin.video.embycon/resources/lib/functions.py
@@ -9,24 +9,29 @@ import pstats
import json
import StringIO
import encodings
+import binascii
import xbmcplugin
import xbmcgui
import xbmcaddon
import xbmc
+from resources.lib.error import catch_except
from downloadutils import DownloadUtils
-from utils import getDetailsString, getArt, cache_artwork
+from utils import getArt, cache_artwork, send_event_notification
from kodi_utils import HomeWindow
from clientinfo import ClientInformation
from datamanager import DataManager
from server_detect import checkServer
from simple_logging import SimpleLogging
-from menu_functions import displaySections, showMovieAlphaList, showGenreList, showWidgets, showSearch
+from menu_functions import displaySections, showMovieAlphaList, showGenreList, showWidgets, showSearch, showYearsList
from translation import i18n
from server_sessions import showServerSessions
from action_menu import ActionMenu
from widgets import getWidgetContent, getWidgetContentCast, getWidgetContentSimilar, getWidgetContentNextUp, getSuggestions, getWidgetUrlContent, checkForNewContent
+import trakttokodi
+from item_functions import add_gui_item, extract_item_info, ItemDetails, add_context_menu
+
__addon__ = xbmcaddon.Addon(id='plugin.video.embycon')
__addondir__ = xbmc.translatePath(__addon__.getAddonInfo('profile'))
@@ -41,6 +46,7 @@ downloadUtils = DownloadUtils()
dataManager = DataManager()
+@catch_except()
def mainEntryPoint():
log.debug("===== EmbyCon START =====")
@@ -54,11 +60,11 @@ def mainEntryPoint():
pr.enable()
ADDON_VERSION = ClientInformation().getVersion()
- log.debug("Running Python: " + str(sys.version_info))
- log.debug("Running EmbyCon: " + str(ADDON_VERSION))
- log.debug("Kodi BuildVersion: " + xbmc.getInfoLabel("System.BuildVersion"))
- log.debug("Kodi Version: " + str(kodi_version))
- log.debug("Script argument data: " + str(sys.argv))
+ log.debug("Running Python: {0}", sys.version_info)
+ log.debug("Running EmbyCon: {0}", ADDON_VERSION)
+ log.debug("Kodi BuildVersion: {0}", xbmc.getInfoLabel("System.BuildVersion"))
+ log.debug("Kodi Version: {0}", kodi_version)
+ log.debug("Script argument data: {0}", sys.argv)
try:
params = get_params(sys.argv[2])
@@ -69,7 +75,7 @@ def mainEntryPoint():
if (len(params) == 0):
windowParams = home_window.getProperty("Params")
- log.debug("windowParams : " + windowParams)
+ log.debug("windowParams: {0}", windowParams)
# home_window.clearProperty("Params")
if (windowParams):
try:
@@ -77,7 +83,7 @@ def mainEntryPoint():
except:
params = {}
- log.debug("Script params = " + str(params))
+ log.debug("Script params: {0}", params)
param_url = params.get('url', None)
@@ -109,12 +115,17 @@ def mainEntryPoint():
elif sys.argv[1] == "delete":
item_id = sys.argv[2]
delete(item_id)
+ elif mode == "playTrailer":
+ item_id = params["id"]
+ playTrailer(item_id)
elif mode == "MOVIE_ALPHA":
showMovieAlphaList()
elif mode == "MOVIE_GENRE":
showGenreList()
elif mode == "SERIES_GENRE":
showGenreList(item_type="series")
+ elif mode == "MOVIE_YEARS":
+ showYearsList()
elif mode == "WIDGETS":
showWidgets()
elif mode == "SHOW_MENU":
@@ -142,7 +153,7 @@ def mainEntryPoint():
getWidgetUrlContent(int(sys.argv[1]), params)
elif mode == "PARENT_CONTENT":
checkServer(notify=False)
- showParentContent(sys.argv[0], int(sys.argv[1]), params)
+ showParentContent(params)
elif mode == "SHOW_CONTENT":
# plugin://plugin.video.embycon?mode=SHOW_CONTENT&item_type=Movie|Series
checkServer(notify=False)
@@ -153,23 +164,18 @@ def mainEntryPoint():
xbmcplugin.setContent(int(sys.argv[1]), 'files')
showSearch()
elif mode == "NEW_SEARCH":
- # plugin://plugin.video.embycon?mode=NEW_SEARCH&item_type=<Movie|Series|Episode>
- if 'SEARCH_RESULTS' not in xbmc.getInfoLabel('Container.FolderPath'): # don't ask for input on '..'
- checkServer(notify=False)
- search(int(sys.argv[1]), params)
- else:
- return
- elif mode == "SEARCH_RESULTS":
- # plugin://plugin.video.embycon?mode=SEARCH_RESULTS&item_type=<Movie|Series>&query=<urllib.quote(search query)>&index=<[0-9]+>
checkServer(notify=False)
searchResults(params)
elif mode == "SHOW_SERVER_SESSIONS":
checkServer(notify=False)
showServerSessions()
+ elif mode == "TRAKTTOKODI":
+ checkServer(notify=False)
+ trakttokodi.entry_point(params)
else:
checkServer(notify=False)
- log.debug("EmbyCon -> Mode: " + str(mode))
- log.debug("EmbyCon -> URL: " + str(param_url))
+ log.debug("EmbyCon -> Mode: {0}", mode)
+ log.debug("EmbyCon -> URL: {0}", param_url)
if mode == "GET_CONTENT":
getContent(param_url, params)
@@ -199,7 +205,7 @@ def mainEntryPoint():
def markWatched(item_id):
- log.debug("Mark Item Watched : " + item_id)
+ log.debug("Mark Item Watched: {0}", item_id)
url = "{server}/emby/Users/{userid}/PlayedItems/" + item_id
downloadUtils.downloadUrl(url, postBody="", method="POST")
home_window = HomeWindow()
@@ -209,7 +215,7 @@ def markWatched(item_id):
def markUnwatched(item_id):
- log.debug("Mark Item UnWatched : " + item_id)
+ log.debug("Mark Item UnWatched: {0}", item_id)
url = "{server}/emby/Users/{userid}/PlayedItems/" + item_id
downloadUtils.downloadUrl(url, method="DELETE")
home_window = HomeWindow()
@@ -219,7 +225,7 @@ def markUnwatched(item_id):
def markFavorite(item_id):
- log.debug("Add item to favourites : " + item_id)
+ log.debug("Add item to favourites: {0}", item_id)
url = "{server}/emby/Users/{userid}/FavoriteItems/" + item_id
downloadUtils.downloadUrl(url, postBody="", method="POST")
home_window = HomeWindow()
@@ -229,7 +235,7 @@ def markFavorite(item_id):
def unmarkFavorite(item_id):
- log.debug("Remove item from favourites : " + item_id)
+ log.debug("Remove item from favourites: {0}", item_id)
url = "{server}/emby/Users/{userid}/FavoriteItems/" + item_id
downloadUtils.downloadUrl(url, method="DELETE")
home_window = HomeWindow()
@@ -241,7 +247,7 @@ def unmarkFavorite(item_id):
def delete(item_id):
return_value = xbmcgui.Dialog().yesno(i18n('confirm_file_delete'), i18n('file_delete_confirm'))
if return_value:
- log.debug('Deleting Item : ' + item_id)
+ log.debug('Deleting Item: {0}', item_id)
url = '{server}/emby/Items/' + item_id
progress = xbmcgui.DialogProgress()
progress.create(i18n('deleting'), i18n('waiting_server_delete'))
@@ -252,224 +258,8 @@ def delete(item_id):
xbmc.executebuiltin("Container.Refresh")
-def addGUIItem(url, details, extraData, display_options, folder=True):
-
- url = url.encode('utf-8')
-
- log.debug("Adding GuiItem for [%s]" % details.get('title', i18n('unknown')))
- log.debug("Passed details: " + str(details))
- log.debug("Passed extraData: " + str(extraData))
-
- if details.get('title', '') == '':
- return
-
- if extraData.get('mode', None) is None:
- mode = "&mode=0"
- else:
- mode = "&mode=%s" % extraData['mode']
-
- # Create the URL to pass to the item
- if folder:
- u = sys.argv[0] + "?url=" + urllib.quote(url) + mode + "&media_type=" + extraData["itemtype"]
- if extraData.get("name_format"):
- u += '&name_format=' + urllib.quote(extraData.get("name_format"))
- else:
- u = sys.argv[0] + "?item_id=" + url + "&mode=PLAY"
-
- # Create the ListItem that will be displayed
- thumbPath = str(extraData.get('thumb', ''))
-
- listItemName = details.get('title', i18n('unknown'))
-
- # calculate percentage
- cappedPercentage = 0
- if (extraData.get('resumetime') != None and int(extraData.get('resumetime')) > 0):
- duration = float(extraData.get('duration'))
- if (duration > 0):
- resume = float(extraData.get('resumetime'))
- percentage = int((resume / duration) * 100.0)
- cappedPercentage = percentage
-
- if (extraData.get('TotalEpisodes') != None and extraData.get('TotalEpisodes') != "0"):
- totalItems = int(extraData.get('TotalEpisodes'))
- watched = int(extraData.get('WatchedEpisodes'))
- percentage = int((float(watched) / float(totalItems)) * 100.0)
- cappedPercentage = percentage
-
- countsAdded = False
- addCounts = display_options.get("addCounts", True)
- if addCounts and extraData.get('UnWatchedEpisodes') != "0":
- countsAdded = True
- listItemName = listItemName + " (" + extraData.get('UnWatchedEpisodes') + ")"
-
- addResumePercent = display_options.get("addResumePercent", True)
- if (countsAdded == False
- and addResumePercent
- and details.get('title') != None
- and cappedPercentage not in [0, 100]):
- listItemName = listItemName + " (" + str(cappedPercentage) + "%)"
-
- subtitle_available = display_options.get("addSubtitleAvailable", False)
- if subtitle_available and extraData.get("SubtitleAvailable", False):
- listItemName += " (cc)"
-
- # update title with new name, this sets the new name in the deailts that are later passed to video info
- details['title'] = listItemName
-
- if kodi_version > 17:
- list_item = xbmcgui.ListItem(listItemName, offscreen=True)
- else:
- list_item = xbmcgui.ListItem(listItemName, iconImage=thumbPath, thumbnailImage=thumbPath)
-
- log.debug("Setting thumbnail as " + thumbPath)
-
- # calculate percentage
- if (cappedPercentage != 0):
- list_item.setProperty("complete_percentage", str(cappedPercentage))
-
- # For all end items
- if (not folder):
- # list_item.setProperty('IsPlayable', 'true')
- if extraData.get('type', 'video').lower() == "video":
- list_item.setProperty('TotalTime', str(extraData.get('duration')))
- list_item.setProperty('ResumeTime', str(int(extraData.get('resumetime'))))
-
- # StartPercent
-
- artTypes = ['thumb', 'poster', 'fanart', 'clearlogo', 'discart', 'banner', 'clearart',
- 'landscape', 'tvshow.poster', 'tvshow.clearart', 'tvshow.banner', 'tvshow.landscape']
- artLinks = {}
- for artType in artTypes:
- artLinks[artType] = extraData.get(artType, '')
- log.debug("Setting " + artType + " as " + artLinks[artType])
- list_item.setProperty('fanart_image', artLinks['fanart']) # back compat
- list_item.setProperty('discart', artLinks['discart']) # not avail to setArt
- list_item.setProperty('tvshow.poster', artLinks['tvshow.poster']) # not avail to setArt
- list_item.setArt(artLinks)
-
- menuItems = addContextMenu(details, extraData, folder)
- if (len(menuItems) > 0):
- list_item.addContextMenuItems(menuItems, True)
-
- # new way
- videoInfoLabels = {}
-
- # add cast
- people = extraData.get('cast')
- if people is not None:
- if kodi_version >= 17:
- list_item.setCast(people)
- else:
- videoInfoLabels['cast'] = videoInfoLabels['castandrole'] = [(cast_member['name'], cast_member['role']) for cast_member in people]
-
- if (extraData.get('type') == None or extraData.get('type') == "Video"):
- videoInfoLabels.update(details)
- else:
- list_item.setInfo(type=extraData.get('type', 'Video'), infoLabels=details)
-
- videoInfoLabels["duration"] = extraData.get("duration")
- videoInfoLabels["playcount"] = extraData.get("playcount")
- if (extraData.get('favorite') == 'true'):
- videoInfoLabels["top250"] = "1"
-
- videoInfoLabels["mpaa"] = extraData.get('mpaa')
- videoInfoLabels["rating"] = extraData.get('rating')
- videoInfoLabels["director"] = extraData.get('director')
- videoInfoLabels["writer"] = extraData.get('writer')
- videoInfoLabels["year"] = extraData.get('year')
- videoInfoLabels["premiered"] = extraData.get('premieredate')
- videoInfoLabels["dateadded"] = extraData.get('dateadded')
- videoInfoLabels["studio"] = extraData.get('studio')
- videoInfoLabels["genre"] = extraData.get('genre')
-
- item_type = extraData.get('itemtype').lower()
- mediatype = 'video'
-
- if (item_type == 'movie') or (item_type == 'boxset'):
- mediatype = 'movie'
- elif (item_type == 'series'):
- mediatype = 'tvshow'
- elif (item_type == 'season'):
- mediatype = 'season'
- elif (item_type == 'episode'):
- mediatype = 'episode'
-
- videoInfoLabels["mediatype"] = mediatype
-
- if mediatype == 'episode':
- videoInfoLabels["episode"] = details.get('episode')
-
- if (mediatype == 'season') or (mediatype == 'episode'):
- videoInfoLabels["season"] = details.get('season')
-
- list_item.setInfo('video', videoInfoLabels)
-
- list_item.addStreamInfo('video',
- {'duration': extraData.get('duration'), 'aspect': extraData.get('aspectratio'),
- 'codec': extraData.get('videocodec'), 'width': extraData.get('width'),
- 'height': extraData.get('height')})
- list_item.addStreamInfo('audio', {'codec': extraData.get('audiocodec'), 'channels': extraData.get('channels')})
- if extraData.get('SubtitleLang', '') != '':
- list_item.addStreamInfo('subtitle', {'language': extraData.get('SubtitleLang', '')})
-
- list_item.setProperty('CriticRating', str(extraData.get('criticrating')))
- list_item.setProperty('ItemType', extraData.get('itemtype'))
-
- if extraData.get('totaltime') != None:
- list_item.setProperty('TotalTime', extraData.get('totaltime'))
- if extraData.get('TotalSeasons') != None:
- list_item.setProperty('TotalSeasons', extraData.get('TotalSeasons'))
- if extraData.get('TotalEpisodes') != None:
- list_item.setProperty('TotalEpisodes', extraData.get('TotalEpisodes'))
- if extraData.get('WatchedEpisodes') != None:
- list_item.setProperty('WatchedEpisodes', extraData.get('WatchedEpisodes'))
- if extraData.get('UnWatchedEpisodes') != None:
- list_item.setProperty('UnWatchedEpisodes', extraData.get('UnWatchedEpisodes'))
- if extraData.get('NumEpisodes') != None:
- list_item.setProperty('NumEpisodes', extraData.get('NumEpisodes'))
-
- #list_item.setProperty('ItemGUID', extraData.get('guiid'))
- list_item.setProperty('id', extraData.get('id'))
-
- return (u, list_item, folder)
-
-
-def addContextMenu(details, extraData, folder):
- commands = []
-
- item_id = extraData.get('id')
- if item_id != None:
- scriptToRun = PLUGINPATH + "/default.py"
-
- if not folder:
- argsToPass = "?mode=PLAY&item_id=" + item_id + "&force_transcode=true"
- commands.append((i18n('emby_force_transcode'), "RunPlugin(plugin://plugin.video.embycon" + argsToPass + ")"))
-
- # watched/unwatched
- if extraData.get("playcount") == "0":
- argsToPass = 'markWatched,' + item_id
- commands.append((i18n('emby_mark_watched'), "RunScript(" + scriptToRun + ", " + argsToPass + ")"))
- elif extraData.get("playcount"):
- argsToPass = 'markUnwatched,' + item_id
- commands.append((i18n('emby_mark_unwatched'), "RunScript(" + scriptToRun + ", " + argsToPass + ")"))
-
- # favourite add/remove
- if extraData.get('favorite') == 'false':
- argsToPass = 'markFavorite,' + item_id
- commands.append((i18n('emby_set_favorite'), "RunScript(" + scriptToRun + ", " + argsToPass + ")"))
- elif extraData.get('favorite') == 'true':
- argsToPass = 'unmarkFavorite,' + item_id
- commands.append((i18n('emby_unset_favorite'), "RunScript(" + scriptToRun + ", " + argsToPass + ")"))
-
- # delete
- argsToPass = 'delete,' + item_id
- commands.append((i18n('emby_delete'), "RunScript(" + scriptToRun + ", " + argsToPass + ")"))
-
- return (commands)
-
-
def get_params(paramstring):
- log.debug("Parameter string: " + paramstring)
+ log.debug("Parameter string: {0}", paramstring)
param = {}
if len(paramstring) >= 2:
params = paramstring
@@ -491,15 +281,19 @@ def get_params(paramstring):
elif (len(splitparams)) == 3:
param[splitparams[0]] = splitparams[1] + "=" + splitparams[2]
- log.debug("EmbyCon -> Detected parameters: " + str(param))
+ log.debug("EmbyCon -> Detected parameters: {0}", param)
return param
def setSort(pluginhandle, viewType):
- log.debug("SETTING_SORT for media type: " + str(viewType))
+ log.debug("SETTING_SORT for media type: {0}", viewType)
if viewType == "BoxSets":
xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_VIDEO_YEAR)
xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE)
+ elif viewType == "Episodes":
+ xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_EPISODE)
+ elif viewType == "Music":
+ xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_TRACKNUM)
else:
xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE)
xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_VIDEO_YEAR)
@@ -518,8 +312,8 @@ def getContent(url, params):
if not media_type:
xbmcgui.Dialog().ok(i18n('error'), i18n('no_media_type'))
- log.debug("URL: " + str(url))
- log.debug("MediaType: " + str(media_type))
+ log.debug("URL: {0}", url)
+ log.debug("MediaType: {0}", media_type)
pluginhandle = int(sys.argv[1])
settings = xbmcaddon.Addon(id='plugin.video.embycon')
@@ -529,6 +323,21 @@ def getContent(url, params):
if media_type.startswith("movie"):
viewType = "Movies"
xbmcplugin.setContent(pluginhandle, 'movies')
+ elif media_type == "musicalbums":
+ viewType = "Albums"
+ xbmcplugin.setContent(pluginhandle, 'albums')
+ elif media_type == "musicartists":
+ viewType = "Artists"
+ xbmcplugin.setContent(pluginhandle, 'artists')
+ elif media_type == "musicartist":
+ viewType = "Albums"
+ xbmcplugin.setContent(pluginhandle, 'albums')
+ elif media_type == "music" or media_type == "audio" or media_type == "musicalbum":
+ viewType = "Music"
+ xbmcplugin.setContent(pluginhandle, 'songs')
+ elif media_type.startswith("boxsets"):
+ viewType = "Movies"
+ xbmcplugin.setContent(pluginhandle, 'sets')
elif media_type.startswith("boxset"):
viewType = "BoxSets"
xbmcplugin.setContent(pluginhandle, 'movies')
@@ -541,9 +350,7 @@ def getContent(url, params):
elif media_type == "season" or media_type == "episodes":
viewType = "Episodes"
xbmcplugin.setContent(pluginhandle, 'episodes')
- log.debug("ViewType: " + viewType)
-
- setSort(pluginhandle, viewType)
+ log.debug("ViewType: {0} media_type: {1}", viewType, media_type)
# show a progress indicator if needed
progress = None
@@ -555,26 +362,19 @@ def getContent(url, params):
# use the data manager to get the data
result = dataManager.GetContent(url)
- if result == None or len(result) == 0:
- if (progress != None):
- progress.close()
- return
-
dirItems = processDirectory(result, progress, params)
if dirItems is None:
return
- xbmcplugin.addDirectoryItems(pluginhandle, dirItems)
+ # set the sort items
+ setSort(pluginhandle, viewType)
+
+ xbmcplugin.addDirectoryItems(pluginhandle, dirItems)
xbmcplugin.endOfDirectory(pluginhandle, cacheToDisc=False)
- # if the view master addon is available then run the script
- try:
- view_addon = xbmcaddon.Addon("script.viewmaster")
- if view_addon is not None:
- xbmc.executebuiltin('RunScript(' + xbmc.translatePath(
- "special://home/addons/script.viewmaster/default.py") + ',' + viewType + ')')
- except:
- pass
+ # send display items event
+ display_items_notification = {"view_type": viewType}
+ send_event_notification("display_items", display_items_notification)
if (progress != None):
progress.update(100, i18n('done'))
@@ -588,9 +388,9 @@ def processDirectory(results, progress, params):
settings = xbmcaddon.Addon(id='plugin.video.embycon')
server = downloadUtils.getServer()
- detailsString = getDetailsString()
name_format = params.get("name_format", None)
+ name_format_type = None
if name_format is not None:
name_format = urllib.unquote(name_format)
tokens = name_format.split("|")
@@ -599,23 +399,22 @@ def processDirectory(results, progress, params):
dirItems = []
if results is None:
- result = []
+ results = []
+
if isinstance(results, dict):
- result = results.get("Items")
- else:
- result = results
+ results = results.get("Items", [])
# flatten single season
# if there is only one result and it is a season and you have flatten signle season turned on then
# build a new url, set the content media type and call get content again
flatten_single_season = settings.getSetting("flatten_single_season") == "true"
- if flatten_single_season and len(result) == 1 and result[0].get("Type", "") == "Season":
- season_id = result[0].get("Id")
+ if flatten_single_season and len(results) == 1 and results[0].get("Type", "") == "Season":
+ season_id = results[0].get("Id")
season_url = ('{server}/emby/Users/{userid}/items' +
'?ParentId=' + season_id +
'&IsVirtualUnAired=false' +
'&IsMissing=false' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&format=json')
if progress is not None:
progress.close()
@@ -623,324 +422,89 @@ def processDirectory(results, progress, params):
getContent(season_url, params)
return None
- add_season_number = settings.getSetting('addSeasonNumber') == 'true'
- add_episode_number = settings.getSetting('addEpisodeNumber') == 'true'
+ hide_unwatched_details = settings.getSetting('hide_unwatched_details') == 'true'
display_options = {}
display_options["addCounts"] = settings.getSetting("addCounts") == 'true'
display_options["addResumePercent"] = settings.getSetting("addResumePercent") == 'true'
display_options["addSubtitleAvailable"] = settings.getSetting("addSubtitleAvailable") == 'true'
- item_count = len(result)
+ item_count = len(results)
current_item = 1
first_season_item = None
total_unwatched = 0
total_episodes = 0
total_watched = 0
- for item in result:
+
+ gui_options = {}
+ gui_options["server"] = server
+
+ gui_options["name_format"] = name_format
+ gui_options["name_format_type"] = name_format_type
+
+ for item in results:
if (progress != None):
percentDone = (float(current_item) / float(item_count)) * 100
progress.update(int(percentDone), i18n('processing_item:') + str(current_item))
current_item = current_item + 1
- id = str(item.get("Id")).encode('utf-8')
- isFolder = item.get("IsFolder")
+ # get the infofrom the item
+ item_details = extract_item_info(item, gui_options)
- item_type = str(item.get("Type")).encode('utf-8')
-
- if item_type == "Season" and first_season_item is None:
+ if item_details.item_type == "Season" and first_season_item is None:
first_season_item = item
- # set the episode number
- tempEpisode = ""
- if item_type == "Episode":
- tempEpisode = item.get("IndexNumber")
- if tempEpisode is not None:
- if tempEpisode < 10:
- tempEpisode = "0" + str(tempEpisode)
- else:
- tempEpisode = str(tempEpisode)
- else:
- tempEpisode = ""
-
- # set the season number
- tempSeason = None
- if item_type == "Episode":
- tempSeason = item.get("ParentIndexNumber")
- elif item_type == "Season":
- tempSeason = item.get("IndexNumber")
- if tempSeason is not None:
- if tempSeason < 10:
- tempSeason = "0" + str(tempSeason)
- else:
- tempSeason = str(tempSeason)
- else:
- tempSeason = ""
-
- # set the item name
- # override with name format string from request
- if name_format is not None and item.get("Type", "") == name_format_type:
- nameInfo = {}
- nameInfo["ItemName"] = item.get("Name", "").encode('utf-8')
- nameInfo["SeriesName"] = item.get("SeriesName", "").encode('utf-8')
- nameInfo["SeasonIndex"] = tempSeason
- nameInfo["EpisodeIndex"] = tempEpisode
- log.debug("FormatName : %s | %s" % (name_format, nameInfo))
- tempTitle = name_format.format(**nameInfo).strip()
+ total_unwatched += item_details.unwatched_episodes
+ total_episodes += item_details.total_episodes
+ total_watched += item_details.watched_episodes
- else:
- if (item.get("Name") != None):
- tempTitle = item.get("Name").encode('utf-8')
- else:
- tempTitle = i18n('missing_title')
-
- if item.get("Type") == "Episode":
- prefix = ''
- if add_season_number:
- prefix = "S" + str(tempSeason)
- if add_episode_number:
- prefix = prefix + "E"
- if add_episode_number:
- prefix = prefix + str(tempEpisode)
- if prefix != '':
- tempTitle = prefix + ' - ' + tempTitle
-
- production_year = item.get("ProductionYear")
- if not production_year and item.get("PremiereDate"):
- production_year = int(item.get("PremiereDate")[:4])
-
- premiere_date = ""
- if item.get("PremiereDate") != None:
- tokens = (item.get("PremiereDate")).split("T")
- premiere_date = tokens[0]
-
- try:
- date_added = item['DateCreated']
- date_added = date_added.split('.')[0].replace('T', " ")
- except KeyError:
- date_added = ""
-
- # add the premiered date for Upcoming TV
- if item.get("LocationType") == "Virtual":
- airtime = item.get("AirTime")
- tempTitle = tempTitle + ' - ' + str(premiere_date) + ' - ' + str(airtime)
-
- # Process MediaStreams
- channels = ''
- videocodec = ''
- audiocodec = ''
- height = ''
- width = ''
- aspectfloat = 0.0
- subtitle_lang = ''
- subtitle_available = False
- mediaStreams = item.get("MediaStreams")
- if (mediaStreams != None):
- for mediaStream in mediaStreams:
- if mediaStream.get("Type") == "Video":
- videocodec = mediaStream.get("Codec")
- height = str(mediaStream.get("Height"))
- width = str(mediaStream.get("Width"))
- aspectratio = mediaStream.get("AspectRatio")
- if aspectratio is not None and len(aspectratio) >= 3:
- try:
- aspectwidth, aspectheight = aspectratio.split(':')
- aspectfloat = float(aspectwidth) / float(aspectheight)
- except:
- aspectfloat = 1.85
- if mediaStream.get("Type") == "Audio":
- audiocodec = mediaStream.get("Codec")
- channels = mediaStream.get("Channels")
- if mediaStream.get("Type") == "Subtitle":
- subtitle_available = True
- if mediaStream.get("Language") is not None:
- subtitle_lang = mediaStream.get("Language")
-
- # Process People
- director = ''
- writer = ''
- cast = None
- people = item.get("People")
- if (people != None):
- cast = []
- for person in people:
- if (person.get("Type") == "Director"):
- director = director + person.get("Name") + ' '
- if (person.get("Type") == "Writing"):
- writer = person.get("Name")
- if (person.get("Type") == "Writer"):
- writer = person.get("Name")
- if (person.get("Type") == "Actor"):
- person_name = person.get("Name")
- person_role = person.get("Role")
- if person_role == None:
- person_role = ''
- person_id = person.get("Id")
- person_tag = person.get("PrimaryImageTag")
- person_thumbnail = downloadUtils.imageUrl(person_id, "Primary", 0, 400, 400, person_tag, server=server)
- person = {"name": person_name, "role": person_role, "thumbnail": person_thumbnail}
- cast.append(person)
-
- # Process Studios
- studio = ""
- studios = item.get("Studios")
- if (studios != None):
- for studio_string in studios:
- if studio == "": # Just take the first one
- temp = studio_string.get("Name")
- studio = temp.encode('utf-8')
-
- # Process Genres
- genre = ""
- genres = item.get("Genres")
- if (genres != None and genres != []):
- for genre_string in genres:
- if genre == "": # Just take the first genre
- genre = genre_string
- elif genre_string != None:
- genre = genre + " / " + genre_string
-
- # Process UserData
- userData = item.get("UserData")
- overlay = "0"
- favorite = "false"
- seekTime = 0
- if (userData != None):
- if userData.get("Played") != True:
- overlay = "7"
- watched = "true"
- else:
- overlay = "6"
- watched = "false"
+ # if set, for unwatched episodes dont show some of the info
+ if hide_unwatched_details and item_details.item_type == "Episode" and item_details.play_count == 0:
+ item_details.plot = "[Spoiler Alert]"
+ item_details.art["poster"] = item_details.art["tvshow.poster"]
+ item_details.art["thumb"] = item_details.art["tvshow.poster"]
- if userData.get("IsFavorite") == True:
- overlay = "5"
- favorite = "true"
- else:
- favorite = "false"
-
- if userData.get("PlaybackPositionTicks") != None:
- reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000
- seekTime = reasonableTicks / 10000
-
- playCount = 0
- if (userData != None and userData.get("Played") == True):
- playCount = 1
- # Populate the details list
- details = {'title': tempTitle,
- 'plot': item.get("Overview"),
- 'Overlay': overlay,
- 'playcount': str(playCount),
- # 'aired' : episode.get('originallyAvailableAt','') ,
- 'TVShowTitle': item.get("SeriesName"),
- }
-
- if item_type == "Episode":
- details['episode'] = tempEpisode
- if item_type == "Episode" or item_type == "Season":
- details['season'] = tempSeason
-
- try:
- tempDuration = str(int(item.get("RunTimeTicks", "0")) / (10000000))
- except TypeError:
- try:
- tempDuration = str(int(item.get("CumulativeRunTimeTicks")) / (10000000))
- except TypeError:
- tempDuration = "0"
-
- TotalSeasons = 0 if item.get("ChildCount") == None else item.get("ChildCount")
- TotalEpisodes = 0 if item.get("RecursiveItemCount") == None else item.get("RecursiveItemCount")
- WatchedEpisodes = 0 if userData.get("UnplayedItemCount") == None else TotalEpisodes - userData.get("UnplayedItemCount")
- UnWatchedEpisodes = 0 if userData.get("UnplayedItemCount") == None else userData.get("UnplayedItemCount")
- NumEpisodes = TotalEpisodes
- total_unwatched += UnWatchedEpisodes
- total_episodes += TotalEpisodes
- total_watched += WatchedEpisodes
-
- art = getArt(item, server)
- # Populate the extraData list
- extraData = {'thumb': art['thumb'],
- 'fanart': art['fanart'],
- 'poster': art['poster'],
- 'banner': art['banner'],
- 'clearlogo': art['clearlogo'],
- 'discart': art['discart'],
- 'clearart': art['clearart'],
- 'landscape': art['landscape'],
- 'tvshow.poster': art['tvshow.poster'],
- 'tvshow.clearart': art['tvshow.clearart'],
- 'tvshow.banner': art['tvshow.banner'],
- 'tvshow.landscape': art['tvshow.landscape'],
- 'id': id,
- 'mpaa': item.get("OfficialRating"),
- 'rating': item.get("CommunityRating"),
- 'criticrating': item.get("CriticRating"),
- 'year': production_year,
- 'premieredate': premiere_date,
- 'dateadded': date_added,
- 'locationtype': item.get("LocationType"),
- 'studio': studio,
- 'genre': genre,
- 'playcount': str(playCount),
- 'director': director,
- 'writer': writer,
- 'channels': channels,
- 'videocodec': videocodec,
- 'aspectratio': str(aspectfloat),
- 'audiocodec': audiocodec,
- 'height': height,
- 'width': width,
- 'cast': cast,
- 'favorite': favorite,
- 'resumetime': str(seekTime),
- 'totaltime': tempDuration,
- 'duration': tempDuration,
- 'RecursiveItemCount': item.get("RecursiveItemCount"),
- 'RecursiveUnplayedItemCount': userData.get("UnplayedItemCount"),
- 'TotalSeasons': str(TotalSeasons),
- 'TotalEpisodes': str(TotalEpisodes),
- 'WatchedEpisodes': str(WatchedEpisodes),
- 'UnWatchedEpisodes': str(UnWatchedEpisodes),
- 'NumEpisodes': str(NumEpisodes),
- 'OriginalTitle': item.get("Name").encode('utf-8'),
- 'itemtype': item_type,
- 'SubtitleLang': subtitle_lang,
- 'SubtitleAvailable': subtitle_available}
-
- extraData["Path"] = item.get("Path")
-
- extraData['mode'] = "GET_CONTENT"
-
- if isFolder == True:
-
- if item.get("Type", "") == "Series":
- u = ('{server}/emby/Shows/' + id +
+ if item["IsFolder"] is True:
+ if item_details.item_type == "Series":
+ u = ('{server}/emby/Shows/' + item_details.id +
'/Seasons'
'?userId={userid}' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&format=json')
+
else:
u = ('{server}/emby/Users/{userid}/items' +
- '?ParentId=' + id +
+ '?ParentId=' + item_details.id +
'&IsVirtualUnAired=false' +
- '&IsMissing=false&' +
- 'Fields=' + detailsString +
+ '&IsMissing=false' +
+ '&Fields={field_filters}' +
'&format=json')
- if item.get("RecursiveItemCount") != 0:
- dirItems.append(addGUIItem(u, details, extraData, display_options))
+ if item["RecursiveItemCount"] != 0:
+ dirItems.append(add_gui_item(u, item_details, display_options))
+
+ elif item_details.item_type == "MusicArtist":
+ u = ('{server}/emby/Users/{userid}/items' +
+ '?ArtistIds=' + item_details.id +
+ '&IncludeItemTypes=MusicAlbum' +
+ '&CollapseBoxSetItems=false' +
+ '&Recursive=true' +
+ '&format=json')
+ dirItems.append(add_gui_item(u, item_details, display_options))
+
else:
- u = id
- dirItems.append(addGUIItem(u, details, extraData, display_options, folder=False))
+ u = item_details.id
+ dirItems.append(add_gui_item(u, item_details, display_options, folder=False))
# add the all episodes item
- if first_season_item is not None:
+ show_all_episodes = settings.getSetting('show_all_episodes') == 'true'
+ if show_all_episodes and first_season_item is not None and len(dirItems) > 1:
series_url = ('{server}/emby/Users/{userid}/items' +
- '?ParentId=' + str(first_season_item.get("SeriesId")).encode('utf-8') +
+ '?ParentId=' + first_season_item.get("SeriesId") +
'&IsVirtualUnAired=false' +
'&IsMissing=false' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&Recursive=true' +
'&IncludeItemTypes=Episode' +
'&format=json')
@@ -949,55 +513,69 @@ def processDirectory(results, progress, params):
if total_unwatched == 0:
played = 1
overlay = "6"
- details = {'title': i18n('all'),
- 'Overlay': overlay,
- 'playcount': str(played)
- }
- art = getArt(first_season_item, server)
- # Populate the extraData list
- extraData = {'thumb': art['tvshow.poster'],
- 'fanart': art['fanart'],
- 'poster': art['tvshow.poster'],
- 'banner': art['tvshow.banner'],
- 'clearlogo': art['clearlogo'],
- 'discart': art['discart'],
- 'clearart': art['clearart'],
- 'landscape': art['landscape'],
- 'tvshow.poster': art['tvshow.poster'],
- 'tvshow.clearart': art['tvshow.clearart'],
- 'tvshow.banner': art['tvshow.banner'],
- 'tvshow.landscape': art['tvshow.landscape'],
- 'itemtype': 'Episodes',
- 'UnWatchedEpisodes': str(total_unwatched),
- 'TotalEpisodes': str(total_episodes),
- 'WatchedEpisodes': str(total_watched),
- 'playcount': str(played),
- 'mode': 'GET_CONTENT',
- 'name_format': 'Episode|episode_name_format'}
- dirItems.append(addGUIItem(series_url, details, extraData, {}, folder=True))
+
+ item_details = ItemDetails()
+
+ item_details.name = i18n('all')
+ item_details.art = getArt(first_season_item, server)
+ item_details.play_count = played
+ item_details.overlay = overlay
+ item_details.name_format = "Episode|episode_name_format"
+ item_details.series_name = first_season_item.get("SeriesName")
+ item_details.item_type = "Season"
+ item_details.unwatched_episodes = total_unwatched
+ item_details.total_episodes = total_episodes
+ item_details.watched_episodes = total_watched
+ item_details.mode = "GET_CONTENT"
+
+ dirItems.append(add_gui_item(series_url, item_details, display_options, folder=True))
return dirItems
def showMenu(params):
- log.debug("showMenu(): " + str(params))
+ log.debug("showMenu(): {0}", params)
+
+ id = params["item_id"]
+
+ url = "{server}/emby/Users/{userid}/Items/" + id + "?format=json"
+ data_manager = DataManager()
+ result = data_manager.GetContent(url)
+ log.debug("Playfile item info: {0}", result)
+
+ if result is None:
+ return
action_items = []
li = xbmcgui.ListItem("Play")
li.setProperty('menu_id', 'play')
action_items.append(li)
+
+ if result["Type"] == "Episode" and result["ParentId"] is not None:
+ li = xbmcgui.ListItem("Show Season")
+ li.setProperty('menu_id', 'show_season')
+ action_items.append(li)
+
li = xbmcgui.ListItem("Force Transcode")
li.setProperty('menu_id', 'transcode')
action_items.append(li)
- li = xbmcgui.ListItem("Mark Watched")
- li.setProperty('menu_id', 'mark_watched')
- action_items.append(li)
- li = xbmcgui.ListItem("Mark Unwatched")
- li.setProperty('menu_id', 'mark_unwatched')
- action_items.append(li)
+
+ user_data = result["UserData"]
+ if user_data.get("Played", False) is False or user_data.get("PlaybackPositionTicks", 0) > 0:
+ li = xbmcgui.ListItem("Mark Watched")
+ li.setProperty('menu_id', 'mark_watched')
+ action_items.append(li)
+
+ if user_data.get("Played", False) is True or user_data.get("PlaybackPositionTicks", 0) > 0:
+ li = xbmcgui.ListItem("Mark Unwatched")
+ li.setProperty('menu_id', 'mark_unwatched')
+ action_items.append(li)
+
li = xbmcgui.ListItem("Delete")
li.setProperty('menu_id', 'delete')
action_items.append(li)
+ #xbmcplugin.endOfDirectory(int(sys.argv[1]), cacheToDisc=False)
+
action_menu = ActionMenu("ActionMenu.xml", PLUGINPATH, "default", "720p")
action_menu.setActionItems(action_items)
action_menu.doModal()
@@ -1005,11 +583,14 @@ def showMenu(params):
selected_action = ""
if selected_action_item is not None:
selected_action = selected_action_item.getProperty('menu_id')
- log.debug("Menu Action Selected: " + str(selected_action_item))
+ log.debug("Menu Action Selected: {0}", selected_action_item)
del action_menu
if selected_action == "play":
log.debug("Play Item")
+ #list_item = populate_listitem(params["item_id"])
+ #result = xbmcgui.Dialog().info(list_item)
+ #log.debug("xbmcgui.Dialog().info: {0}", result)
PLAY(params)
elif selected_action == "transcode":
params['force_transcode'] = 'true'
@@ -1023,10 +604,66 @@ def showMenu(params):
elif selected_action == "delete":
delete(params["item_id"])
xbmc.executebuiltin("XBMC.ReloadSkin()")
+ elif selected_action == "show_season":
+ parent_id = result["ParentId"]
+ xbmc.executebuiltin(
+ 'ActivateWindow(Videos, plugin://plugin.video.embycon/?mode=PARENT_CONTENT&ParentId={0}&media_type=episodes, return)'.format(parent_id))
+
+
+def populate_listitem(item_id):
+ log.debug("populate_listitem: {0}", item_id)
+
+ url = "{server}/emby/Users/{userid}/Items/" + item_id + "?format=json"
+ jsonData = downloadUtils.downloadUrl(url)
+ result = json.loads(jsonData)
+ log.debug("populate_listitem item info: {0}", result)
+
+ '''
+ server = downloadUtils.getServer()
+ gui_options = {}
+ gui_options["server"] = server
+
+ gui_options["name_format"] = None
+ gui_options["name_format_type"] = None
+
+ details, extraData = extract_item_info(result,gui_options )
+ u, list_item, folder = add_gui_item(result["Id"], details, extraData, {}, folder=False)
+
+ log.debug("list_item path: {0}", u)
+
+ #list_item.setProperty('IsPlayable', 'false')
+ #list_item.setPath(u)
+ '''
+
+ item_title = result.get("Name", i18n('missing_title'))
+
+ list_item = xbmcgui.ListItem(label=item_title)
+
+ server = downloadUtils.getServer()
+
+ art = getArt(result, server=server)
+ list_item.setIconImage(art['thumb']) # back compat
+ list_item.setProperty('fanart_image', art['fanart']) # back compat
+ list_item.setProperty('discart', art['discart']) # not avail to setArt
+ list_item.setArt(art)
+
+ list_item.setProperty('IsPlayable', 'false')
+ list_item.setProperty('IsFolder', 'false')
+ list_item.setProperty('id', result.get("Id"))
+
+ # play info
+ details = {
+ 'title': item_title,
+ 'plot': result.get("Overview")
+ }
+
+ list_item.setInfo("Video", infoLabels=details)
+
+ return list_item
def showContent(pluginName, handle, params):
- log.debug("showContent Called: " + str(params))
+ log.debug("showContent Called: {0}", params)
item_type = params.get("item_type")
@@ -1034,40 +671,37 @@ def showContent(pluginName, handle, params):
"?format=json"
"&ImageTypeLimit=1"
"&IsMissing=False"
- "&Fields=" + getDetailsString() +
+ "&Fields={field_filters}" +
"&Recursive=true"
"&IsVirtualUnaired=false"
"&IsMissing=False"
"&IncludeItemTypes=" + item_type)
- log.debug("showContent Content Url : " + str(contentUrl))
+ log.debug("showContent Content Url: {0}", contentUrl)
getContent(contentUrl, params)
-def showParentContent(pluginName, handle, params):
- log.debug("showParentContent Called: " + str(params))
- settings = xbmcaddon.Addon(id='plugin.video.embycon')
+def showParentContent(params):
+ log.debug("showParentContent Called: {0}", params)
parentId = params.get("ParentId")
- detailsString = getDetailsString()
contentUrl = (
"{server}/emby/Users/{userid}/items?ParentId=" + parentId +
"&IsVirtualUnaired=false" +
"&IsMissing=False" +
"&ImageTypeLimit=1" +
- "&Fields=" + detailsString +
+ "&Fields={field_filters}" +
"&format=json")
- log.debug("showParentContent Content Url : " + str(contentUrl))
+ log.debug("showParentContent Content Url: {0}", contentUrl)
getContent(contentUrl, params)
-def search(handle, params):
- log.debug('search Called: ' + str(params))
+
+def searchResults(params):
+
item_type = params.get('item_type')
- if not item_type:
- return
- kb = xbmc.Keyboard()
+
if item_type.lower() == 'movie':
heading_type = i18n('movies')
elif item_type.lower() == 'series':
@@ -1076,26 +710,26 @@ def search(handle, params):
heading_type = i18n('episodes')
else:
heading_type = item_type
+
+ home_window = HomeWindow()
+
+ last_search = home_window.getProperty("last_search")
+ kb = xbmc.Keyboard()
kb.setHeading(heading_type.capitalize() + ' ' + i18n('search').lower())
+ kb.setDefault(last_search)
kb.doModal()
+
if kb.isConfirmed():
user_input = kb.getText().strip()
- if user_input:
- xbmcplugin.endOfDirectory(handle, cacheToDisc=False)
- user_input = urllib.quote(user_input)
- xbmc.executebuiltin('Container.Update(plugin://plugin.video.embycon/?mode=SEARCH_RESULTS&query={user_input}&item_type={item_type}&index=0)'
- .format(user_input=user_input, item_type=item_type)) # redirect for results to avoid page refreshing issues
- else:
- return
else:
return
+ home_window.setProperty("last_search", user_input)
-def searchResults(params):
- log.debug('searchResults Called: ' + str(params))
+ log.debug('searchResults Called: {0}', params)
handle = int(sys.argv[1])
- query = params.get('query')
+ query = user_input
item_type = params.get('item_type')
if (not item_type) or (not query):
return
@@ -1105,15 +739,18 @@ def searchResults(params):
settings = xbmcaddon.Addon(id='plugin.video.embycon')
server = downloadUtils.getServer()
- userid = downloadUtils.getUserId()
- details_string = getDetailsString()
content_url = ('{server}/emby/Search/Hints?searchTerm=' + query +
- '&IncludeItemTypes=' + item_type +
- '&UserId={userid}'
- '&StartIndex=' + str(index) +
- '&Limit=' + str(limit) +
- '&IncludePeople=false&IncludeMedia=true&IncludeGenres=false&IncludeStudios=false&IncludeArtists=false')
+ '&IncludeItemTypes=' + item_type +
+ '&ExcludeItemTypes=LiveTvProgram' +
+ '&UserId={userid}'
+ '&StartIndex=' + str(index) +
+ '&Limit=' + str(limit) +
+ '&IncludePeople=false' +
+ '&IncludeMedia=true' +
+ '&IncludeGenres=false' +
+ '&IncludeStudios=false' +
+ '&IncludeArtists=false')
if item_type.lower() == 'movie':
xbmcplugin.setContent(handle, 'movies')
@@ -1142,7 +779,10 @@ def searchResults(params):
progress.update(0, i18n('retrieving_data'))
result = dataManager.GetContent(content_url)
- log.debug('SearchHints jsonData: ' + str(result))
+ log.debug('SearchHints jsonData: {0}', result)
+
+ if result is None:
+ result = {}
results = result.get('SearchHints')
if results is None:
@@ -1150,13 +790,13 @@ def searchResults(params):
item_count = 1
total_results = int(result.get('TotalRecordCount', 0))
- log.debug('SEARCH_TOTAL_RESULTS: ' + str(total_results))
+ log.debug('SEARCH_TOTAL_RESULTS: {0}', total_results)
list_items = []
for item in results:
item_id = item.get('ItemId')
name = title = item.get('Name')
- log.debug('SEARCH_RESULT_NAME: ' + name)
+ log.debug('SEARCH_RESULT_NAME: {0}', name)
if progress is not None:
percent_complete = (float(item_count) / float(total_results)) * 100
@@ -1215,7 +855,7 @@ def searchResults(params):
list_item = xbmcgui.ListItem(label=name, iconImage=art['thumb'])
info = {'title': title, 'tvshowtitle': tvshowtitle, 'mediatype': media_type}
- log.debug('SEARCH_RESULT_ART: ' + str(art))
+ log.debug('SEARCH_RESULT_ART: {0}', art)
list_item.setProperty('fanart_image', art['fanart'])
list_item.setProperty('discart', art['discart'])
list_item.setArt(art)
@@ -1227,21 +867,23 @@ def searchResults(params):
if item.get('MediaType') == 'Video':
total_time = str(int(float(item.get('RunTimeTicks', '0')) / (10000000 * 60)))
list_item.setProperty('TotalTime', str(total_time))
- list_item.setProperty('IsPlayable', 'true')
+ list_item.setProperty('IsPlayable', 'false')
list_item_url = 'plugin://plugin.video.embycon/?item_id=' + item_id + '&mode=PLAY'
is_folder = False
else:
item_url = ('{server}/emby/Users/{userid}' +
'/items?ParentId=' + item_id +
'&IsVirtualUnAired=false&IsMissing=false' +
- '&Fields=' + details_string +
+ '&Fields={field_filters}' +
'&format=json')
list_item_url = 'plugin://plugin.video.embycon/?mode=GET_CONTENT&media_type={item_type}&url={item_url}'\
.format(item_type=item_type, item_url=urllib.quote(item_url))
list_item.setProperty('IsPlayable', 'false')
is_folder = True
- menu_items = addContextMenu({}, {'id': item_id}, is_folder)
+ item_details = ItemDetails()
+ item_details.id = item_id
+ menu_items = add_context_menu(item_details, is_folder)
if len(menu_items) > 0:
list_item.addContextMenuItems(menu_items, True)
@@ -1251,7 +893,7 @@ def searchResults(params):
info['year'] = item.get('ProductionYear', '')
- log.debug('SEARCH_RESULT_INFO: ' + str(info))
+ log.debug('SEARCH_RESULT_INFO: {0}', info)
list_item.setInfo('Video', infoLabels=info)
item_tuple = (list_item_url, list_item, is_folder)
@@ -1268,14 +910,20 @@ def searchResults(params):
def PLAY(params):
log.debug("== ENTER: PLAY ==")
- log.debug("PLAY ACTION PARAMS: " + str(params))
+ log.debug("PLAY ACTION PARAMS: {0}", params)
item_id = params.get("item_id")
auto_resume = int(params.get("auto_resume", "-1"))
- log.debug("AUTO_RESUME: " + str(auto_resume))
+ log.debug("AUTO_RESUME: {0}", auto_resume)
forceTranscode = params.get("force_transcode", None) is not None
- log.debug("FORCE_TRANSCODE: " + str(forceTranscode))
+ log.debug("FORCE_TRANSCODE: {0}", forceTranscode)
+
+ media_source_id = params.get("media_source_id", "")
+ log.debug("media_source_id: {0}", media_source_id)
+
+ use_default = params.get("use_default", "false") == "true"
+ log.debug("use_default: {0}", use_default)
# set the current playing item id
# set all the playback info, this will be picked up by the service
@@ -1284,13 +932,69 @@ def PLAY(params):
xbmc.Player().stop()
play_info = {}
- play_info["item_id"] = item_id
+ play_info["item_id"] = item_id
play_info["auto_resume"] = str(auto_resume)
play_info["force_transcode"] = forceTranscode
- play_data = json.dumps(play_info)
+ play_info["media_source_id"] = media_source_id
+ play_info["use_default"] = use_default
+ send_event_notification("embycon_play_action", play_info)
+
+
+def playTrailer(id):
+ log.debug("== ENTER: playTrailer ==")
+
+ url = ("{server}/emby/Users/{userid}/Items/%s/LocalTrailers?format=json" % id)
+
+ jsonData = downloadUtils.downloadUrl(url)
+ result = json.loads(jsonData)
+ log.debug("LocalTrailers {0}", result)
+
+ trailer_list = []
+ for trailer in result:
+ info = {}
+ info["type"] = "local"
+ info["name"] = trailer.get("Name", "na")
+ info["id"] = trailer.get("Id")
+ trailer_list.append(info)
+
+ url = ("{server}/emby/Users/{userid}/Items/%s?format=json&Fields=RemoteTrailers" % id)
+ jsonData = downloadUtils.downloadUrl(url)
+ result = json.loads(jsonData)
+ log.debug("RemoteTrailers: {0}", result)
+
+ remote_trailers = result.get("RemoteTrailers", [])
+ for trailer in remote_trailers:
+ info = {}
+ info["type"] = "remote"
+ info["name"] = trailer.get("Name", "na")
+ url = trailer.get("Url", "none")
+ if url.lower().find("youtube"):
+ info["url"] = url
+ trailer_list.append(info)
+
+ log.debug("TrailerList: {0}", trailer_list)
+
+ trailer_text = []
+ for trailer in trailer_list:
+ name = trailer.get("name") + " (" + trailer.get("type") + ")"
+ trailer_text.append(name)
+
+ dialog = xbmcgui.Dialog()
+ resp = dialog.select(i18n('select_trailer'), trailer_text)
+ if resp > -1:
+ trailer = trailer_list[resp]
+ log.debug("SelectedTrailer: {0}", trailer)
+
+ if trailer.get("type") == "local":
+ params = {}
+ params["item_id"] = trailer.get("id")
+ PLAY(params)
+
+ elif trailer.get("type") == "remote":
+ youtube_id = trailer.get("url").rsplit('=', 1)[1]
+ log.debug("YoutubeID: {0}", youtube_id)
+ youtube_plugin = "PlayMedia(plugin://plugin.video.youtube/?action=play_video&videoid=%s)" % youtube_id
+ xbmc.executebuiltin(youtube_plugin)
+
- home_window = HomeWindow()
- home_window.setProperty("item_id", item_id)
- home_window.setProperty("play_item_message", play_data)
- #xbmcgui.Dialog().notification("EmbyCon", "Starting Playback")
diff --git a/plugin.video.embycon/resources/lib/item_functions.py b/plugin.video.embycon/resources/lib/item_functions.py
new file mode 100644
index 0000000..3818613
--- /dev/null
+++ b/plugin.video.embycon/resources/lib/item_functions.py
@@ -0,0 +1,556 @@
+
+import sys
+import os
+import urllib
+import json
+from collections import defaultdict
+
+import xbmc
+import xbmcaddon
+import xbmcgui
+
+from utils import getArt
+from simple_logging import SimpleLogging
+from translation import i18n
+from downloadutils import DownloadUtils
+from datamanager import DataManager
+
+log = SimpleLogging(__name__)
+kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
+
+addon_instance = xbmcaddon.Addon(id='plugin.video.embycon')
+addon_path = addon_instance.getAddonInfo('path')
+PLUGINPATH = xbmc.translatePath(os.path.join(addon_path))
+
+download_utils = DownloadUtils()
+
+class ItemDetails():
+
+ name = None
+ id = None
+ path = None
+ is_folder = False
+ plot = None
+ series_name = None
+ episode_number = 0
+ season_number = 0
+ track_number = 0
+
+ art = None
+
+ mpaa = None
+ rating = None
+ critic_rating = 0.0
+ year = None
+ premiere_date = ""
+ date_added = ""
+ location_type = None
+ studio = None
+ genre = ""
+ play_count = 0
+ director = ""
+ writer = ""
+ channels = ""
+ video_codec = ""
+ aspect_ratio = 0.0
+ audio_codec = ""
+ height = 0
+ width = 0
+ cast = None
+
+ resume_time = 0
+ duration = 0
+ recursive_item_count = 0
+ recursive_unplayed_items_count = 0
+ total_seasons = 0
+ total_episodes = 0
+ watched_episodes = 0
+ unwatched_episodes = 0
+ number_episodes = 0
+ original_title = None
+ item_type = None
+ subtitle_lang = ""
+ subtitle_available = False
+
+ song_artist = ""
+ album_artist = ""
+ album_name = ""
+
+ favorite = "false"
+ overlay = "0"
+
+ name_format = ""
+ mode = ""
+
+def extract_item_info(item, gui_options):
+
+ item_details = ItemDetails()
+
+ item_details.id = item["Id"]
+ item_details.is_folder = item["IsFolder"]
+ item_details.item_type = item["Type"]
+ item_details.location_type = item["LocationType"]
+ item_details.name = item["Name"]
+ item_details.original_title = item_details.name
+
+ if item_details.item_type == "Episode":
+ item_details.episode_number = item["IndexNumber"]
+
+ if item_details.item_type == "Episode":
+ item_details.season_number = item["ParentIndexNumber"]
+ elif item_details.item_type == "Season":
+ item_details.season_number = item["IndexNumber"]
+
+ if item_details.season_number is None:
+ item_details.season_number = 0
+ if item_details.episode_number is None:
+ item_details.episode_number = 0
+
+ if item_details.item_type == "Audio":
+ item_details.track_number = item["IndexNumber"]
+ item_details.album_name = item["Album"]
+ artists = item["Artists"]
+ if artists is not None and len(artists) > 0:
+ item_details.song_artist = artists[0] # get first artist
+
+ if item_details.item_type == "MusicAlbum":
+ item_details.album_artist = item["AlbumArtist"]
+ item_details.album_name = item_details.name
+
+ # set the item name
+ # override with name format string from request
+ name_format = gui_options["name_format"]
+ name_format_type = gui_options["name_format_type"]
+
+ if name_format is not None and item_details.item_type == name_format_type:
+ nameInfo = {}
+ nameInfo["ItemName"] = item["Name"]
+ season_name = item["SeriesName"]
+ if season_name:
+ nameInfo["SeriesName"] = season_name
+ else:
+ nameInfo["SeriesName"] = ""
+ nameInfo["SeasonIndex"] = u"%02d" % item_details.season_number
+ nameInfo["EpisodeIndex"] = u"%02d" % item_details.episode_number
+ log.debug("FormatName: {0} | {1}", name_format, nameInfo)
+ item_details.name = unicode(name_format).format(**nameInfo).strip()
+
+ year = item["ProductionYear"]
+ prem_date = item["PremiereDate"]
+
+ if year is not None:
+ item_details.year = year
+ elif item_details.year is None and prem_date is not None:
+ item_details.year = int(prem_date[:4])
+
+ if prem_date is not None:
+ tokens = prem_date.split("T")
+ item_details.premiere_date = tokens[0]
+
+ create_date = item["DateCreated"]
+ if create_date is not None:
+ item_details.date_added = create_date.split('.')[0].replace('T', " ")
+
+ # add the premiered date for Upcoming TV
+ if item_details.location_type == "Virtual":
+ airtime = item["AirTime"]
+ item_details.name = item_details.name + ' - ' + item_details.premiere_date + ' - ' + str(airtime)
+
+ # Process MediaStreams
+ mediaStreams = item["MediaStreams"]
+ if mediaStreams is not None:
+ for mediaStream in mediaStreams:
+ stream_type = mediaStream["Type"]
+ if stream_type == "Video":
+ item_details.video_codec = mediaStream["Codec"]
+ item_details.height = mediaStream["Height"]
+ item_details.width = mediaStream["Width"]
+ aspect = mediaStream["AspectRatio"]
+ if aspect is not None and len(aspect) >= 3:
+ try:
+ aspect_width, aspect_height = aspect.split(':')
+ item_details.aspect_ratio = float(aspect_width) / float(aspect_height)
+ except:
+ item_details.aspect_ratio = 1.85
+ if stream_type == "Audio":
+ item_details.audio_codec = mediaStream["Codec"]
+ item_details.channels = mediaStream["Channels"]
+ if stream_type == "Subtitle":
+ item_details.subtitle_available = True
+ sub_lang = mediaStream["Language"]
+ if sub_lang is not None:
+ item_details.subtitle_lang = sub_lang
+
+ # Process People
+ people = item["People"]
+ if people is not None:
+ cast = []
+ for person in people:
+ person_type = person["Type"]
+ if person_type == "Director":
+ item_details.director = item_details.director + person["Name"] + ' '
+ elif person_type == "Writing":
+ item_details.writer = person["Name"]
+ elif person_type == "Actor":
+ log.debug("Person: {0}", person)
+ person_name = person["Name"]
+ person_role = person["Role"]
+ person_id = person["Id"]
+ person_tag = person["PrimaryImageTag"]
+ if person_tag is not None:
+ person_thumbnail = download_utils.imageUrl(person_id, "Primary", 0, 400, 400, person_tag, server = gui_options["server"])
+ else:
+ person_thumbnail = ""
+ person = {"name": person_name, "role": person_role, "thumbnail": person_thumbnail}
+ cast.append(person)
+ item_details.cast = cast
+
+ # Process Studios
+ studios = item["Studios"]
+ if studios is not None:
+ for studio in studios:
+ if item_details.studio == "": # Just take the first one
+ studio_name = studio["Name"]
+ item_details.studio = studio_name
+ break
+
+ # Process Genres
+ genres = item["Genres"]
+ if genres is not None:
+ for genre in genres:
+ item_details.genre = item_details.genre + " / " + genre
+
+ # Process UserData
+ userData = item["UserData"]
+ if userData is None:
+ userData = defaultdict(lambda: None, {})
+
+ if userData["Played"] == True:
+ item_details.overlay = "6"
+ item_details.play_count = 1
+ else:
+ item_details.overlay = "7"
+ item_details.play_count = 0
+
+ if userData["IsFavorite"] == True:
+ item_details.overlay = "5"
+ item_details.favorite = "true"
+ else:
+ item_details.favorite = "false"
+
+ reasonableTicks = userData["PlaybackPositionTicks"]
+ if reasonableTicks is not None:
+ reasonableTicks = int(reasonableTicks) / 1000
+ item_details.resume_time = int(reasonableTicks / 10000)
+
+ item_details.series_name = item["SeriesName"]
+ item_details.plot = item["Overview"]
+
+ runtime = item["RunTimeTicks"]
+ if item_details.is_folder == False and runtime is not None:
+ item_details.duration = long(runtime) / 10000000
+
+ child_count = item["ChildCount"]
+ if child_count is not None:
+ item_details.total_seasons = child_count
+
+ recursive_item_count = item["RecursiveItemCount"]
+ if recursive_item_count is not None:
+ item_details.total_episodes = recursive_item_count
+
+ unplayed_item_count = userData["UnplayedItemCount"]
+ if unplayed_item_count is not None:
+ item_details.unwatched_episodes = unplayed_item_count
+ item_details.watched_episodes = item_details.total_episodes - unplayed_item_count
+
+ item_details.number_episodes = item_details.total_episodes
+
+ item_details.art = getArt(item, gui_options["server"])
+ item_details.rating = item["OfficialRating"]
+ item_details.mpaa = item["OfficialRating"]
+ item_details.critic_rating = item["CommunityRating"]
+ if item_details.critic_rating is None:
+ item_details.critic_rating = 0.0
+ item_details.location_type = item["LocationType"]
+ item_details.recursive_item_count = item["RecursiveItemCount"]
+ item_details.recursive_unplayed_items_count = userData["UnplayedItemCount"]
+
+ item_details.mode = "GET_CONTENT"
+
+ return item_details
+
+def add_gui_item(url, item_details, display_options, folder=True):
+
+ log.debug("Passed item_details: {0}", item_details.__dict__)
+
+ if not item_details.name:
+ return
+
+ if item_details.mode:
+ mode = "&mode=%s" % item_details.mode
+ else:
+ mode = "&mode=0"
+
+ # Create the URL to pass to the item
+ if folder:
+ u = sys.argv[0] + "?url=" + urllib.quote(url) + mode + "&media_type=" + item_details.item_type
+ if item_details.name_format:
+ u += '&name_format=' + urllib.quote(item_details.name_format)
+ else:
+ u = sys.argv[0] + "?item_id=" + url + "&mode=PLAY"
+
+ # Create the ListItem that will be displayed
+ thumbPath = item_details.art["thumb"]
+
+ listItemName = item_details.name
+ item_type = item_details.item_type.lower()
+ is_video = item_type not in ['musicalbum', 'audio', 'music']
+
+ # calculate percentage
+ cappedPercentage = 0
+ if item_details.resume_time > 0:
+ duration = float(item_details.duration)
+ if (duration > 0):
+ resume = float(item_details.resume_time)
+ percentage = int((resume / duration) * 100.0)
+ cappedPercentage = percentage
+
+ totalItems = item_details.total_episodes
+ if totalItems != 0:
+ watched = float(item_details.watched_episodes)
+ percentage = int((watched / float(totalItems)) * 100.0)
+ cappedPercentage = percentage
+
+ countsAdded = False
+ addCounts = display_options["addCounts"]
+ if addCounts and item_details.unwatched_episodes != 0:
+ countsAdded = True
+ listItemName = listItemName + (" (%s)" % item_details.unwatched_episodes)
+
+ addResumePercent = display_options["addResumePercent"]
+ if (not countsAdded
+ and addResumePercent
+ and cappedPercentage not in [0, 100]):
+ listItemName = listItemName + (" (%s%%)" % cappedPercentage)
+
+ subtitle_available = display_options["addSubtitleAvailable"]
+ if subtitle_available and item_details.subtitle_available:
+ listItemName += " (cc)"
+
+ if kodi_version > 17:
+ list_item = xbmcgui.ListItem(listItemName, offscreen=True)
+ else:
+ list_item = xbmcgui.ListItem(listItemName, iconImage=thumbPath, thumbnailImage=thumbPath)
+
+ log.debug("Setting thumbnail as: {0}", thumbPath)
+
+ # calculate percentage
+ if (cappedPercentage != 0):
+ list_item.setProperty("complete_percentage", str(cappedPercentage))
+
+ list_item.setProperty('IsPlayable', 'false')
+
+ if folder == False and is_video:
+ list_item.setProperty('TotalTime', str(item_details.duration))
+ list_item.setProperty('ResumeTime', str(item_details.resume_time))
+
+ list_item.setArt(item_details.art)
+
+ list_item.setProperty('fanart_image', item_details.art['fanart']) # back compat
+ list_item.setProperty('discart', item_details.art['discart']) # not avail to setArt
+ list_item.setProperty('tvshow.poster', item_details.art['tvshow.poster']) # not avail to setArt
+
+ # add context menu
+ menu_items = add_context_menu(item_details, folder)
+ if len(menu_items) > 0:
+ list_item.addContextMenuItems(menu_items, True)
+
+ # new way
+ info_labels = {}
+
+ # add cast
+ if item_details.cast is not None:
+ if kodi_version >= 17:
+ list_item.setCast(item_details.cast)
+ else:
+ info_labels['cast'] = info_labels['castandrole'] = [(cast_member['name'], cast_member['role']) for cast_member in item_details.cast]
+
+ info_labels["title"] = listItemName
+ info_labels["plot"] = item_details.plot
+ info_labels["Overlay"] = item_details.overlay
+ info_labels["playcount"] = str(item_details.play_count)
+ info_labels["TVShowTitle"] = item_details.series_name
+
+ info_labels["duration"] = item_details.duration
+ info_labels["playcount"] = item_details.play_count
+ if item_details.favorite == 'true':
+ info_labels["top250"] = "1"
+
+ info_labels["mpaa"] = item_details.mpaa
+ info_labels["rating"] = item_details.rating
+ info_labels["director"] = item_details.director
+ info_labels["writer"] = item_details.writer
+ info_labels["year"] = item_details.year
+ info_labels["premiered"] = item_details.premiere_date
+ info_labels["dateadded"] = item_details.date_added
+ info_labels["studio"] = item_details.studio
+ info_labels["genre"] = item_details.genre
+
+ mediatype = 'video'
+
+ if item_type == 'movie':
+ mediatype = 'movie'
+ elif item_type == 'boxset':
+ mediatype = 'set'
+ elif item_type == 'series':
+ mediatype = 'tvshow'
+ elif item_type == 'season':
+ mediatype = 'season'
+ elif item_type == 'episode':
+ mediatype = 'episode'
+ elif item_type == 'musicalbum':
+ mediatype = 'album'
+ elif item_type == 'musicartist':
+ mediatype = 'artist'
+ elif item_type == 'audio' or item_type == 'music':
+ mediatype = 'song'
+
+ info_labels["mediatype"] = mediatype
+
+ if mediatype == 'episode':
+ info_labels["episode"] = item_details.episode_number
+
+ if (mediatype == 'season') or (mediatype == 'episode'):
+ info_labels["season"] = item_details.season_number
+
+ if is_video:
+ list_item.setInfo('video', info_labels)
+ log.debug("info_labels: {0}", info_labels)
+ list_item.addStreamInfo('video',
+ {'duration': item_details.duration,
+ 'aspect': item_details.aspect_ratio,
+ 'codec': item_details.video_codec,
+ 'width': item_details.width,
+ 'height': item_details.height})
+ list_item.addStreamInfo('audio',
+ {'codec': item_details.audio_codec,
+ 'channels': item_details.channels})
+
+ list_item.setProperty('TotalSeasons', str(item_details.total_seasons))
+ list_item.setProperty('TotalEpisodes', str(item_details.total_episodes))
+ list_item.setProperty('WatchedEpisodes', str(item_details.watched_episodes))
+ list_item.setProperty('UnWatchedEpisodes', str(item_details.unwatched_episodes))
+ list_item.setProperty('NumEpisodes', str(item_details.number_episodes))
+
+ if item_details.subtitle_lang != '':
+ list_item.addStreamInfo('subtitle', {'language': item_details.subtitle_lang})
+
+ list_item.setRating("imdb", item_details.critic_rating, 0, True)
+ list_item.setProperty('TotalTime', str(item_details.duration))
+
+ else:
+ info_labels["tracknumber"] = item_details.track_number
+ if item_details.album_artist:
+ info_labels["artist"] = item_details.album_artist
+ elif item_details.song_artist:
+ info_labels["artist"] = item_details.song_artist
+ info_labels["album"] = item_details.album_name
+
+ log.debug("info_labels: {0}", info_labels)
+ list_item.setInfo('music', info_labels)
+
+ list_item.setContentLookup(False)
+ list_item.setProperty('ItemType', item_details.item_type)
+ list_item.setProperty('id', item_details.id)
+
+ return (u, list_item, folder)
+
+
+def add_context_menu(item_details, folder):
+ commands = []
+
+ if item_details.id is None:
+ return commands
+
+ scriptToRun = PLUGINPATH + "/default.py"
+
+ if item_details.item_type == "Season" or item_details.item_type == "MusicAlbum":
+ argsToPass = "?mode=PLAY&item_id=" + item_details.id
+ commands.append((i18n('play_all'), "RunPlugin(plugin://plugin.video.embycon" + argsToPass + ")"))
+
+ if not folder:
+ argsToPass = "?mode=PLAY&item_id=" + item_details.id + "&force_transcode=true"
+ commands.append((i18n('emby_force_transcode'), "RunPlugin(plugin://plugin.video.embycon" + argsToPass + ")"))
+
+ if not folder and item_details.item_type == "Movie":
+ argsToPass = "?mode=playTrailer&id=" + item_details.id
+ commands.append((i18n('play_trailer'), "RunPlugin(plugin://plugin.video.embycon" + argsToPass + ")"))
+
+ # watched/unwatched
+ if item_details.play_count == 0:
+ argsToPass = 'markWatched,' + item_details.id
+ commands.append((i18n('emby_mark_watched'), "RunScript(" + scriptToRun + ", " + argsToPass + ")"))
+ else:
+ argsToPass = 'markUnwatched,' + item_details.id
+ commands.append((i18n('emby_mark_unwatched'), "RunScript(" + scriptToRun + ", " + argsToPass + ")"))
+
+ # favourite add/remove
+ if item_details.favorite == 'false':
+ argsToPass = 'markFavorite,' + item_details.id
+ commands.append((i18n('emby_set_favorite'), "RunScript(" + scriptToRun + ", " + argsToPass + ")"))
+ else:
+ argsToPass = 'unmarkFavorite,' + item_details.id
+ commands.append((i18n('emby_unset_favorite'), "RunScript(" + scriptToRun + ", " + argsToPass + ")"))
+
+ # delete
+ argsToPass = 'delete,' + item_details.id
+ commands.append((i18n('emby_delete'), "RunScript(" + scriptToRun + ", " + argsToPass + ")"))
+
+ return commands
+
+
+def get_next_episode(item):
+
+ if item.get("Type", "na") != "Episode":
+ log.debug("Not an episode, can not get next")
+ return None
+
+ parendId = item.get("ParentId", "na")
+ item_index = item.get("IndexNumber", -1)
+
+ if parendId == "na":
+ log.debug("No parent id, can not get next")
+ return None
+
+ if item_index == -1:
+ log.debug("No episode number, can not get next")
+ return None
+
+ url = ( '{server}/emby/Users/{userid}/Items?' +
+ '?Recursive=true' +
+ '&ParentId=' + parendId +
+ '&IsVirtualUnaired=false' +
+ '&IsMissing=False' +
+ '&IncludeItemTypes=Episode' +
+ '&ImageTypeLimit=1' +
+ '&format=json')
+
+ data_manager = DataManager()
+ items_result = data_manager.GetContent(url)
+ log.debug("get_next_episode, sibling list: {0}", items_result)
+
+ if items_result is None:
+ log.debug("get_next_episode no results")
+ return None
+
+ item_list = items_result.get("Items", [])
+
+ for item in item_list:
+ index = item.get("IndexNumber", -1)
+ # find the very next episode in the season
+ if index == item_index + 1:
+ log.debug("get_next_episode, found next episode: {0}", item)
+ return item
+
+ return None
+
diff --git a/plugin.video.embycon/resources/lib/kodi_utils.py b/plugin.video.embycon/resources/lib/kodi_utils.py
index 0bf3ab5..21071d4 100644
--- a/plugin.video.embycon/resources/lib/kodi_utils.py
+++ b/plugin.video.embycon/resources/lib/kodi_utils.py
@@ -23,17 +23,17 @@ class HomeWindow():
def getProperty(self, key):
key = self.id_string % key
value = self.window.getProperty(key)
- # log.debug('HomeWindow: getProperty |%s| -> |%s|' % (key, value))
+ # log.debug('HomeWindow: getProperty |{0}| -> |{1}|', key, value)
return value
def setProperty(self, key, value):
key = self.id_string % key
- # log.debug('HomeWindow: setProperty |%s| -> |%s|' % (key, value))
+ # log.debug('HomeWindow: setProperty |{0}| -> |{1}|', key, value)
self.window.setProperty(key, value)
def clearProperty(self, key):
key = self.id_string % key
- # log.debug('HomeWindow: clearProperty |%s|' % key)
+ # log.debug('HomeWindow: clearProperty |{0}|', key)
self.window.clearProperty(key)
@@ -59,9 +59,9 @@ def getKodiVersion():
result = result.get("result")
versionData = result.get("version")
version = float(str(versionData.get("major")) + "." + str(versionData.get("minor")))
- log.debug("Version : " + str(version) + " - " + str(versionData))
+ log.debug("Version: {0} - {1}", version, versionData)
except:
version = 0.0
- log.error("Version Error : RAW Version Data : " + str(result))
+ log.error("Version Error : RAW Version Data: {0}", result)
return version
diff --git a/plugin.video.embycon/resources/lib/menu_functions.py b/plugin.video.embycon/resources/lib/menu_functions.py
index 4e9ca0f..89809e9 100644
--- a/plugin.video.embycon/resources/lib/menu_functions.py
+++ b/plugin.video.embycon/resources/lib/menu_functions.py
@@ -9,10 +9,10 @@ import xbmcplugin
import xbmcaddon
from downloadutils import DownloadUtils
-from utils import getDetailsString
from kodi_utils import addMenuDirectoryItem
from simple_logging import SimpleLogging
from translation import i18n
+from datamanager import DataManager
log = SimpleLogging(__name__)
downloadUtils = DownloadUtils()
@@ -27,29 +27,27 @@ def showGenreList(item_type=None):
if server is None:
return
- detailsString = getDetailsString()
-
kodi_type = "Movies"
emby_type = "Movie"
if item_type is not None and item_type == "series":
emby_type = "Series"
kodi_type = "tvshows"
- try:
- jsonData = downloadUtils.downloadUrl("{server}/emby/Genres?" +
- "SortBy=SortName" +
- "&SortOrder=Ascending" +
- "&IncludeItemTypes=" + emby_type +
- "&Recursive=true" +
- "&UserId={userid}" +
- "&format=json")
- log.debug("GENRE_LIST_DATA : " + jsonData)
- except Exception, msg:
- error = "Get connect : " + str(msg)
- log.error(error)
+ url = ("{server}/emby/Genres?" +
+ "SortBy=SortName" +
+ "&SortOrder=Ascending" +
+ "&IncludeItemTypes=" + emby_type +
+ "&Recursive=true" +
+ "&UserId={userid}" +
+ "&format=json")
- result = json.loads(jsonData)
- result = result.get("Items")
+ data_manager = DataManager()
+ result = data_manager.GetContent(url)
+
+ if result is not None:
+ result = result.get("Items")
+ else:
+ result = []
collections = []
@@ -58,7 +56,7 @@ def showGenreList(item_type=None):
item_data['title'] = genre.get("Name")
item_data['media_type'] = kodi_type
item_data['thumbnail'] = downloadUtils.getArtwork(genre, "Thumb", server=server)
- item_data['path'] = ('{server}/emby/Users/{userid}/Items?Fields=' + detailsString +
+ item_data['path'] = ('{server}/emby/Users/{userid}/Items?Fields={field_filters}' +
'&Recursive=true&GenreIds=' + genre.get("Id") +
'&IncludeItemTypes=' + emby_type +
'&ImageTypeLimit=1&format=json')
@@ -68,7 +66,7 @@ def showGenreList(item_type=None):
url = sys.argv[0] + ("?url=" + urllib.quote(collection['path']) +
"&mode=GET_CONTENT" +
"&media_type=" + collection["media_type"])
- log.debug("addMenuDirectoryItem: " + collection.get('title', i18n('unknown')) + " " + str(url))
+ log.debug("addMenuDirectoryItem: {0} ({1})", collection.get('title'), url)
addMenuDirectoryItem(collection.get('title', i18n('unknown')), url, thumbnail=collection.get("thumbnail"))
xbmcplugin.endOfDirectory(int(sys.argv[1]))
@@ -81,15 +79,14 @@ def showMovieAlphaList():
server = downloadUtils.getServer()
if server is None:
return
- detailsString = getDetailsString()
collections = []
item_data = {}
item_data['title'] = "#"
item_data['media_type'] = "Movies"
- item_data['path'] = ('{server}/emby/Users/{userid}' +
- '/Items?Fields=' + detailsString +
+ item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
+ '?Fields={field_filters}' +
'&Recursive=true' +
'&NameLessThan=A' +
'&IncludeItemTypes=Movie' +
@@ -103,8 +100,8 @@ def showMovieAlphaList():
item_data = {}
item_data['title'] = alphaName
item_data['media_type'] = "Movies"
- item_data['path'] = ('{server}/emby/Users/{userid}' +
- '/Items?Fields=' + detailsString +
+ item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
+ '?Fields={field_filters}' +
'&Recursive=true' +
'&NameStartsWith=' + alphaName +
'&IncludeItemTypes=Movie' +
@@ -114,11 +111,58 @@ def showMovieAlphaList():
for collection in collections:
url = (sys.argv[0] + "?url=" + urllib.quote(collection['path']) +
"&mode=GET_CONTENT&media_type=" + collection["media_type"])
- log.debug("addMenuDirectoryItem: " + collection.get('title', i18n('unknown')) + " " + str(url))
+ log.debug("addMenuDirectoryItem: {0} ({1})", collection.get('title'), url)
addMenuDirectoryItem(collection.get('title', i18n('unknown')), url)
xbmcplugin.endOfDirectory(int(sys.argv[1]))
+def showYearsList():
+
+ server = downloadUtils.getServer()
+ if server is None:
+ return
+
+ jsonData = downloadUtils.downloadUrl("{server}/emby/Years" +
+ "?SortBy=SortName" +
+ "&SortOrder=Descending" +
+ "&IncludeItemTypes=Movie" +
+ "&Recursive=true" +
+ "&UserId={userid}" +
+ "&format=json")
+ log.debug("YEAR_LIST_DATA: {0}", jsonData)
+
+ result = json.loads(jsonData)
+ if result is not None:
+ result = result.get("Items")
+ else:
+ result = []
+
+ collections = []
+
+ for year in result:
+ item_data = {}
+ item_data['title'] = year.get("Name")
+ item_data['media_type'] = "Movies"
+ #item_data['thumbnail'] = server + "/Years/" + year.get("Name") + "/Images/Thumb"
+ item_data['path'] = ('{server}/emby/Users/{userid}/Items'
+ '?Fields={field_filters}' +
+ '&Recursive=true' +
+ '&Years=' + year.get("Name") +
+ '&IncludeItemTypes=Movie' +
+ '&ImageTypeLimit=1' +
+ '&format=json')
+ collections.append(item_data)
+
+ for collection in collections:
+ url = sys.argv[0] + ("?url=" + urllib.quote(collection['path']) +
+ "&mode=GET_CONTENT" +
+ "&media_type=" + collection["media_type"])
+ log.debug("addMenuDirectoryItem: {0} ({1})", collection.get('title'), url)
+ addMenuDirectoryItem(collection.get('title', i18n('unknown')), url)#, thumbnail=collection.get("thumbnail"))
+
+ xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_LABEL)
+ xbmcplugin.endOfDirectory(int(sys.argv[1]))
+
def displaySections():
log.debug("== ENTER: displaySections() ==")
@@ -129,8 +173,7 @@ def displaySections():
return
# Add collections
- detailsString = getDetailsString()
- collections = getCollections(detailsString)
+ collections = getCollections()
if collections:
for collection in collections:
@@ -138,9 +181,10 @@ def displaySections():
"&mode=GET_CONTENT&media_type=" + collection["media_type"])
if collection.get("name_format") is not None:
url += "&name_format=" + urllib.quote(collection.get("name_format"))
- log.debug("addMenuDirectoryItem: " + collection.get('title', i18n('unknown')) + " " + str(url))
+ log.debug("addMenuDirectoryItem: {0} ({1})", collection.get('title'), url)
addMenuDirectoryItem(collection.get('title', i18n('unknown')), url, thumbnail=collection.get("thumbnail"))
+ addMenuDirectoryItem(i18n('movies_year'), "plugin://plugin.video.embycon/?mode=MOVIE_YEARS")
addMenuDirectoryItem(i18n('movies_genre'), "plugin://plugin.video.embycon/?mode=MOVIE_GENRE")
addMenuDirectoryItem(i18n('movies_az'), "plugin://plugin.video.embycon/?mode=MOVIE_ALPHA")
addMenuDirectoryItem(i18n('tvshow_genre'), "plugin://plugin.video.embycon/?mode=SERIES_GENRE")
@@ -151,15 +195,16 @@ def displaySections():
addMenuDirectoryItem(i18n('detect_server'), "plugin://plugin.video.embycon/?mode=DETECT_SERVER_USER")
addMenuDirectoryItem(i18n('show_settings'), "plugin://plugin.video.embycon/?mode=SHOW_SETTINGS")
- addMenuDirectoryItem(i18n('cache_textures'), "plugin://plugin.video.embycon/?mode=CACHE_ARTWORK")
+ # only add these if we have other collection which means we have a valid server conn
if collections:
+ addMenuDirectoryItem(i18n('cache_textures'), "plugin://plugin.video.embycon/?mode=CACHE_ARTWORK")
addMenuDirectoryItem(i18n('widgets'), "plugin://plugin.video.embycon/?mode=WIDGETS")
xbmcplugin.endOfDirectory(int(sys.argv[1]))
-def getCollections(detailsString):
+def getCollections():
log.debug("== ENTER: getCollections ==")
server = downloadUtils.getServer()
@@ -172,39 +217,56 @@ def getCollections(detailsString):
log.debug("No userid so returning []")
return []
- try:
- jsonData = downloadUtils.downloadUrl("{server}/emby/Users/{userid}/Items/Root?format=json")
- except Exception, msg:
- error = "Get connect : " + str(msg)
- log.error(error)
- return []
-
- log.debug("jsonData : " + jsonData)
- result = json.loads(jsonData)
+ data_manager = DataManager()
+ result = data_manager.GetContent("{server}/emby/Users/{userid}/Items/Root?format=json")
if result is None:
return []
parentid = result.get("Id")
- log.debug("parentid : " + parentid)
+ log.debug("parentid: {0}", parentid)
htmlpath = "{server}/emby/Users/{userid}/items?ParentId=" + parentid + "&Sortby=SortName&format=json"
- jsonData = downloadUtils.downloadUrl(htmlpath)
- log.debug("jsonData : " + jsonData)
- collections = []
+ result = data_manager.GetContent(htmlpath)
- result = []
- try:
- result = json.loads(jsonData)
+ if result is not None:
result = result.get("Items")
- except Exception as error:
- log.error("Error parsing user collection: " + str(error))
+ else:
+ result = []
+
+ collections = []
for item in result:
- item_name = (item.get("Name")).encode('utf-8')
+ item_name = item.get("Name")
collection_type = item.get('CollectionType', None)
- log.debug("CollectionType: " + str(collection_type))
- log.debug("Title: " + item_name)
+ log.debug("CollectionType: {0}", collection_type)
+ log.debug("Title: {0}", item_name)
+
+ if collection_type == "music":
+ item_data = {}
+ item_data['title'] = item_name + i18n('_all_albums')
+ item_data['thumbnail'] = downloadUtils.getArtwork(item, "Primary", server=server)
+ item_data['media_type'] = 'MusicAlbums'
+ item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
+ '?Recursive=true' +
+ '&ParentId=' + item.get("Id") +
+ '&IncludeItemTypes=MusicAlbum' +
+ '&ImageTypeLimit=1' +
+ '&EnableImageTypes=Primary,Backdrop,Banner,Thumb' +
+ '&format=json')
+ collections.append(item_data)
+
+ item_data = {}
+ item_data['title'] = item_name + i18n('_all_artists')
+ item_data['thumbnail'] = downloadUtils.getArtwork(item, "Primary", server=server)
+ item_data['media_type'] = 'MusicArtists'
+ item_data['path'] = ('{server}/emby/Artists/AlbumArtists' +
+ '?Recursive=true' +
+ '&ParentId=' + item.get("Id") +
+ '&ImageTypeLimit=1' +
+ '&EnableImageTypes=Primary,Backdrop,Banner,Thumb' +
+ '&format=json')
+ collections.append(item_data)
if collection_type in ["tvshows", "movies", "boxsets"]:
collections.append({
@@ -214,7 +276,7 @@ def getCollections(detailsString):
'?ParentId=' + item.get("Id") +
'&IsVirtualUnaired=false' +
'&IsMissing=False' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&ImageTypeLimit=1' +
'&format=json'),
'media_type': collection_type})
@@ -227,7 +289,7 @@ def getCollections(detailsString):
'?ParentId=' + item.get("Id") +
'&IsVirtualUnaired=false' +
'&IsMissing=False' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&Filters=IsUnplayed' +
'&Recursive=true' +
'&IncludeItemTypes=Series' +
@@ -242,7 +304,7 @@ def getCollections(detailsString):
'&Limit={ItemLimit}' +
'&IsVirtualUnaired=false' +
'&IsMissing=False' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&Filters=IsResumable' +
'&Recursive=true' +
'&IncludeItemTypes=Episode' +
@@ -258,7 +320,7 @@ def getCollections(detailsString):
'&Limit={ItemLimit}' +
'&IsVirtualUnaired=false' +
'&IsMissing=False' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&SortBy=DateCreated' +
'&SortOrder=Descending' +
'&Filters=IsUnplayed' +
@@ -276,7 +338,7 @@ def getCollections(detailsString):
'&Limit={ItemLimit}' +
'&IsVirtualUnaired=false' +
'&IsMissing=False' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&SortBy=DateCreated' +
'&SortOrder=Descending' +
'&Filters={IsUnplayed,}IsNotFolder' +
@@ -293,7 +355,7 @@ def getCollections(detailsString):
'&ParentId=' + item.get("Id") +
'&Limit={ItemLimit}' +
'&Recursive=true' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&Filters=IsUnplayed,IsNotFolder' +
'&IsVirtualUnaired=false' +
'&IsMissing=False' +
@@ -311,7 +373,7 @@ def getCollections(detailsString):
'?ParentId=' + item.get("Id") +
'&IsVirtualUnaired=false' +
'&IsMissing=False' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&Filters=IsUnplayed' +
'&ImageTypeLimit=1' +
'&format=json'),
@@ -324,7 +386,7 @@ def getCollections(detailsString):
'&Limit={ItemLimit}' +
'&IsVirtualUnaired=false' +
'&IsMissing=False' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&Filters=IsResumable' +
'&ImageTypeLimit=1' +
'&format=json'),
@@ -337,7 +399,7 @@ def getCollections(detailsString):
'&Limit={ItemLimit}' +
'&IsVirtualUnaired=false' +
'&IsMissing=False' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&SortBy=DateCreated' +
'&SortOrder=Descending' +
'&Filters={IsUnplayed,}IsNotFolder' +
@@ -350,7 +412,7 @@ def getCollections(detailsString):
item_data['title'] = i18n('movies_all')
item_data['media_type'] = 'Movies'
item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
- '?Fields=' + detailsString +
+ '?Fields={field_filters}' +
'&Recursive=true' +
'&IncludeItemTypes=Movie' +
'&ImageTypeLimit=1' +
@@ -362,7 +424,7 @@ def getCollections(detailsString):
item_data['media_type'] = 'Movies'
item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
'?Recursive=true' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&Filters=IsUnplayed' +
'&IncludeItemTypes=Movie' +
'&ImageTypeLimit=1' +
@@ -375,7 +437,7 @@ def getCollections(detailsString):
item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
'?Limit={ItemLimit}' +
'&Recursive=true' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&Filters=IsResumable' +
'&IncludeItemTypes=Movie' +
'&ImageTypeLimit=1' +
@@ -389,7 +451,7 @@ def getCollections(detailsString):
'?Limit={ItemLimit}' +
'&Recursive=true' +
'&SortBy=DateCreated' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&SortOrder=Descending' +
'&Filters={IsUnplayed,}IsNotFolder' +
'&IncludeItemTypes=Movie' +
@@ -401,7 +463,7 @@ def getCollections(detailsString):
item_data['title'] = i18n('movies_favorites')
item_data['media_type'] = 'Movies'
item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
- '?Fields=' + detailsString +
+ '?Fields={field_filters}' +
'&Recursive=true' +
'&Filters=IsFavorite' +
'&IncludeItemTypes=Movie' +
@@ -414,7 +476,7 @@ def getCollections(detailsString):
item_data['media_type'] = 'BoxSets'
item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
'?Recursive=true' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&IncludeItemTypes=BoxSet' +
'&ImageTypeLimit=1' +
'&format=json')
@@ -424,7 +486,7 @@ def getCollections(detailsString):
item_data['title'] = i18n('tvshows_all')
item_data['media_type'] = 'tvshows'
item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
- '?Fields=' + detailsString +
+ '?Fields={field_filters}' +
'&Recursive=true' +
'&IncludeItemTypes=Series' +
'&ImageTypeLimit=1' +
@@ -435,7 +497,7 @@ def getCollections(detailsString):
item_data['title'] = i18n('tvshows_unwatched')
item_data['media_type'] = 'tvshows'
item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
- '?Fields=' + detailsString +
+ '?Fields={field_filters}' +
'&Recursive=true' +
'&Filters=IsUnplayed' +
'&IncludeItemTypes=Series' +
@@ -447,7 +509,7 @@ def getCollections(detailsString):
item_data['title'] = i18n('tvshows_favorites')
item_data['media_type'] = 'tvshows'
item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
- '?Fields=' + detailsString +
+ '?Fields={field_filters}' +
'&Recursive=true' +
'&Filters=IsFavorite' +
'&IncludeItemTypes=Series' +
@@ -463,7 +525,7 @@ def getCollections(detailsString):
'&Recursive=true' +
'&GroupItems=true' +
'&SortBy=DateCreated' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&SortOrder=Descending' +
'&Filters={IsUnplayed}' +
'&IsVirtualUnaired=false' +
@@ -480,7 +542,7 @@ def getCollections(detailsString):
item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
'?Limit={ItemLimit}' +
'&Recursive=true' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&Filters=IsResumable' +
'&IncludeItemTypes=Episode' +
'&ImageTypeLimit=1' +
@@ -495,7 +557,7 @@ def getCollections(detailsString):
'?Limit={ItemLimit}' +
'&Recursive=true' +
'&SortBy=DateCreated' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&SortOrder=Descending' +
'&Filters={IsUnplayed,}IsNotFolder' +
'&IsVirtualUnaired=false' +
@@ -512,7 +574,7 @@ def getCollections(detailsString):
item_data['path'] = ('{server}/emby/Shows/NextUp/?Userid={userid}' +
'&Limit={ItemLimit}' +
'&Recursive=true' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&Filters=IsUnplayed,IsNotFolder' +
'&IsVirtualUnaired=false' +
'&IsMissing=False' +
@@ -528,7 +590,7 @@ def getCollections(detailsString):
item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
'?Recursive=true' +
'&SortBy=PremiereDate' +
- '&Fields=' + detailsString +
+ '&Fields={field_filters}' +
'&SortOrder=Ascending' +
'&IsVirtualUnaired=true' +
'&IsNotFolder' +
@@ -537,6 +599,28 @@ def getCollections(detailsString):
'&format=json')
collections.append(item_data)
+ item_data = {}
+ item_data['title'] = i18n('music_all_albums')
+ item_data['media_type'] = 'MusicAlbums'
+ item_data['path'] = ('{server}/emby/Users/{userid}/Items' +
+ '?Recursive=true' +
+ '&IncludeItemTypes=MusicAlbum' +
+ '&ImageTypeLimit=1' +
+ '&EnableImageTypes=Primary,Backdrop,Banner,Thumb' +
+ '&format=json')
+ collections.append(item_data)
+
+ item_data = {}
+ item_data['title'] = i18n('music_all_artists')
+ item_data['media_type'] = 'MusicArtists'
+ item_data['path'] = ('{server}/emby/Artists/AlbumArtists' +
+ '?Recursive=true' +
+ '&ImageTypeLimit=1' +
+ '&EnableImageTypes=Primary,Backdrop,Banner,Thumb' +
+ '&format=json')
+ collections.append(item_data)
+
+
return collections
diff --git a/plugin.video.embycon/resources/lib/play_utils.py b/plugin.video.embycon/resources/lib/play_utils.py
index e6455c5..16146d9 100644
--- a/plugin.video.embycon/resources/lib/play_utils.py
+++ b/plugin.video.embycon/resources/lib/play_utils.py
@@ -1,5 +1,7 @@
# Gnu General Public License - see LICENSE.TXT
+import binascii
+
import xbmc
import xbmcgui
import xbmcaddon
@@ -9,42 +11,175 @@ import time
import json
import hashlib
+from resources.lib.error import catch_except
from simple_logging import SimpleLogging
from downloadutils import DownloadUtils
from resume_dialog import ResumeDialog
-from utils import PlayUtils, getArt, id_generator
+from utils import PlayUtils, getArt, id_generator, send_event_notification
from kodi_utils import HomeWindow
from translation import i18n
from json_rpc import json_rpc
+from datamanager import DataManager
+from item_functions import get_next_episode, extract_item_info
log = SimpleLogging(__name__)
-downloadUtils = DownloadUtils()
+download_utils = DownloadUtils()
+
+@catch_except()
+def playAllFiles(id, monitor):
+ log.debug("PlayAllFiles for parent item id: {0}", id)
+
+ url = ('{server}/emby/Users/{userid}/items' +
+ '?ParentId=' + id +
+ '&Fields=MediaSources' +
+ '&format=json')
+ data_manager = DataManager()
+ result = data_manager.GetContent(url)
+ log.debug("PlayAllFiles items info: {0}", result)
+
+ # process each item
+ items = result["Items"]
+ if items is None:
+ items = []
+
+ settings = xbmcaddon.Addon('plugin.video.embycon')
+ server = download_utils.getServer()
+
+ playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
+ playlist.clear()
+ for item in items:
-def playFile(play_info):
+ item_id = item.get("Id")
+ sources = item.get("MediaSources")
+ selected_media_source = sources[0]
+
+ listitem_props = []
+ playback_type = "0"
+ playurl = None
+ play_session_id = id_generator()
+ log.debug("play_session_id: {0}", play_session_id)
+
+ # check if strm file, path will contain contain strm contents
+ if selected_media_source.get('Container') == 'strm':
+ playurl, listitem_props = PlayUtils().getStrmDetails(selected_media_source)
+ if playurl is None:
+ return
+
+ if not playurl:
+ playurl, playback_type = PlayUtils().getPlayUrl(item_id, selected_media_source, False, play_session_id)
+
+ log.debug("Play URL: {0} ListItem Properties: {1}", playurl, listitem_props)
+
+ playback_type_string = "DirectPlay"
+ if playback_type == "2":
+ playback_type_string = "Transcode"
+ elif playback_type == "1":
+ playback_type_string = "DirectStream"
+
+ # add the playback type into the overview
+ if item.get("Overview", None) is not None:
+ item["Overview"] = playback_type_string + "\n" + item.get("Overview")
+ else:
+ item["Overview"] = playback_type_string
+
+ # add title decoration is needed
+ item_title = item.get("Name", i18n('missing_title'))
+ list_item = xbmcgui.ListItem(label=item_title)
+
+ # add playurl and data to the monitor
+ data = {}
+ data["item_id"] = item_id
+ data["playback_type"] = playback_type_string
+ data["play_session_id"] = play_session_id
+ data["play_action_type"] = "play_all"
+ monitor.played_information[playurl] = data
+ log.debug("Add to played_information: {0}", monitor.played_information)
+
+ list_item.setPath(playurl)
+ list_item = setListItemProps(item_id, list_item, item, server, listitem_props, item_title)
+
+ playlist.add(playurl, list_item)
+
+ xbmc.Player().play(playlist)
+
+
+def playFile(play_info, monitor):
id = play_info.get("item_id")
- auto_resume = play_info.get("auto_resume")
- force_transcode = play_info.get("force_transcode")
+ auto_resume = play_info.get("auto_resume", "-1")
+ force_transcode = play_info.get("force_transcode", False)
+ media_source_id = play_info.get("media_source_id", "")
+ use_default = play_info.get("use_default", False)
- log.debug("playFile id(%s) resume(%s) force_transcode(%s)" % (id, auto_resume, force_transcode))
+ log.debug("playFile id({0}) resume({1}) force_transcode({2})", id, auto_resume, force_transcode)
settings = xbmcaddon.Addon('plugin.video.embycon')
addon_path = settings.getAddonInfo('path')
+ force_auto_resume = settings.getSetting('forceAutoResume') == 'true'
jump_back_amount = int(settings.getSetting("jump_back_amount"))
- server = downloadUtils.getServer()
+ server = download_utils.getServer()
- jsonData = downloadUtils.downloadUrl("{server}/emby/Users/{userid}/Items/" + id + "?format=json",
- suppress=False, popup=1)
- result = json.loads(jsonData)
- log.debug("Playfile item info: " + str(result))
+ url = "{server}/emby/Users/{userid}/Items/" + id + "?format=json"
+ data_manager = DataManager()
+ result = data_manager.GetContent(url)
+ log.debug("Playfile item info: {0}", result)
+
+ if result is None:
+ log.debug("Playfile item was None, so can not play!")
+ return
+
+ # if this is a season, tv show or album then play all items
+ if result.get("Type") == "Season" or result.get("Type") == "MusicAlbum":
+ return playAllFiles(id, monitor)
+
+ # select the media source to use
+ media_sources = result.get('MediaSources')
+ selected_media_source = None
+
+ if media_sources is None or len(media_sources) == 0:
+ log.debug("Play Failed! There is no MediaSources data!")
+ return
+
+ elif len(media_sources) == 1:
+ selected_media_source = media_sources[0]
+
+ elif media_source_id != "":
+ for source in media_sources:
+ if source.get("Id", "na") == media_source_id:
+ selected_media_source = source
+ break
+
+ elif len(media_sources) > 1:
+ sourceNames = []
+ for source in media_sources:
+ sourceNames.append(source.get("Name", "na"))
+
+ dialog = xbmcgui.Dialog()
+ resp = dialog.select(i18n('select_source'), sourceNames)
+ if resp > -1:
+ selected_media_source = media_sources[resp]
+ else:
+ log.debug("Play Aborted, user did not select a MediaSource")
+ return
+
+ if selected_media_source is None:
+ log.debug("Play Aborted, MediaSource was None")
+ return
seekTime = 0
auto_resume = int(auto_resume)
+ # process user data for resume points
if auto_resume != -1:
seekTime = (auto_resume / 1000) / 10000
+
+ elif force_auto_resume:
+ userData = result.get("UserData")
+ reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000
+ seekTime = reasonableTicks / 10000
+
else:
userData = result.get("UserData")
if userData.get("PlaybackPositionTicks") != 0:
@@ -58,13 +193,13 @@ def playFile(play_info):
resumeDialog.doModal()
resume_result = resumeDialog.getResumeAction()
del resumeDialog
- log.debug("Resume Dialog Result: " + str(resume_result))
+ log.debug("Resume Dialog Result: {0}", resume_result)
# check system settings for play action
# if prompt is set ask to set it to auto resume
params = {"setting": "myvideos.selectaction"}
setting_result = json_rpc('Settings.getSettingValue').execute(params)
- log.debug("Current Setting (myvideos.selectaction): %s" % setting_result)
+ log.debug("Current Setting (myvideos.selectaction): {0}", setting_result)
current_value = setting_result.get("result", None)
if current_value is not None:
current_value = current_value.get("value", -1)
@@ -73,7 +208,7 @@ def playFile(play_info):
if return_value:
params = {"setting": "myvideos.selectaction", "value": 2}
json_rpc_result = json_rpc('Settings.setSettingValue').execute(params)
- log.debug("Save Setting (myvideos.selectaction): %s" % json_rpc_result)
+ log.debug("Save Setting (myvideos.selectaction): {0}", json_rpc_result)
if resume_result == 1:
seekTime = 0
@@ -81,20 +216,21 @@ def playFile(play_info):
return
listitem_props = []
+ playback_type = "0"
playurl = None
play_session_id = id_generator()
- log.debug("play_session_id: %s" % play_session_id)
+ log.debug("play_session_id: {0}", play_session_id)
# check if strm file, path will contain contain strm contents
- if result.get('MediaSources'):
- source = result['MediaSources'][0]
- if source.get('Container') == 'strm':
- playurl, listitem_props = PlayUtils().getStrmDetails(result)
+ if selected_media_source.get('Container') == 'strm':
+ playurl, listitem_props = PlayUtils().getStrmDetails(selected_media_source)
+ if playurl is None:
+ return
if not playurl:
- playurl, playback_type = PlayUtils().getPlayUrl(id, result, force_transcode, play_session_id)
+ playurl, playback_type = PlayUtils().getPlayUrl(id, selected_media_source, force_transcode, play_session_id)
- log.debug("Play URL: " + playurl + " ListItem Properties: " + str(listitem_props))
+ log.debug("Play URL: {0} ListItem Properties: {1}", playurl, listitem_props)
playback_type_string = "DirectPlay"
if playback_type == "2":
@@ -102,37 +238,31 @@ def playFile(play_info):
elif playback_type == "1":
playback_type_string = "DirectStream"
- home_window = HomeWindow()
- home_window.setProperty("PlaybackType_" + id, playback_type_string)
- home_window.setProperty("PlaySessionId_" + id, play_session_id)
-
# add the playback type into the overview
if result.get("Overview", None) is not None:
result["Overview"] = playback_type_string + "\n" + result.get("Overview")
else:
result["Overview"] = playback_type_string
+ # add title decoration is needed
item_title = result.get("Name", i18n('missing_title'))
- add_episode_number = settings.getSetting('addEpisodeNumber') == 'true'
- if result.get("Type") == "Episode" and add_episode_number:
- episode_num = result.get("IndexNumber")
- if episode_num is not None:
- if episode_num < 10:
- episode_num = "0" + str(episode_num)
- else:
- episode_num = str(episode_num)
- else:
- episode_num = ""
- item_title = episode_num + " - " + item_title
-
list_item = xbmcgui.ListItem(label=item_title)
- # if transcoding then prompt for audio and subtitle
- if playback_type == "2":
- playurl = audioSubsPref(playurl, list_item, result)
- log.debug("New playurl for transcoding : " + playurl)
- elif playback_type == "1":
- externalSubs(result, list_item)
+ if playback_type == "2": # if transcoding then prompt for audio and subtitle
+ playurl = audioSubsPref(playurl, list_item, selected_media_source, id, use_default)
+ log.debug("New playurl for transcoding: {0}", playurl)
+
+ elif playback_type == "1": # for direct stream add any streamable subtitles
+ externalSubs(selected_media_source, list_item, id)
+
+ # add playurl and data to the monitor
+ data = {}
+ data["item_id"] = id
+ data["playback_type"] = playback_type_string
+ data["play_session_id"] = play_session_id
+ data["play_action_type"] = "play"
+ monitor.played_information[playurl] = data
+ log.debug("Add to played_information: {0}", monitor.played_information)
list_item.setPath(playurl)
list_item = setListItemProps(id, list_item, result, server, listitem_props, item_title)
@@ -142,6 +272,8 @@ def playFile(play_info):
playlist.add(playurl, list_item)
xbmc.Player().play(playlist)
+ send_next_episode_details(result)
+
if seekTime == 0:
return
@@ -156,18 +288,74 @@ def playFile(play_info):
seekTime = seekTime - jump_back_amount
- while xbmc.Player().getTime() < (seekTime - 5):
+ target_seek = (seekTime - 5)
+ current_position = 0
+ while current_position < target_seek:
# xbmc.Player().pause()
xbmc.sleep(100)
xbmc.Player().seekTime(seekTime)
xbmc.sleep(100)
# xbmc.Player().play()
+ current_position = xbmc.Player().getTime()
+ log.debug("Playback_Start_Seek target:{0} current:{1}", target_seek, current_position)
+
+
+def send_next_episode_details(item):
+
+ next_episode = get_next_episode(item)
+
+ if next_episode is None:
+ log.debug("No next episode")
+ return
+
+ gui_options = {}
+ gui_options["server"] = download_utils.getServer()
+
+ gui_options["name_format"] = None
+ gui_options["name_format_type"] = ""
+
+ item_details = extract_item_info(item, gui_options)
+ next_item_details = extract_item_info(next_episode, gui_options)
+
+ current_item = {}
+ current_item["id"] = item_details.id
+ current_item["title"] = item_details.name
+ current_item["image"] = item_details.art.get('tvshow.poster', '')
+ current_item["thumb"] = item_details.art.get('thumb', '')
+ current_item["fanartimage"] = item_details.art.get('tvshow.fanart', '')
+ current_item["overview"] = item_details.plot
+ current_item["tvshowtitle"] = item_details.series_name
+ current_item["playcount"] = item_details.play_count
+ current_item["season"] = item_details.season_number
+ current_item["episode"] = item_details.episode_number
+ current_item["rating"] = item_details.critic_rating
+ current_item["year"] = item_details.year
+
+ next_item = {}
+ next_item["id"] = next_item_details.id
+ next_item["title"] = next_item_details.name
+ next_item["image"] = next_item_details.art.get('tvshow.poster', '')
+ next_item["thumb"] = next_item_details.art.get('thumb', '')
+ next_item["fanartimage"] = next_item_details.art.get('tvshow.fanart', '')
+ next_item["overview"] = next_item_details.plot
+ next_item["tvshowtitle"] = next_item_details.series_name
+ next_item["playcount"] = next_item_details.play_count
+ next_item["season"] = next_item_details.season_number
+ next_item["episode"] = next_item_details.episode_number
+ next_item["rating"] = next_item_details.critic_rating
+ next_item["year"] = next_item_details.year
+
+ next_info = {
+ "current_item": current_item,
+ "next_item": next_item
+ }
+
+ log.debug("send_next_episode_details: {0}", next_info)
+ send_event_notification("embycon_next_episode", next_info)
+
def setListItemProps(id, listItem, result, server, extra_props, title):
# set up item and item info
- thumbID = id
- eppNum = -1
- seasonNum = -1
art = getArt(result, server=server)
listItem.setIconImage(art['thumb']) # back compat
@@ -175,34 +363,69 @@ def setListItemProps(id, listItem, result, server, extra_props, title):
listItem.setProperty('discart', art['discart']) # not avail to setArt
listItem.setArt(art)
- listItem.setProperty('IsPlayable', 'true')
+ listItem.setProperty('IsPlayable', 'false')
listItem.setProperty('IsFolder', 'false')
listItem.setProperty('id', result.get("Id"))
for prop in extra_props:
listItem.setProperty(prop[0], prop[1])
- # play info
- details = {
- 'title': title,
- 'plot': result.get("Overview")
- }
+ item_type = result.get("Type", "").lower()
+ mediatype = 'video'
- if (eppNum > -1):
- details["episode"] = str(eppNum)
+ if item_type == 'movie' or item_type == 'boxset':
+ mediatype = 'movie'
+ elif item_type == 'series':
+ mediatype = 'tvshow'
+ elif item_type == 'season':
+ mediatype = 'season'
+ elif item_type == 'episode':
+ mediatype = 'episode'
+ elif item_type == 'audio':
+ mediatype = 'song'
- if (seasonNum > -1):
- details["season"] = str(seasonNum)
+ if item_type == "audio":
- listItem.setInfo("Video", infoLabels=details)
+ details = {
+ 'title': title,
+ 'mediatype': mediatype
+ }
+ listItem.setInfo("Music", infoLabels=details)
+
+ else:
+
+ details = {
+ 'title': title,
+ 'plot': result.get("Overview"),
+ 'mediatype': mediatype
+ }
+
+ tv_show_name = result.get("SeriesName")
+ if tv_show_name is not None:
+ details['tvshowtitle'] = tv_show_name
+
+ if item_type == "episode":
+ episode_number = result.get("IndexNumber", -1)
+ details["episode"] = str(episode_number)
+ season_number = result.get("ParentIndexNumber", -1)
+ details["season"] = str(season_number)
+ elif item_type == "season":
+ season_number = result.get("IndexNumber", -1)
+ details["season"] = str(season_number)
+
+ details["plotoutline"] = "emby_id:" + id
+ #listItem.setUniqueIDs({'emby': id})
+
+ listItem.setInfo("Video", infoLabels=details)
return listItem
+
# For transcoding only
# Present the list of audio and subtitles to select from
# for external streamable subtitles add the URL to the Kodi item and let Kodi handle it
# else ask for the subtitles to be burnt in when transcoding
-def audioSubsPref(url, list_item, emby_item):
+def audioSubsPref(url, list_item, media_source, item_id, use_default):
dialog = xbmcgui.Dialog()
audioStreamsList = {}
@@ -214,14 +437,12 @@ def audioSubsPref(url, list_item, emby_item):
selectAudioIndex = ""
selectSubsIndex = ""
playurlprefs = "%s" % url
+ default_audio = media_source.get('DefaultAudioStreamIndex', 1)
+ default_sub = media_source.get('DefaultSubtitleStreamIndex', "")
- try:
- mediasources = emby_item['MediaSources'][0]
- mediastreams = mediasources['MediaStreams']
- except (TypeError, KeyError, IndexError):
- return
+ media_streams = media_source['MediaStreams']
- for stream in mediastreams:
+ for stream in media_streams:
# Since Emby returns all possible tracks together, have to sort them.
index = stream['Index']
@@ -258,7 +479,10 @@ def audioSubsPref(url, list_item, emby_item):
subtitleStreamsList[track] = index
subtitleStreams.append(track)
- if len(audioStreams) > 1:
+ if use_default:
+ playurlprefs += "&AudioStreamIndex=%s" % default_audio
+
+ elif len(audioStreams) > 1:
resp = dialog.select(i18n('select_audio_stream'), audioStreams)
if resp > -1:
# User selected audio
@@ -266,35 +490,38 @@ def audioSubsPref(url, list_item, emby_item):
selectAudioIndex = audioStreamsList[selected]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
else: # User backed out of selection
- playurlprefs += "&AudioStreamIndex=%s" % mediasources['DefaultAudioStreamIndex']
- else: # There's only one audiotrack.
+ playurlprefs += "&AudioStreamIndex=%s" % default_audio
+
+ elif len(audioStreams) == 1: # There's only one audiotrack.
selectAudioIndex = audioStreamsList[audioStreams[0]]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
if len(subtitleStreams) > 1:
- resp = dialog.select(i18n('select_subtitle'), subtitleStreams)
- if resp == 0:
- # User selected no subtitles
- pass
- elif resp > -1:
- # User selected subtitles
- selected = subtitleStreams[resp]
- selectSubsIndex = subtitleStreamsList[selected]
-
- # Load subtitles in the listitem if downloadable
- if selectSubsIndex in downloadableStreams:
-
- itemid = emby_item['Id']
- url = [("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
- % (downloadUtils.getServer(), itemid, itemid, selectSubsIndex))]
- log.debug("Streaming subtitles url: %s %s" % (selectSubsIndex, url))
- list_item.setSubtitles(url)
- else:
- # Burn subtitles
- playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
+ if use_default:
+ playurlprefs += "&SubtitleStreamIndex=%s" % default_sub
- else: # User backed out of selection
- playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "")
+ else:
+ resp = dialog.select(i18n('select_subtitle'), subtitleStreams)
+ if resp == 0:
+ # User selected no subtitles
+ pass
+ elif resp > -1:
+ # User selected subtitles
+ selected = subtitleStreams[resp]
+ selectSubsIndex = subtitleStreamsList[selected]
+
+ # Load subtitles in the listitem if downloadable
+ if selectSubsIndex in downloadableStreams:
+ url = [("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
+ % (download_utils.getServer(), item_id, item_id, selectSubsIndex))]
+ log.debug("Streaming subtitles url: {0} {1}", selectSubsIndex, url)
+ list_item.setSubtitles(url)
+ else:
+ # Burn subtitles
+ playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
+
+ else: # User backed out of selection
+ playurlprefs += "&SubtitleStreamIndex=%s" % default_sub
# Get number of channels for selected audio track
audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)
@@ -305,17 +532,14 @@ def audioSubsPref(url, list_item, emby_item):
return playurlprefs
+
# direct stream, set any available subtitle streams
-def externalSubs(emby_item, list_item):
+def externalSubs(media_source, list_item, item_id):
externalsubs = []
- itemid = emby_item['Id']
- try:
- mediastreams = emby_item['MediaSources'][0]['MediaStreams']
- except (TypeError, KeyError, IndexError):
- return
+ media_streams = media_source['MediaStreams']
- for stream in mediastreams:
+ for stream in media_streams:
if (stream['Type'] == "Subtitle"
and stream['IsExternal']
@@ -324,8 +548,273 @@ def externalSubs(emby_item, list_item):
index = stream['Index']
url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.%s"
- % (downloadUtils.getServer(), itemid, itemid, index, stream['Codec']))
+ % (download_utils.getServer(), item_id, item_id, index, stream['Codec']))
externalsubs.append(url)
- list_item.setSubtitles(externalsubs) \ No newline at end of file
+ list_item.setSubtitles(externalsubs)
+
+
+def sendProgress(monitor):
+ playing_file = xbmc.Player().getPlayingFile()
+
+ '''
+ video_tag_info = xbmc.Player().getVideoInfoTag()
+ plotoutline = video_tag_info.getPlotOutline()
+ log.debug("Player().getVideoInfoTag().getPlotOutline(): {0}", plotoutline)
+ emby_id = None
+ if plotoutline is not None and plotoutline.startswith("emby_id:"):
+ emby_id = plotoutline[8:]
+ log.debug("EmbyId: {0}", emby_id)
+ '''
+
+ play_data = monitor.played_information.get(playing_file)
+
+ if play_data is None:
+ return
+
+ log.debug("Sending Progress Update")
+
+ play_time = xbmc.Player().getTime()
+ play_data["currentPossition"] = play_time
+ play_data["currently_playing"] = True
+
+ item_id = play_data.get("item_id")
+ if item_id is None:
+ return
+
+ ticks = int(play_time * 10000000)
+ paused = play_data.get("paused", False)
+ playback_type = play_data.get("playback_type")
+ play_session_id = play_data.get("play_session_id")
+
+ postdata = {
+ 'QueueableMediaTypes': "Video",
+ 'CanSeek': True,
+ 'ItemId': item_id,
+ 'MediaSourceId': item_id,
+ 'PositionTicks': ticks,
+ 'IsPaused': paused,
+ 'IsMuted': False,
+ 'PlayMethod': playback_type,
+ 'PlaySessionId': play_session_id
+ }
+
+ log.debug("Sending POST progress started: {0}", postdata)
+
+ url = "{server}/emby/Sessions/Playing/Progress"
+ download_utils.downloadUrl(url, postBody=postdata, method="POST")
+
+
+@catch_except()
+def promptForStopActions(item_id, current_possition):
+
+ settings = xbmcaddon.Addon(id='plugin.video.embycon')
+
+ prompt_next_percentage = int(settings.getSetting('promptPlayNextEpisodePercentage'))
+ play_prompt = settings.getSetting('promptPlayNextEpisodePercentage_prompt') == "true"
+ prompt_delete_episode_percentage = int(settings.getSetting('promptDeleteEpisodePercentage'))
+ prompt_delete_movie_percentage = int(settings.getSetting('promptDeleteMoviePercentage'))
+
+ # everything is off so return
+ if (prompt_next_percentage == 100 and
+ prompt_delete_episode_percentage == 100 and
+ prompt_delete_movie_percentage == 100):
+ return
+
+ jsonData = download_utils.downloadUrl("{server}/emby/Users/{userid}/Items/" + item_id + "?format=json")
+ result = json.loads(jsonData)
+
+ if result is None:
+ log.debug("promptForStopActions failed! no result from server.")
+ return
+
+ prompt_to_delete = False
+ runtime = result.get("RunTimeTicks", 0)
+
+ # if no runtime we cant calculate perceantge so just return
+ if runtime == 0:
+ log.debug("No runtime so returing")
+ return
+
+ # item percentage complete
+ percenatge_complete = int(((current_possition * 10000000) / runtime) * 100)
+ log.debug("Episode Percentage Complete: {0}", percenatge_complete)
+
+ if (prompt_delete_episode_percentage < 100 and
+ result.get("Type", "na") == "Episode" and
+ percenatge_complete > prompt_delete_episode_percentage):
+ prompt_to_delete = True
+
+ if (prompt_delete_movie_percentage < 100 and
+ result.get("Type", "na") == "Movie" and
+ percenatge_complete > prompt_delete_movie_percentage):
+ prompt_to_delete = True
+
+ if prompt_to_delete:
+ log.debug("Prompting for delete")
+ resp = xbmcgui.Dialog().yesno(i18n('confirm_file_delete'), i18n('file_delete_confirm'), autoclose=10000)
+ if resp:
+ log.debug("Deleting item: {0}", item_id)
+ url = "{server}/emby/Items/%s?format=json" % item_id
+ download_utils.downloadUrl(url, method="DELETE")
+ xbmc.executebuiltin("Container.Refresh")
+
+ # prompt for next episode
+ if (prompt_next_percentage < 100 and
+ result.get("Type", "na") == "Episode" and
+ percenatge_complete > prompt_next_percentage):
+
+ next_episode = get_next_episode(result)
+
+ if next_episode is not None:
+ resp = True
+ index = next_episode.get("IndexNumber", -1)
+ if play_prompt:
+ next_epp_name = "%02d - %s" % (index, next_episode.get("Name", "n/a"))
+ resp = xbmcgui.Dialog().yesno(i18n("play_next_title"), i18n("play_next_question"), next_epp_name, autoclose=10000)
+
+ if resp:
+ next_item_id = next_episode.get("Id")
+ log.debug("Playing Next Episode: {0}", next_item_id)
+
+ play_info = {}
+ play_info["item_id"] = next_item_id
+ play_info["auto_resume"] = "-1"
+ play_info["force_transcode"] = False
+ send_event_notification("embycon_play_action", play_info)
+
+
+@catch_except()
+def stopAll(played_information):
+ if len(played_information) == 0:
+ return
+
+ log.debug("played_information: {0}", played_information)
+
+ for item_url in played_information:
+ data = played_information.get(item_url)
+ if data.get("currently_playing", False) is True:
+ log.debug("item_url: {0}", item_url)
+ log.debug("item_data: {0}", data)
+
+ current_possition = data.get("currentPossition", 0)
+ emby_item_id = data.get("item_id")
+
+ if emby_item_id is not None and len(emby_item_id) != 0 and emby_item_id != "None":
+ log.debug("Playback Stopped at: {0}", current_possition)
+
+ url = "{server}/emby/Sessions/Playing/Stopped"
+ postdata = {
+ 'ItemId': emby_item_id,
+ 'MediaSourceId': emby_item_id,
+ 'PositionTicks': int(current_possition * 10000000)
+ }
+ download_utils.downloadUrl(url, postBody=postdata, method="POST")
+ data["currently_playing"] = False
+
+ if data.get("play_action_type", "") == "play":
+ promptForStopActions(emby_item_id, current_possition)
+
+
+class Service(xbmc.Player):
+
+ def __init__(self, *args):
+ log.debug("Starting monitor service: {0}", args)
+ self.played_information = {}
+
+ def onPlayBackStarted(self):
+ # Will be called when xbmc starts playing a file
+ stopAll(self.played_information)
+
+ current_playing_file = xbmc.Player().getPlayingFile()
+ log.debug("onPlayBackStarted: {0}", current_playing_file)
+ log.debug("played_information: {0}", self.played_information)
+
+ if current_playing_file not in self.played_information:
+ log.debug("This file was not started by EmbyCon")
+ return
+
+ data = self.played_information[current_playing_file]
+ data["paused"] = False
+ data["currently_playing"] = True
+
+ emby_item_id = data["item_id"]
+ playback_type = data["playback_type"]
+ play_session_id = data["play_session_id"]
+
+ # if we could not find the ID of the current item then return
+ if emby_item_id is None or len(emby_item_id) == 0:
+ return
+
+ log.debug("Sending Playback Started")
+ postdata = {
+ 'QueueableMediaTypes': "Video",
+ 'CanSeek': True,
+ 'ItemId': emby_item_id,
+ 'MediaSourceId': emby_item_id,
+ 'PlayMethod': playback_type,
+ 'PlaySessionId': play_session_id
+ }
+
+ log.debug("Sending POST play started: {0}", postdata)
+
+ url = "{server}/emby/Sessions/Playing"
+ download_utils.downloadUrl(url, postBody=postdata, method="POST")
+
+ def onPlayBackEnded(self):
+ # Will be called when kodi stops playing a file
+ log.debug("EmbyCon Service -> onPlayBackEnded")
+ stopAll(self.played_information)
+
+ def onPlayBackStopped(self):
+ # Will be called when user stops kodi playing a file
+ log.debug("onPlayBackStopped")
+ stopAll(self.played_information)
+
+ def onPlayBackPaused(self):
+ # Will be called when kodi pauses the video
+ log.debug("onPlayBackPaused")
+ current_file = xbmc.Player().getPlayingFile()
+ play_data = self.played_information.get(current_file)
+
+ if play_data is not None:
+ play_data['paused'] = True
+ sendProgress(self)
+
+ def onPlayBackResumed(self):
+ # Will be called when kodi resumes the video
+ log.debug("onPlayBackResumed")
+ current_file = xbmc.Player().getPlayingFile()
+ play_data = self.played_information.get(current_file)
+
+ if play_data is not None:
+ play_data['paused'] = False
+ sendProgress(self)
+
+ def onPlayBackSeek(self, time, seekOffset):
+ # Will be called when kodi seeks in video
+ log.debug("onPlayBackSeek")
+ sendProgress(self)
+
+
+class PlaybackService(xbmc.Monitor):
+
+ def __init__(self, monitor):
+ self.monitor = monitor
+
+ def onNotification(self, sender, method, data):
+ log.debug("PlaybackService:onNotification:{0}:{1}:{2}", sender, method, data)
+ if sender[-7:] != '.SIGNAL':
+ return
+
+ signal = method.split('.', 1)[-1]
+ if signal != "embycon_play_action":
+ return
+
+ data_json = json.loads(data)
+ hex_data = data_json[0]
+ log.debug("PlaybackService:onNotification:{0}", hex_data)
+ decoded_data = binascii.unhexlify(hex_data)
+ play_info = json.loads(decoded_data)
+ playFile(play_info, self.monitor)
diff --git a/plugin.video.embycon/resources/lib/server_detect.py b/plugin.video.embycon/resources/lib/server_detect.py
index 392a9df..a3fbf23 100644
--- a/plugin.video.embycon/resources/lib/server_detect.py
+++ b/plugin.video.embycon/resources/lib/server_detect.py
@@ -35,8 +35,8 @@ def getServerDetails():
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
- log.debug("MutliGroup : " + str(MULTI_GROUP))
- log.debug("Sending UDP Data : " + MESSAGE)
+ log.debug("MutliGroup: {0}", MULTI_GROUP)
+ log.debug("Sending UDP Data: {0}", MESSAGE)
sock.sendto(MESSAGE, MULTI_GROUP)
servers = []
@@ -46,10 +46,10 @@ def getServerDetails():
data, addr = sock.recvfrom(1024) # buffer size
servers.append(json.loads(data))
except Exception as e:
- log.error("Read UPD responce: %s" % e)
+ log.error("Read UPD responce: {0}", e)
# break
- log.debug("Found Servers: %s" % servers)
+ log.debug("Found Servers: {0}", servers)
return servers
@@ -83,14 +83,14 @@ def checkServer(force=False, change_user=False, notify=False):
return
serverUrl = serverInfo[return_index]["Address"]
- log.debug("Selected server: " + serverUrl)
+ log.debug("Selected server: {0}", serverUrl)
# parse the url
url_bits = urlparse(serverUrl)
server_address = url_bits.hostname
server_port = str(url_bits.port)
server_protocol = url_bits.scheme
- log.debug("Detected server info " + server_protocol + " - " + server_address + " - " + server_port)
+ log.debug("Detected server info {0} - {1} - {2}", server_protocol, server_address, server_port)
# save the server info
settings.setSetting("port", server_port)
@@ -107,6 +107,7 @@ def checkServer(force=False, change_user=False, notify=False):
# we need to change the user
current_username = settings.getSetting("username")
+ current_username = unicode(current_username, "utf-8")
# if asked or we have no current user then show user selection screen
if change_user or len(current_username) == 0:
@@ -114,69 +115,71 @@ def checkServer(force=False, change_user=False, notify=False):
log.debug("Getting user list")
jsonData = downloadUtils.downloadUrl(serverUrl + "/emby/Users/Public?format=json", authenticate=False)
- log.debug("jsonData : " + str(jsonData))
+ log.debug("jsonData: {0}", jsonData)
result = json.loads(jsonData)
if result is None:
- result = []
-
- names = []
- user_list = []
- secured = []
- for user in result:
- config = user.get("Configuration")
- if (config != None):
- if (config.get("IsHidden") is None) or (config.get("IsHidden") is False):
- name = user.get("Name")
- user_list.append(name)
- if (user.get("HasPassword") is True):
- secured.append(True)
- name = i18n('username_secured') % name
- else:
- secured.append(False)
- names.append(name)
-
- if (len(current_username) > 0) and (not any(n == current_username for n in user_list)):
- names.insert(0, i18n('username_userdefined') % current_username)
- user_list.insert(0, current_username)
+ xbmcgui.Dialog().ok(i18n('error'),
+ i18n('unable_connect_server'),
+ i18n('address:') + serverUrl)
+ else:
+ names = []
+ user_list = []
+ secured = []
+ for user in result:
+ config = user.get("Configuration")
+ if (config != None):
+ if (config.get("IsHidden") is None) or (config.get("IsHidden") is False):
+ name = user.get("Name")
+ user_list.append(name)
+ if (user.get("HasPassword") is True):
+ secured.append(True)
+ name = i18n('username_secured') % name
+ else:
+ secured.append(False)
+ names.append(name)
+
+ if (len(current_username) > 0) and (not any(n == current_username for n in user_list)):
+ names.insert(0, i18n('username_userdefined') % current_username)
+ user_list.insert(0, current_username)
+ secured.insert(0, True)
+
+ names.insert(0, i18n('username_userinput'))
+ user_list.insert(0, '')
secured.insert(0, True)
+ log.debug("User List: {0}", names)
+ log.debug("User List: {0}", user_list)
- names.insert(0, i18n('username_userinput'))
- user_list.insert(0, '')
- secured.insert(0, True)
- log.debug("User List : " + str(names))
- log.debug("User List : " + str(user_list))
-
- return_value = xbmcgui.Dialog().select(i18n('select_user'), names)
-
- if (return_value > -1):
- log.debug("Selected User Index : " + str(return_value))
- if return_value == 0:
- kb = xbmc.Keyboard()
- kb.setHeading(i18n('username:'))
- kb.doModal()
- if kb.isConfirmed():
- selected_user = kb.getText()
- else:
- selected_user = None
- else:
- selected_user = user_list[return_value]
+ return_value = xbmcgui.Dialog().select(i18n('select_user'), names)
- log.debug("Selected User Name : " + str(selected_user))
-
- if selected_user:
- # we have a user so save it
- log.debug("Saving Username : " + selected_user)
- settings.setSetting("username", selected_user)
- if secured[return_value] is True:
+ if (return_value > -1):
+ log.debug("Selected User Index: {0}", return_value)
+ if return_value == 0:
kb = xbmc.Keyboard()
- kb.setHeading(i18n('password:'))
- kb.setHiddenInput(True)
+ kb.setHeading(i18n('username:'))
kb.doModal()
if kb.isConfirmed():
- log.debug("Saving Password for Username : " + selected_user)
- settings.setSetting('password', kb.getText())
+ selected_user = kb.getText()
+ else:
+ selected_user = None
else:
- settings.setSetting('password', '')
+ selected_user = user_list[return_value]
+
+ log.debug("Selected User Name: {0}", selected_user)
+
+ if selected_user:
+ # we have a user so save it
+ log.debug("Saving Username: {0}", selected_user)
+ settings.setSetting("username", selected_user)
+ if secured[return_value] is True:
+ kb = xbmc.Keyboard()
+ kb.setHeading(i18n('password:'))
+ kb.setHiddenInput(True)
+ kb.doModal()
+ if kb.isConfirmed():
+ log.debug("Saving Password for Username: {0}", selected_user)
+ settings.setSetting('password', kb.getText())
+ else:
+ settings.setSetting('password', '')
home_window = HomeWindow()
home_window.clearProperty("userid")
diff --git a/plugin.video.embycon/resources/lib/server_sessions.py b/plugin.video.embycon/resources/lib/server_sessions.py
index 85e7a9d..feb9efd 100644
--- a/plugin.video.embycon/resources/lib/server_sessions.py
+++ b/plugin.video.embycon/resources/lib/server_sessions.py
@@ -35,28 +35,27 @@ def showServerSessions():
if play_state is not None:
runtime = 0
media_id = play_state.get("MediaSourceId", None)
- log.debug("Media ID " + str(media_id))
+ log.debug("Media ID: {0}", media_id)
if media_id is not None:
- jsonData = downloadUtils.downloadUrl("{server}/emby/Users/{userid}/Items/" +
- media_id + "?format=json",
- suppress=False, popup=1)
+ url = "{server}/emby/Users/{userid}/Items/" + media_id + "?format=json"
+ jsonData = downloadUtils.downloadUrl(url)
media_info = json.loads(jsonData)
- log.debug("Media Info " + str(media_info))
+ log.debug("Media Info: {0}", media_info)
runtime = media_info.get("RunTimeTicks", 0)
- log.debug("Media Runtime " + str(runtime))
+ log.debug("Media Runtime: {0}", runtime)
position_ticks = play_state.get("PositionTicks", 0)
- log.debug("Media PositionTicks " + str(position_ticks))
+ log.debug("Media PositionTicks: {0}", position_ticks)
if position_ticks > 0 and runtime > 0:
percenatge_played = (position_ticks / float(runtime)) * 100.0
percenatge_played = int(percenatge_played)
now_playing = session.get("NowPlayingItem", None)
- log.debug("NOW_PLAYING: " + str(now_playing))
+ log.debug("NOW_PLAYING: {0}", now_playing)
if now_playing is not None:
session_info += " (" + now_playing.get("Name", "na") + " " + str(percenatge_played) + "%)"
- log.debug(session_info)
+ log.debug("session_info: {0}", session_info)
list_item = xbmcgui.ListItem(label=session_info)
item_tuple = ("", list_item, False)
list_items.append(item_tuple)
diff --git a/plugin.video.embycon/resources/lib/simple_logging.py b/plugin.video.embycon/resources/lib/simple_logging.py
index 63ce6ac..05230d9 100644
--- a/plugin.video.embycon/resources/lib/simple_logging.py
+++ b/plugin.video.embycon/resources/lib/simple_logging.py
@@ -19,23 +19,30 @@ class SimpleLogging():
current_value = setting_result.get("result", None)
if current_value is not None:
self.enable_logging = current_value.get("value", False)
- #xbmc.log("LOGGING_ENABLED %s: %s" % (self.name, str(self.enable_logging)), level=xbmc.LOGDEBUG)
+ xbmc.log("LOGGING_ENABLED %s : %s" % (self.name, str(self.enable_logging)), level=xbmc.LOGDEBUG)
def __str__(self):
return "LoggingEnabled: " + str(self.enable_logging)
- def error(self, msg):
- try:
- xbmc.log(self.format(msg, "ERROR"), level=xbmc.LOGERROR)
- except UnicodeEncodeError:
- xbmc.log(self.format(msg, "ERROR").encode('utf-8'), level=xbmc.LOGERROR)
-
- def debug(self, msg):
- if (self.enable_logging):
- try:
- xbmc.log(self.format(msg, "DEBUG"), level=xbmc.LOGDEBUG)
- except UnicodeEncodeError:
- xbmc.log(self.format(msg, "DEBUG").encode('utf-8'), level=xbmc.LOGDEBUG)
-
- def format(self, msg, levelValue):
- return self.name + "(" + str(levelValue) + ") -> " + msg
+ def error(self, fmt, *args, **kwargs):
+ new_args = []
+ # convert any unicode to utf-8 strings
+ for arg in args:
+ if isinstance(arg, unicode):
+ new_args.append(arg.encode("utf-8"))
+ else:
+ new_args.append(arg)
+ log_line = self.name + " (ERROR) -> " + fmt.format(*new_args)
+ xbmc.log(log_line, level=xbmc.LOGDEBUG)
+
+ def debug(self, fmt, *args, **kwargs):
+ if self.enable_logging:
+ new_args = []
+ # convert any unicode to utf-8 strings
+ for arg in args:
+ if isinstance(arg, unicode):
+ new_args.append(arg.encode("utf-8"))
+ else:
+ new_args.append(arg)
+ log_line = self.name + " (DEBUG) -> " + fmt.format(*new_args)
+ xbmc.log(log_line, level=xbmc.LOGDEBUG)
diff --git a/plugin.video.embycon/resources/lib/trakttokodi.py b/plugin.video.embycon/resources/lib/trakttokodi.py
new file mode 100644
index 0000000..3e27740
--- /dev/null
+++ b/plugin.video.embycon/resources/lib/trakttokodi.py
@@ -0,0 +1,248 @@
+# Gnu General Public License - see LICENSE.TXT
+
+import urllib
+import encodings
+
+import xbmc
+import xbmcgui
+
+from simple_logging import SimpleLogging
+from datamanager import DataManager
+
+from translation import i18n
+
+log = SimpleLogging(__name__)
+dataManager = DataManager()
+
+details_string = 'EpisodeCount,SeasonCount,Path,Etag,MediaStreams'
+icon = xbmc.translatePath('special://home/addons/plugin.video.embycon/icon.png')
+
+
+def not_found(content_string):
+ xbmcgui.Dialog().notification('EmbyCon', i18n('not_found_') % content_string, icon=icon, sound=False)
+
+
+def playback_starting(content_string):
+ xbmcgui.Dialog().notification('EmbyCon', i18n('playback_starting_') % content_string, icon=icon, sound=False)
+
+
+def search(item_type, query):
+ content_url = ('{server}/emby/Search/Hints?searchTerm=' + query +
+ '&IncludeItemTypes=' + item_type +
+ '&UserId={userid}'
+ '&StartIndex=0' +
+ '&Limit=25' +
+ '&IncludePeople=false&IncludeMedia=true&IncludeGenres=false&IncludeStudios=false&IncludeArtists=false')
+
+ result = dataManager.GetContent(content_url)
+ return result
+
+
+def get_items(video_type, item_id=None, parent_id=None):
+ content_url = None
+ result = dict()
+
+ if video_type == 'season':
+ content_url = ('{server}/emby/Shows/' + item_id +
+ '/Seasons'
+ '?userId={userid}' +
+ '&Fields=' + details_string +
+ '&format=json')
+
+ elif video_type == 'movie' or video_type == 'episode':
+ content_url = ('{server}/emby/Users/{userid}/items' +
+ '?ParentId=' + parent_id +
+ '&IsVirtualUnAired=false' +
+ '&IsMissing=false' +
+ '&Fields=' + details_string +
+ '&format=json')
+
+ if content_url:
+ result = dataManager.GetContent(content_url)
+
+ return result
+
+
+def get_item(item_id):
+ result = dataManager.GetContent('{server}/emby/Users/{userid}/Items/' + item_id + '?Fields=ProviderIds&format=json')
+ return result
+
+
+def get_imdb_id(item_id):
+ item = get_item(item_id)
+ imdb = item.get('ProviderIds', {}).get('Imdb')
+ return imdb
+
+
+def get_season_id(parent_id, season):
+ season_items = get_items('season', parent_id)
+ season_items = season_items.get('Items')
+
+ if season_items is None:
+ season_items = []
+
+ for season_item in season_items:
+ if season_item.get('IndexNumber') == int(season):
+ season_id = season_item.get('Id')
+ return season_id
+
+ return None
+
+
+def get_episode_id(parent_id, episode):
+ episode_items = get_items('episode', parent_id=parent_id)
+ episode_items = episode_items.get('Items')
+
+ if episode_items is None:
+ episode_items = []
+
+ for episode_item in episode_items:
+ if episode_item.get('IndexNumber') == int(episode):
+ episode_id = episode_item.get('Id')
+ return episode_id
+
+ return None
+
+
+def get_match(item_type, title, year, imdb_id):
+ query = urllib.quote(title)
+
+ results = search(item_type, query=query)
+ results = results.get('SearchHints')
+ if results is None:
+ results = []
+ log.debug('SearchHints jsonData: {0}', results)
+
+ potential_matches = []
+
+ for item in results:
+ name = item.get('Name')
+ production_year = item.get('ProductionYear')
+ if (name == title and int(year) == production_year) or (int(year) == production_year):
+ potential_matches.append(item)
+
+ log.debug('Potential matches: {0}', potential_matches)
+
+ for item in potential_matches:
+ item_imdb_id = get_imdb_id(item.get('ItemId'))
+ if item_imdb_id == imdb_id:
+ log.debug('Found match: {0}', item)
+ return item
+
+ return None
+
+
+def entry_point(parameters):
+ item_type = None
+ action = parameters.get('action', None)
+ video_type = parameters.get('video_type', None)
+
+ title = urllib.unquote(parameters.get('title', ''))
+
+ year = parameters.get('year', '')
+ episode = parameters.get('episode', '')
+ season = parameters.get('season', '')
+ imdb_id = parameters.get('imdb_id', '')
+
+ if video_type == 'show' or video_type == 'season' or video_type == 'episode':
+ item_type = 'Series'
+ elif video_type == 'movie':
+ item_type = 'Movie'
+
+ if not item_type:
+ return
+
+ match = get_match(item_type, title, year, imdb_id)
+
+ if not match:
+ title_search_word = ''
+ title_words = title.split(' ')
+
+ for word in title_words:
+ if len(word) > len(title_search_word):
+ title_search_word = word
+
+ title_search_word = title_search_word.replace(':', '')
+
+ if title_search_word:
+ match = get_match(item_type, title_search_word, year, imdb_id)
+
+ str_season = str(season)
+ if len(str_season) == 1:
+ str_season = '0' + str_season
+ str_episode = str(episode)
+ if len(str_episode) == 1:
+ str_episode = '0' + str_episode
+
+ if action == 'play':
+ play_item_id = None
+
+ if video_type == 'movie':
+ if match:
+ play_item_id = match.get('ItemId')
+
+ if not play_item_id:
+ not_found('{title} ({year})'.format(title=title, year=year))
+
+ elif video_type == 'episode':
+ if not season or not episode:
+ return
+
+ if match:
+ item_id = match.get('ItemId')
+ season_id = get_season_id(item_id, season)
+
+ if season_id:
+ episode_id = get_episode_id(season_id, episode)
+ if episode_id:
+ play_item_id = episode_id
+
+ if not play_item_id:
+ not_found('{title} ({year}) - S{season}E{episode}'.format(title=title, year=year, season=str_season, episode=str_episode))
+
+ if play_item_id:
+ if video_type == 'episode':
+ playback_starting('{title} ({year}) - S{season}E{episode}'.format(title=title, year=year, season=str_season, episode=str_episode))
+ else:
+ playback_starting('{title} ({year})'.format(title=title, year=year))
+ xbmc.executebuiltin('RunPlugin(plugin://plugin.video.embycon/?mode=PLAY&item_id={item_id})'.format(item_id=play_item_id))
+
+ elif action == 'open':
+ url = media_type = None
+
+ if video_type == 'show':
+ if match:
+ item_id = match.get('ItemId')
+ media_type = 'series'
+ url = ('{server}/emby/Shows/' + item_id +
+ '/Seasons'
+ '?userId={userid}' +
+ '&Fields=' + details_string +
+ '&format=json')
+
+ if not url:
+ not_found('{title} ({year})'.format(title=title, year=year))
+
+ elif video_type == 'season':
+ if not season:
+ return
+
+ if match:
+ item_id = match.get('ItemId')
+ season_id = get_season_id(item_id, season)
+
+ if season_id:
+ media_type = 'episodes'
+
+ url = ('{server}/emby/Users/{userid}/items' +
+ '?ParentId=' + season_id +
+ '&IsVirtualUnAired=false' +
+ '&IsMissing=false' +
+ '&Fields=' + details_string +
+ '&format=json')
+
+ if not url:
+ not_found('{title} ({year}) - S{season}'.format(title=title, year=year, season=str_season))
+
+ if url and media_type:
+ xbmc.executebuiltin('ActivateWindow(Videos, plugin://plugin.video.embycon/?mode=GET_CONTENT&url={url}&media_type={media_type})'.format(url=urllib.quote(url), media_type=media_type))
diff --git a/plugin.video.embycon/resources/lib/translation.py b/plugin.video.embycon/resources/lib/translation.py
index 4df1142..58b0c7e 100644
--- a/plugin.video.embycon/resources/lib/translation.py
+++ b/plugin.video.embycon/resources/lib/translation.py
@@ -10,7 +10,7 @@ def i18n(string_id):
try:
return addon.getLocalizedString(STRINGS[string_id]).encode('utf-8', 'ignore')
except Exception as e:
- log.error('Failed String Lookup: %s (%s)' % (string_id, e))
+ log.error('Failed String Lookup: {0} ({1})', string_id, e)
return string_id
@@ -61,6 +61,7 @@ STRINGS = {
'movies_az': 30252,
'change_user': 30253,
'show_settings': 30254,
+ 'movies_year': 30255,
'movies_all': 30256,
'movies_recently_added': 30257,
'movies_in_progress': 30258,
@@ -111,5 +112,19 @@ STRINGS = {
'caching_textures': 30301,
'existing_textures': 30302,
'missing_textures': 30303,
- 'loaded_textures': 30304
+ 'loaded_textures': 30304,
+ 'not_found_': 30305,
+ 'playback_starting_': 30306,
+ 'play_trailer': 30307,
+ 'select_trailer': 30308,
+ 'select_source': 30309,
+ 'embycon_error': 30311,
+ 'embycon_error_submit': 30312,
+ 'connection_error': 30316,
+ 'play_all': 30317,
+ 'music_all_albums': 30318,
+ 'music_all_artists': 30319,
+ '_all_albums': 30320,
+ '_all_artists': 30321
+
}
diff --git a/plugin.video.embycon/resources/lib/utils.py b/plugin.video.embycon/resources/lib/utils.py
index b91aa59..945ad7e 100644
--- a/plugin.video.embycon/resources/lib/utils.py
+++ b/plugin.video.embycon/resources/lib/utils.py
@@ -12,12 +12,14 @@ import json
import httplib
import base64
import sys
+import binascii
from downloadutils import DownloadUtils
from simple_logging import SimpleLogging
from clientinfo import ClientInformation
from json_rpc import json_rpc
from translation import i18n
+from datamanager import DataManager
# define our global download utils
downloadUtils = DownloadUtils()
@@ -26,19 +28,21 @@ log = SimpleLogging(__name__)
###########################################################################
class PlayUtils():
- def getPlayUrl(self, id, result, force_transcode, play_session_id):
+ def getPlayUrl(self, id, media_source, force_transcode, play_session_id):
log.debug("getPlayUrl")
addonSettings = xbmcaddon.Addon(id='plugin.video.embycon')
playback_type = addonSettings.getSetting("playback_type")
server = downloadUtils.getServer()
- log.debug("playback_type: " + playback_type)
+ log.debug("playback_type: {0}", playback_type)
if force_transcode:
log.debug("playback_type: FORCED_TRANSCODE")
playurl = None
- log.debug("play_session_id: " + play_session_id)
+ log.debug("play_session_id: {0}", play_session_id)
+ media_source_id = media_source.get("Id")
+ log.debug("media_source_id: {0}", media_source_id)
is_h265 = False
- streams = result.get("MediaStreams", [])
+ streams = media_source.get("MediaStreams", [])
for stream in streams:
if stream.get("Type", "") == "Video" and stream.get("Codec", "") in ["hevc", "h265"]:
is_h265 = True
@@ -60,7 +64,7 @@ class PlayUtils():
if playback_type == "2":
playback_bitrate = addonSettings.getSetting("playback_bitrate")
- log.debug("playback_bitrate: " + playback_bitrate)
+ log.debug("playback_bitrate: {0}", playback_bitrate)
playback_max_width = addonSettings.getSetting("playback_max_width")
playback_video_force_8 = addonSettings.getSetting("playback_video_force_8") == "true"
@@ -70,25 +74,28 @@ class PlayUtils():
bitrate = int(playback_bitrate) * 1000
user_token = downloadUtils.authenticate()
- playurl = (
- "%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s&PlaySessionId=%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s"
- % (server, id, id, play_session_id, deviceId, bitrate))
-
- playurl = playurl + "&maxWidth=" + playback_max_width
-
+ playurl = ("%s/emby/Videos/%s/master.m3u8" +
+ "?MediaSourceId=%s" +
+ "&PlaySessionId=%s" +
+ "&VideoCodec=h264" +
+ "&AudioCodec=ac3" +
+ "&MaxAudioChannels=6" +
+ "&deviceId=%s" +
+ "&VideoBitrate=%s" +
+ "&maxWidth=%s")
+ playurl = playurl % (server, id, media_source_id, play_session_id, deviceId, bitrate, playback_max_width)
if playback_video_force_8:
playurl = playurl + "&MaxVideoBitDepth=8"
-
playurl = playurl + "&api_key=" + user_token
# do direct path playback
elif playback_type == "0":
- playurl = result.get("Path")
+ playurl = media_source.get("Path")
# handle DVD structure
- if (result.get("VideoType") == "Dvd"):
+ if (media_source.get("VideoType") == "Dvd"):
playurl = playurl + "/VIDEO_TS/VIDEO_TS.IFO"
- elif (result.get("VideoType") == "BluRay"):
+ elif (media_source.get("VideoType") == "BluRay"):
playurl = playurl + "/BDMV/index.bdmv"
smb_username = addonSettings.getSetting('smbusername')
@@ -104,59 +111,53 @@ class PlayUtils():
# do direct http streaming playback
elif playback_type == "1":
- playurl = "%s/emby/Videos/%s/stream?static=true&PlaySessionId=%s" % (server, id, play_session_id)
+ playurl = ("%s/emby/Videos/%s/stream" +
+ "?static=true" +
+ "&PlaySessionId=%s" +
+ "&MediaSourceId=%s")
+ playurl = playurl % (server, id, play_session_id, media_source_id)
user_token = downloadUtils.authenticate()
playurl = playurl + "&api_key=" + user_token
- log.debug("Playback URL: " + playurl)
- return playurl.encode('utf-8'), playback_type
+ log.debug("Playback URL: {0}", playurl)
+ return playurl, playback_type
- def getStrmDetails(self, result):
+ def getStrmDetails(self, media_source):
playurl = None
listitem_props = []
- source = result['MediaSources'][0]
- contents = source.get('Path').encode('utf-8') # contains contents of strm file with linebreaks
+ contents = media_source.get('Path').encode('utf-8') # contains contents of strm file with linebreaks
line_break = '\r'
if '\r\n' in contents:
- line_break += '\n'
+ line_break = '\r\n'
+ elif '\n' in contents:
+ line_break = '\n'
lines = contents.split(line_break)
for line in lines:
line = line.strip()
+ log.debug("STRM Line: {0}", line)
if line.startswith('#KODIPROP:'):
match = re.search('#KODIPROP:(?P<item_property>[^=]+?)=(?P<property_value>.+)', line)
if match:
- listitem_props.append((match.group('item_property'), match.group('property_value')))
+ item_property = match.group('item_property')
+ property_value = match.group('property_value')
+ log.debug("STRM property found: {0} value: {1}", item_property, property_value)
+ listitem_props.append((item_property, property_value))
+ else:
+ log.debug("STRM #KODIPROP incorrect format")
+ elif line.startswith('#'):
+ # unrecognized, treat as comment
+ log.debug("STRM unrecognized line identifier, ignored")
elif line != '':
playurl = line
+ log.debug("STRM playback url found")
- log.debug("Playback URL: " + playurl + " ListItem Properties: " + str(listitem_props))
+ log.debug("Playback URL: {0} ListItem Properties: {1}", playurl, listitem_props)
return playurl, listitem_props
-def getDetailsString():
-
- addonSettings = xbmcaddon.Addon(id='plugin.video.embycon')
- include_media = addonSettings.getSetting("include_media") == "true"
- include_people = addonSettings.getSetting("include_people") == "true"
- include_overview = addonSettings.getSetting("include_overview") == "true"
-
- detailsString = "DateCreated,EpisodeCount,SeasonCount,Path,Genres,Studios,CumulativeRunTimeTicks,Etag"
-
- if include_media:
- detailsString += ",MediaStreams"
-
- if include_people:
- detailsString += ",People"
-
- if include_overview:
- detailsString += ",Overview"
-
- return detailsString
-
-
def getChecksum(item):
userdata = item['UserData']
checksum = "%s_%s_%s_%s_%s_%s_%s" % (
@@ -182,36 +183,43 @@ def getArt(item, server, widget=False):
'clearart': '',
'discart': '',
'landscape': '',
+ 'tvshow.fanart': '',
'tvshow.poster': '',
'tvshow.clearart': '',
+ 'tvshow.clearlogo': '',
'tvshow.banner': '',
'tvshow.landscape': ''
}
- item_id = item.get("Id")
+ item_id = item["Id"]
image_id = item_id
- imageTags = item.get("ImageTags")
- if (imageTags is not None) and (imageTags.get("Primary") is not None):
- image_tag = imageTags.get("Primary")
+ imageTags = item["ImageTags"]
+ if imageTags is not None and imageTags["Primary"] is not None:
+ image_tag = imageTags["Primary"]
if widget:
art['thumb'] = downloadUtils.imageUrl(image_id, "Primary", 0, 400, 400, image_tag, server=server)
else:
art['thumb'] = downloadUtils.getArtwork(item, "Primary", server=server)
- if item.get("Type") == "Episode" or item.get("Type") == "Season":
+ item_type = item["Type"]
+
+ if item_type == "Episode" or item_type == "Season":
art['tvshow.poster'] = downloadUtils.getArtwork(item, "Primary", parent=True, server=server)
- art['tvshow.clearart'] = downloadUtils.getArtwork(item, "Logo", parent=True, server=server)
+ art['tvshow.clearart'] = downloadUtils.getArtwork(item, "Art", parent=True, server=server)
+ art['tvshow.clearlogo'] = downloadUtils.getArtwork(item, "Logo", parent=True, server=server)
art['tvshow.banner'] = downloadUtils.getArtwork(item, "Banner", parent=True, server=server)
art['tvshow.landscape'] = downloadUtils.getArtwork(item, "Thumb", parent=True, server=server)
- elif item.get("Type") == "Series":
+ art['tvshow.fanart'] = downloadUtils.getArtwork(item, "Backdrop", parent=True, server=server)
+ elif item_type == "Series":
art['tvshow.poster'] = downloadUtils.getArtwork(item, "Primary", parent=False, server=server)
- art['tvshow.clearart'] = downloadUtils.getArtwork(item, "Logo", parent=False, server=server)
+ art['tvshow.clearart'] = downloadUtils.getArtwork(item, "Art", parent=False, server=server)
+ art['tvshow.clearlogo'] = downloadUtils.getArtwork(item, "Logo", parent=False, server=server)
art['tvshow.banner'] = downloadUtils.getArtwork(item, "Banner", parent=False, server=server)
art['tvshow.landscape'] = downloadUtils.getArtwork(item, "Thumb", parent=False, server=server)
+ art['tvshow.fanart'] = downloadUtils.getArtwork(item, "Backdrop", parent=False, server=server)
- if item.get("Type") == "Episode":
- art['thumb'] = art['thumb'] if art['thumb'] else downloadUtils.getArtwork(item, "Thumb", server=server)
- art['landscape'] = art['thumb'] if art['thumb'] else downloadUtils.getArtwork(item, "Thumb", parent=True, server=server)
+ if item_type == "Episode":
+ art['landscape'] = downloadUtils.getArtwork(item, "Thumb", parent=True, server=server)
else:
art['poster'] = art['thumb']
@@ -256,13 +264,13 @@ def cache_artwork():
web_port = {"setting": "services.webserverport"}
result = json_rpc('Settings.GetSettingValue').execute(web_port)
xbmc_port = result['result']['value']
- log.debug("xbmc_port: " + str(xbmc_port))
+ log.debug("xbmc_port: {0}", xbmc_port)
# get the user
web_user = {"setting": "services.webserverusername"}
result = json_rpc('Settings.GetSettingValue').execute(web_user)
xbmc_username = result['result']['value']
- log.debug("xbmc_username: " + str(xbmc_username))
+ log.debug("xbmc_username: {0}", xbmc_username)
# get the password
web_pass = {"setting": "services.webserverpassword"}
@@ -278,7 +286,7 @@ def cache_artwork():
json_result = json_rpc('Textures.GetTextures').execute()
textures = json_result.get("result", {}).get("textures", [])
- log.debug("texture ids: " + str(textures))
+ log.debug("texture ids: {0}", textures)
total = len(textures)
for texture in textures:
texture_id = texture["textureid"]
@@ -320,11 +328,10 @@ def cache_artwork():
'&ImageTypeLimit=1' +
'&format=json')
- results = downloadUtils.downloadUrl(url, method="GET")
+ data_manager = DataManager()
+ results = data_manager.GetContent(url)
if results is None:
results = []
- else:
- results = json.loads(results)
if isinstance(results, dict):
results = results.get("Items")
@@ -340,10 +347,10 @@ def cache_artwork():
if image_url not in texture_urls and not image_url.endswith("&Tag=") and len(image_url) > 0:
missing_texture_urls.add(image_url)
- log.debug("texture_urls:" + str(texture_urls))
- log.debug("missing_texture_urls: " + str(missing_texture_urls))
- log.debug("Number of existing textures: %s" % len(texture_urls))
- log.debug("Number of missing textures: %s" % len(missing_texture_urls))
+ log.debug("texture_urls: {0}", texture_urls)
+ log.debug("missing_texture_urls: {0}", missing_texture_urls)
+ log.debug("Number of existing textures: {0}", len(texture_urls))
+ log.debug("Number of missing textures: {0}", len(missing_texture_urls))
kodi_http_server = "localhost:" + str(xbmc_port)
headers = {}
@@ -358,10 +365,10 @@ def cache_artwork():
count_done = 0
for get_url in missing_texture_urls:
- log.debug("texture_url:" + get_url)
+ log.debug("texture_url: {0}", get_url)
url = double_urlencode(get_url)
kodi_texture_url = ("/image/image://%s" % url)
- log.debug("kodi_texture_url: " + kodi_texture_url)
+ log.debug("kodi_texture_url: {0}", kodi_texture_url)
percentage = int((float(index) / float(total)) * 100)
message = "%s of %s" % (index, total)
@@ -372,7 +379,7 @@ def cache_artwork():
data = conn.getresponse()
if data.status == 200:
count_done += 1
- log.debug("Get Image Result: " + str(data.status))
+ log.debug("Get Image Result: {0}", data.status)
index += 1
if pdialog.iscanceled():
@@ -398,3 +405,13 @@ def single_urlencode(text):
text = urllib.urlencode({'blahblahblah': text.encode('utf-8')})
text = text[13:]
return text.decode('utf-8') #return the result again as unicode
+
+
+def send_event_notification(method, data):
+ next_data = json.dumps(data)
+ source_id = "embycon"
+ data = '\\"[\\"{0}\\"]\\"'.format(binascii.hexlify(next_data))
+ command = 'XBMC.NotifyAll({0}.SIGNAL,{1},{2})'.format(source_id, method, data)
+ log.debug("Sending notification event data: {0}", command)
+ xbmc.executebuiltin(command)
+
diff --git a/plugin.video.embycon/resources/lib/websocket.py b/plugin.video.embycon/resources/lib/websocket.py
new file mode 100644
index 0000000..a533652
--- /dev/null
+++ b/plugin.video.embycon/resources/lib/websocket.py
@@ -0,0 +1,930 @@
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+
+
+import socket
+
+try:
+ import ssl
+ from ssl import SSLError
+ HAVE_SSL = True
+except ImportError:
+ # dummy class of SSLError for ssl none-support environment.
+ class SSLError(Exception):
+ pass
+
+ HAVE_SSL = False
+
+from urlparse import urlparse
+import os
+import array
+import struct
+import uuid
+import hashlib
+import base64
+import threading
+import time
+import logging
+import traceback
+import sys
+
+"""
+websocket python client.
+=========================
+
+This version support only hybi-13.
+Please see http://tools.ietf.org/html/rfc6455 for protocol.
+"""
+
+
+# websocket supported version.
+VERSION = 13
+
+# closing frame status codes.
+STATUS_NORMAL = 1000
+STATUS_GOING_AWAY = 1001
+STATUS_PROTOCOL_ERROR = 1002
+STATUS_UNSUPPORTED_DATA_TYPE = 1003
+STATUS_STATUS_NOT_AVAILABLE = 1005
+STATUS_ABNORMAL_CLOSED = 1006
+STATUS_INVALID_PAYLOAD = 1007
+STATUS_POLICY_VIOLATION = 1008
+STATUS_MESSAGE_TOO_BIG = 1009
+STATUS_INVALID_EXTENSION = 1010
+STATUS_UNEXPECTED_CONDITION = 1011
+STATUS_TLS_HANDSHAKE_ERROR = 1015
+
+logger = logging.getLogger()
+
+
+class WebSocketException(Exception):
+ """
+ websocket exeception class.
+ """
+ pass
+
+
+class WebSocketConnectionClosedException(WebSocketException):
+ """
+ If remote host closed the connection or some network error happened,
+ this exception will be raised.
+ """
+ pass
+
+class WebSocketTimeoutException(WebSocketException):
+ """
+ WebSocketTimeoutException will be raised at socket timeout during read/write data.
+ """
+ pass
+
+default_timeout = None
+traceEnabled = False
+
+
+def enableTrace(tracable):
+ """
+ turn on/off the tracability.
+
+ tracable: boolean value. if set True, tracability is enabled.
+ """
+ global traceEnabled
+ traceEnabled = tracable
+ if tracable:
+ if not logger.handlers:
+ logger.addHandler(logging.StreamHandler())
+ logger.setLevel(logging.DEBUG)
+
+
+def setdefaulttimeout(timeout):
+ """
+ Set the global timeout setting to connect.
+
+ timeout: default socket timeout time. This value is second.
+ """
+ global default_timeout
+ default_timeout = timeout
+
+
+def getdefaulttimeout():
+ """
+ Return the global timeout setting(second) to connect.
+ """
+ return default_timeout
+
+
+def _wrap_sni_socket(sock, sslopt, hostname):
+ context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_SSLv23))
+
+ if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE:
+ capath = ssl.get_default_verify_paths().capath
+ context.load_verify_locations(cafile=sslopt.get('ca_certs', None),
+ capath=sslopt.get('ca_cert_path', capath))
+
+ return context.wrap_socket(
+ sock,
+ do_handshake_on_connect=sslopt.get('do_handshake_on_connect', True),
+ suppress_ragged_eofs=sslopt.get('suppress_ragged_eofs', True),
+ server_hostname=hostname,
+ )
+
+
+def _parse_url(url):
+ """
+ parse url and the result is tuple of
+ (hostname, port, resource path and the flag of secure mode)
+
+ url: url string.
+ """
+ if ":" not in url:
+ raise ValueError("url is invalid")
+
+ scheme, url = url.split(":", 1)
+
+ parsed = urlparse(url, scheme="http")
+ if parsed.hostname:
+ hostname = parsed.hostname
+ else:
+ raise ValueError("hostname is invalid")
+ port = 0
+ if parsed.port:
+ port = parsed.port
+
+ is_secure = False
+ if scheme == "ws":
+ if not port:
+ port = 80
+ elif scheme == "wss":
+ is_secure = True
+ if not port:
+ port = 443
+ else:
+ raise ValueError("scheme %s is invalid" % scheme)
+
+ if parsed.path:
+ resource = parsed.path
+ else:
+ resource = "/"
+
+ if parsed.query:
+ resource += "?" + parsed.query
+
+ return (hostname, port, resource, is_secure)
+
+
+def create_connection(url, timeout=None, **options):
+ """
+ connect to url and return websocket object.
+
+ Connect to url and return the WebSocket object.
+ Passing optional timeout parameter will set the timeout on the socket.
+ If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used.
+ You can customize using 'options'.
+ If you set "header" list object, you can set your own custom header.
+
+ >>> conn = create_connection("ws://echo.websocket.org/",
+ ... header=["User-Agent: MyProgram",
+ ... "x-custom: header"])
+
+
+ timeout: socket timeout time. This value is integer.
+ if you set None for this value, it means "use default_timeout value"
+
+ options: current support option is only "header".
+ if you set header as dict value, the custom HTTP headers are added.
+ """
+ sockopt = options.get("sockopt", [])
+ sslopt = options.get("sslopt", {})
+ websock = WebSocket(sockopt=sockopt, sslopt=sslopt)
+ websock.settimeout(timeout if timeout is not None else default_timeout)
+ websock.connect(url, **options)
+ return websock
+
+_MAX_INTEGER = (1 << 32) -1
+_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1)
+_MAX_CHAR_BYTE = (1<<8) -1
+
+# ref. Websocket gets an update, and it breaks stuff.
+# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html
+
+
+def _create_sec_websocket_key():
+ uid = uuid.uuid4()
+ return base64.encodestring(uid.bytes).strip()
+
+
+_HEADERS_TO_CHECK = {
+ "upgrade": "websocket",
+ "connection": "upgrade",
+ }
+
+
+class ABNF(object):
+ """
+ ABNF frame class.
+ see http://tools.ietf.org/html/rfc5234
+ and http://tools.ietf.org/html/rfc6455#section-5.2
+ """
+
+ # operation code values.
+ OPCODE_CONT = 0x0
+ OPCODE_TEXT = 0x1
+ OPCODE_BINARY = 0x2
+ OPCODE_CLOSE = 0x8
+ OPCODE_PING = 0x9
+ OPCODE_PONG = 0xa
+
+ # available operation code value tuple
+ OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
+ OPCODE_PING, OPCODE_PONG)
+
+ # opcode human readable string
+ OPCODE_MAP = {
+ OPCODE_CONT: "cont",
+ OPCODE_TEXT: "text",
+ OPCODE_BINARY: "binary",
+ OPCODE_CLOSE: "close",
+ OPCODE_PING: "ping",
+ OPCODE_PONG: "pong"
+ }
+
+ # data length threashold.
+ LENGTH_7 = 0x7d
+ LENGTH_16 = 1 << 16
+ LENGTH_63 = 1 << 63
+
+ def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0,
+ opcode=OPCODE_TEXT, mask=1, data=""):
+ """
+ Constructor for ABNF.
+ please check RFC for arguments.
+ """
+ self.fin = fin
+ self.rsv1 = rsv1
+ self.rsv2 = rsv2
+ self.rsv3 = rsv3
+ self.opcode = opcode
+ self.mask_value = mask
+ self.data = data
+ self.get_mask_key = os.urandom
+
+ def __str__(self):
+ return "fin=" + str(self.fin) \
+ + " opcode=" + str(self.opcode) \
+ + " data=" + str(self.data)
+
+ @staticmethod
+ def create_frame(data, opcode):
+ """
+ create frame to send text, binary and other data.
+
+ data: data to send. This is string value(byte array).
+ if opcode is OPCODE_TEXT and this value is uniocde,
+ data value is conveted into unicode string, automatically.
+
+ opcode: operation code. please see OPCODE_XXX.
+ """
+ if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode):
+ data = data.encode("utf-8")
+ # mask must be set if send data from client
+ return ABNF(1, 0, 0, 0, opcode, 1, data)
+
+ def format(self):
+ """
+ format this object to string(byte array) to send data to server.
+ """
+ if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
+ raise ValueError("not 0 or 1")
+ if self.opcode not in ABNF.OPCODES:
+ raise ValueError("Invalid OPCODE")
+ length = len(self.data)
+ if length >= ABNF.LENGTH_63:
+ raise ValueError("data is too long")
+
+ frame_header = chr(self.fin << 7
+ | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4
+ | self.opcode)
+ if length < ABNF.LENGTH_7:
+ frame_header += chr(self.mask_value << 7 | length)
+ elif length < ABNF.LENGTH_16:
+ frame_header += chr(self.mask_value << 7 | 0x7e)
+ frame_header += struct.pack("!H", length)
+ else:
+ frame_header += chr(self.mask_value << 7 | 0x7f)
+ frame_header += struct.pack("!Q", length)
+
+ if not self.mask_value:
+ return frame_header + self.data
+ else:
+ mask_key = self.get_mask_key(4)
+ return frame_header + self._get_masked(mask_key)
+
+ def _get_masked(self, mask_key):
+ s = ABNF.mask(mask_key, self.data)
+ return mask_key + "".join(s)
+
+ @staticmethod
+ def mask(mask_key, data):
+ """
+ mask or unmask data. Just do xor for each byte
+
+ mask_key: 4 byte string(byte).
+
+ data: data to mask/unmask.
+ """
+ _m = array.array("B", mask_key)
+ _d = array.array("B", data)
+ for i in xrange(len(_d)):
+ _d[i] ^= _m[i % 4]
+ return _d.tostring()
+
+
+class WebSocket(object):
+ """
+ Low level WebSocket interface.
+ This class is based on
+ The WebSocket protocol draft-hixie-thewebsocketprotocol-76
+ http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
+
+ We can connect to the websocket server and send/recieve data.
+ The following example is a echo client.
+
+ >>> import websocket
+ >>> ws = websocket.WebSocket()
+ >>> ws.connect("ws://echo.websocket.org")
+ >>> ws.send("Hello, Server")
+ >>> ws.recv()
+ 'Hello, Server'
+ >>> ws.close()
+
+ get_mask_key: a callable to produce new mask keys, see the set_mask_key
+ function's docstring for more details
+ sockopt: values for socket.setsockopt.
+ sockopt must be tuple and each element is argument of sock.setscokopt.
+ sslopt: dict object for ssl socket option.
+ """
+
+ def __init__(self, get_mask_key=None, sockopt=None, sslopt=None):
+ """
+ Initalize WebSocket object.
+ """
+ if sockopt is None:
+ sockopt = []
+ if sslopt is None:
+ sslopt = {}
+ self.connected = False
+ self.sock = socket.socket()
+ for opts in sockopt:
+ self.sock.setsockopt(*opts)
+ self.sslopt = sslopt
+ self.get_mask_key = get_mask_key
+ # Buffers over the packets from the layer beneath until desired amount
+ # bytes of bytes are received.
+ self._recv_buffer = []
+ # These buffer over the build-up of a single frame.
+ self._frame_header = None
+ self._frame_length = None
+ self._frame_mask = None
+ self._cont_data = None
+
+ def fileno(self):
+ return self.sock.fileno()
+
+ def set_mask_key(self, func):
+ """
+ set function to create musk key. You can custumize mask key generator.
+ Mainly, this is for testing purpose.
+
+ func: callable object. the fuct must 1 argument as integer.
+ The argument means length of mask key.
+ This func must be return string(byte array),
+ which length is argument specified.
+ """
+ self.get_mask_key = func
+
+ def gettimeout(self):
+ """
+ Get the websocket timeout(second).
+ """
+ return self.sock.gettimeout()
+
+ def settimeout(self, timeout):
+ """
+ Set the timeout to the websocket.
+
+ timeout: timeout time(second).
+ """
+ self.sock.settimeout(timeout)
+
+ timeout = property(gettimeout, settimeout)
+
+ def connect(self, url, **options):
+ """
+ Connect to url. url is websocket url scheme. ie. ws://host:port/resource
+ You can customize using 'options'.
+ If you set "header" dict object, you can set your own custom header.
+
+ >>> ws = WebSocket()
+ >>> ws.connect("ws://echo.websocket.org/",
+ ... header={"User-Agent: MyProgram",
+ ... "x-custom: header"})
+
+ timeout: socket timeout time. This value is integer.
+ if you set None for this value,
+ it means "use default_timeout value"
+
+ options: current support option is only "header".
+ if you set header as dict value,
+ the custom HTTP headers are added.
+
+ """
+ hostname, port, resource, is_secure = _parse_url(url)
+ # TODO: we need to support proxy
+ self.sock.connect((hostname, port))
+ if is_secure:
+ if HAVE_SSL:
+ if self.sslopt is None:
+ sslopt = {}
+ else:
+ sslopt = self.sslopt
+ if ssl.HAS_SNI:
+ self.sock = _wrap_sni_socket(self.sock, sslopt, hostname)
+ else:
+ self.sock = ssl.wrap_socket(self.sock, **sslopt)
+ else:
+ raise WebSocketException("SSL not available.")
+
+ self._handshake(hostname, port, resource, **options)
+
+ def _handshake(self, host, port, resource, **options):
+ headers = []
+ headers.append("GET %s HTTP/1.1" % resource)
+ headers.append("Upgrade: websocket")
+ headers.append("Connection: Upgrade")
+ if port == 80:
+ hostport = host
+ else:
+ hostport = "%s:%d" % (host, port)
+ headers.append("Host: %s" % hostport)
+
+ if "origin" in options:
+ headers.append("Origin: %s" % options["origin"])
+ else:
+ headers.append("Origin: http://%s" % hostport)
+
+ key = _create_sec_websocket_key()
+ headers.append("Sec-WebSocket-Key: %s" % key)
+ headers.append("Sec-WebSocket-Version: %s" % VERSION)
+ if "header" in options:
+ headers.extend(options["header"])
+
+ headers.append("")
+ headers.append("")
+
+ header_str = "\r\n".join(headers)
+ self._send(header_str)
+ if traceEnabled:
+ logger.debug("--- request header ---")
+ logger.debug(header_str)
+ logger.debug("-----------------------")
+
+ status, resp_headers = self._read_headers()
+ if status != 101:
+ self.close()
+ raise WebSocketException("Handshake Status %d" % status)
+
+ success = self._validate_header(resp_headers, key)
+ if not success:
+ self.close()
+ raise WebSocketException("Invalid WebSocket Header")
+
+ self.connected = True
+
+ def _validate_header(self, headers, key):
+ for k, v in _HEADERS_TO_CHECK.iteritems():
+ r = headers.get(k, None)
+ if not r:
+ return False
+ r = r.lower()
+ if v != r:
+ return False
+
+ result = headers.get("sec-websocket-accept", None)
+ if not result:
+ return False
+ result = result.lower()
+
+ value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+ hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower()
+ return hashed == result
+
+ def _read_headers(self):
+ status = None
+ headers = {}
+ if traceEnabled:
+ logger.debug("--- response header ---")
+
+ while True:
+ line = self._recv_line()
+ if line == "\r\n":
+ break
+ line = line.strip()
+ if traceEnabled:
+ logger.debug(line)
+ if not status:
+ status_info = line.split(" ", 2)
+ status = int(status_info[1])
+ else:
+ kv = line.split(":", 1)
+ if len(kv) == 2:
+ key, value = kv
+ headers[key.lower()] = value.strip().lower()
+ else:
+ raise WebSocketException("Invalid header")
+
+ if traceEnabled:
+ logger.debug("-----------------------")
+
+ return status, headers
+
+ def send(self, payload, opcode=ABNF.OPCODE_TEXT):
+ """
+ Send the data as string.
+
+ payload: Payload must be utf-8 string or unicoce,
+ if the opcode is OPCODE_TEXT.
+ Otherwise, it must be string(byte array)
+
+ opcode: operation code to send. Please see OPCODE_XXX.
+ """
+ frame = ABNF.create_frame(payload, opcode)
+ if self.get_mask_key:
+ frame.get_mask_key = self.get_mask_key
+ data = frame.format()
+ length = len(data)
+ if traceEnabled:
+ logger.debug("send: " + repr(data))
+ while data:
+ l = self._send(data)
+ data = data[l:]
+ return length
+
+ def send_binary(self, payload):
+ return self.send(payload, ABNF.OPCODE_BINARY)
+
+ def ping(self, payload=""):
+ """
+ send ping data.
+
+ payload: data payload to send server.
+ """
+ self.send(payload, ABNF.OPCODE_PING)
+
+ def pong(self, payload):
+ """
+ send pong data.
+
+ payload: data payload to send server.
+ """
+ self.send(payload, ABNF.OPCODE_PONG)
+
+ def recv(self):
+ """
+ Receive string data(byte array) from the server.
+
+ return value: string(byte array) value.
+ """
+ opcode, data = self.recv_data()
+ return data
+
+ def recv_data(self):
+ """
+ Recieve data with operation code.
+
+ return value: tuple of operation code and string(byte array) value.
+ """
+ while True:
+ frame = self.recv_frame()
+ if not frame:
+ # handle error:
+ # 'NoneType' object has no attribute 'opcode'
+ raise WebSocketException("Not a valid frame %s" % frame)
+ elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT):
+ if frame.opcode == ABNF.OPCODE_CONT and not self._cont_data:
+ raise WebSocketException("Illegal frame")
+ if self._cont_data:
+ self._cont_data[1] += frame.data
+ else:
+ self._cont_data = [frame.opcode, frame.data]
+
+ if frame.fin:
+ data = self._cont_data
+ self._cont_data = None
+ return data
+ elif frame.opcode == ABNF.OPCODE_CLOSE:
+ self.send_close()
+ return (frame.opcode, None)
+ elif frame.opcode == ABNF.OPCODE_PING:
+ self.pong(frame.data)
+
+ def recv_frame(self):
+ """
+ recieve data as frame from server.
+
+ return value: ABNF frame object.
+ """
+ # Header
+ if self._frame_header is None:
+ self._frame_header = self._recv_strict(2)
+ b1 = ord(self._frame_header[0])
+ fin = b1 >> 7 & 1
+ rsv1 = b1 >> 6 & 1
+ rsv2 = b1 >> 5 & 1
+ rsv3 = b1 >> 4 & 1
+ opcode = b1 & 0xf
+ b2 = ord(self._frame_header[1])
+ has_mask = b2 >> 7 & 1
+ # Frame length
+ if self._frame_length is None:
+ length_bits = b2 & 0x7f
+ if length_bits == 0x7e:
+ length_data = self._recv_strict(2)
+ self._frame_length = struct.unpack("!H", length_data)[0]
+ elif length_bits == 0x7f:
+ length_data = self._recv_strict(8)
+ self._frame_length = struct.unpack("!Q", length_data)[0]
+ else:
+ self._frame_length = length_bits
+ # Mask
+ if self._frame_mask is None:
+ self._frame_mask = self._recv_strict(4) if has_mask else ""
+ # Payload
+ payload = self._recv_strict(self._frame_length)
+ if has_mask:
+ payload = ABNF.mask(self._frame_mask, payload)
+ # Reset for next frame
+ self._frame_header = None
+ self._frame_length = None
+ self._frame_mask = None
+ return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload)
+
+
+ def send_close(self, status=STATUS_NORMAL, reason=""):
+ """
+ send close data to the server.
+
+ status: status code to send. see STATUS_XXX.
+
+ reason: the reason to close. This must be string.
+ """
+ if status < 0 or status >= ABNF.LENGTH_16:
+ raise ValueError("code is invalid range")
+ self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
+
+ def close(self, status=STATUS_NORMAL, reason=""):
+ """
+ Close Websocket object
+
+ status: status code to send. see STATUS_XXX.
+
+ reason: the reason to close. This must be string.
+ """
+
+ try:
+ self.sock.shutdown(socket.SHUT_RDWR)
+ except:
+ pass
+
+ '''
+ if self.connected:
+ if status < 0 or status >= ABNF.LENGTH_16:
+ raise ValueError("code is invalid range")
+
+ try:
+ self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
+ timeout = self.sock.gettimeout()
+ self.sock.settimeout(3)
+ try:
+ frame = self.recv_frame()
+ if logger.isEnabledFor(logging.ERROR):
+ recv_status = struct.unpack("!H", frame.data)[0]
+ if recv_status != STATUS_NORMAL:
+ logger.error("close status: " + repr(recv_status))
+ except:
+ pass
+ self.sock.settimeout(timeout)
+ self.sock.shutdown(socket.SHUT_RDWR)
+ except:
+ pass
+ '''
+ self._closeInternal()
+
+ def _closeInternal(self):
+ self.connected = False
+ self.sock.close()
+
+ def _send(self, data):
+ try:
+ return self.sock.send(data)
+ except socket.timeout as e:
+ raise WebSocketTimeoutException(e.args[0])
+ except Exception as e:
+ if "timed out" in e.args[0]:
+ raise WebSocketTimeoutException(e.args[0])
+ else:
+ raise e
+
+ def _recv(self, bufsize):
+ try:
+ bytes = self.sock.recv(bufsize)
+ except socket.timeout as e:
+ raise WebSocketTimeoutException(e.args[0])
+ except SSLError as e:
+ if e.args[0] == "The read operation timed out":
+ raise WebSocketTimeoutException(e.args[0])
+ else:
+ raise
+ if not bytes:
+ raise WebSocketConnectionClosedException()
+ return bytes
+
+
+ def _recv_strict(self, bufsize):
+ shortage = bufsize - sum(len(x) for x in self._recv_buffer)
+ while shortage > 0:
+ bytes = self._recv(shortage)
+ self._recv_buffer.append(bytes)
+ shortage -= len(bytes)
+ unified = "".join(self._recv_buffer)
+ if shortage == 0:
+ self._recv_buffer = []
+ return unified
+ else:
+ self._recv_buffer = [unified[bufsize:]]
+ return unified[:bufsize]
+
+
+ def _recv_line(self):
+ line = []
+ while True:
+ c = self._recv(1)
+ line.append(c)
+ if c == "\n":
+ break
+ return "".join(line)
+
+
+class WebSocketApp(object):
+ """
+ Higher level of APIs are provided.
+ The interface is like JavaScript WebSocket object.
+ """
+ def __init__(self, url, header=[],
+ on_open=None, on_message=None, on_error=None,
+ on_close=None, keep_running=True, get_mask_key=None):
+ """
+ url: websocket url.
+ header: custom header for websocket handshake.
+ on_open: callable object which is called at opening websocket.
+ this function has one argument. The arugment is this class object.
+ on_message: callbale object which is called when recieved data.
+ on_message has 2 arguments.
+ The 1st arugment is this class object.
+ The passing 2nd arugment is utf-8 string which we get from the server.
+ on_error: callable object which is called when we get error.
+ on_error has 2 arguments.
+ The 1st arugment is this class object.
+ The passing 2nd arugment is exception object.
+ on_close: callable object which is called when closed the connection.
+ this function has one argument. The arugment is this class object.
+ keep_running: a boolean flag indicating whether the app's main loop should
+ keep running, defaults to True
+ get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's
+ docstring for more information
+ """
+ self.url = url
+ self.header = header
+ self.on_open = on_open
+ self.on_message = on_message
+ self.on_error = on_error
+ self.on_close = on_close
+ self.keep_running = keep_running
+ self.get_mask_key = get_mask_key
+ self.sock = None
+
+ def send(self, data, opcode=ABNF.OPCODE_TEXT):
+ """
+ send message.
+ data: message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode.
+ opcode: operation code of data. default is OPCODE_TEXT.
+ """
+ if self.sock.send(data, opcode) == 0:
+ raise WebSocketConnectionClosedException()
+
+ def close(self):
+ """
+ close websocket connection.
+ """
+ self.keep_running = False
+ if(self.sock != None):
+ self.sock.close()
+
+ def _send_ping(self, interval):
+ while True:
+ for i in range(interval):
+ time.sleep(1)
+ if not self.keep_running:
+ return
+ self.sock.ping()
+
+ def run_forever(self, sockopt=None, sslopt=None, ping_interval=0):
+ """
+ run event loop for WebSocket framework.
+ This loop is infinite loop and is alive during websocket is available.
+ sockopt: values for socket.setsockopt.
+ sockopt must be tuple and each element is argument of sock.setscokopt.
+ sslopt: ssl socket optional dict.
+ ping_interval: automatically send "ping" command every specified period(second)
+ if set to 0, not send automatically.
+ """
+ if sockopt is None:
+ sockopt = []
+ if sslopt is None:
+ sslopt = {}
+ if self.sock:
+ raise WebSocketException("socket is already opened")
+ thread = None
+ self.keep_running = True
+
+ try:
+ self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt)
+ self.sock.settimeout(default_timeout)
+ self.sock.connect(self.url, header=self.header)
+ self._callback(self.on_open)
+
+ if ping_interval:
+ thread = threading.Thread(target=self._send_ping, args=(ping_interval,))
+ thread.setDaemon(True)
+ thread.start()
+
+ while self.keep_running:
+
+ try:
+ data = self.sock.recv()
+
+ if data is None or self.keep_running == False:
+ break
+ self._callback(self.on_message, data)
+
+ except Exception, e:
+ #print str(e.args[0])
+ if "timed out" not in e.args[0]:
+ raise e
+
+ except Exception, e:
+ self._callback(self.on_error, e)
+ finally:
+ if thread:
+ self.keep_running = False
+ self.sock.close()
+ self._callback(self.on_close)
+ self.sock = None
+
+ def _callback(self, callback, *args):
+ if callback:
+ try:
+ callback(self, *args)
+ except Exception, e:
+ logger.error(e)
+ if True:#logger.isEnabledFor(logging.DEBUG):
+ _, _, tb = sys.exc_info()
+ traceback.print_tb(tb)
+
+
+if __name__ == "__main__":
+ enableTrace(True)
+ ws = create_connection("ws://echo.websocket.org/")
+ print("Sending 'Hello, World'...")
+ ws.send("Hello, World")
+ print("Sent")
+ print("Receiving...")
+ result = ws.recv()
+ print("Received '%s'" % result)
+ ws.close()
diff --git a/plugin.video.embycon/resources/lib/websocket_client.py b/plugin.video.embycon/resources/lib/websocket_client.py
new file mode 100644
index 0000000..db0a073
--- /dev/null
+++ b/plugin.video.embycon/resources/lib/websocket_client.py
@@ -0,0 +1,302 @@
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+import threading
+import websocket
+
+import xbmc
+import xbmcgui
+
+from functions import PLAY
+from simple_logging import SimpleLogging
+import clientinfo
+import downloadutils
+from json_rpc import json_rpc
+
+log = SimpleLogging(__name__)
+
+class WebSocketClient(threading.Thread):
+
+ _shared_state = {}
+
+ _client = None
+ _stop_websocket = False
+
+ def __init__(self):
+
+ self.__dict__ = self._shared_state
+ self.monitor = xbmc.Monitor()
+
+ self.client_info = clientinfo.ClientInformation()
+ self.device_id = self.client_info.getDeviceId()
+
+ threading.Thread.__init__(self)
+
+ def on_message(self, ws, message):
+
+ result = json.loads(message)
+ message_type = result['MessageType']
+
+ if message_type == 'Play':
+ data = result['Data']
+ self._play(data)
+
+ elif message_type == 'Playstate':
+ data = result['Data']
+ self._playstate(data)
+
+ elif message_type == "UserDataChanged":
+ log.debug("WebSocket Message UserDataChanged: {0}", message)
+
+ elif message_type == "LibraryChanged":
+ log.debug("WebSocket Message LibraryChanged: {0}", message)
+
+ elif message_type == "GeneralCommand":
+ data = result['Data']
+ self._general_commands(data)
+
+ else:
+ log.debug("WebSocket Message Type: {0}", message)
+
+ def _play(cls, data):
+
+ item_ids = data['ItemIds']
+ command = data['PlayCommand']
+
+ if command == 'PlayNow':
+ startat = data.get('StartPositionTicks', 0)
+ log.debug("WebSocket Message PlayNow: {0}", data)
+
+ media_source_id = data.get("MediaSourceId", "")
+
+ params = {}
+ params["item_id"] = item_ids[0]
+ params["auto_resume"] = str(startat)
+ params["media_source_id"] = media_source_id
+ params["use_default"] = "true"
+ PLAY(params)
+
+
+ def _playstate(cls, data):
+
+ command = data['Command']
+ player = xbmc.Player()
+
+ actions = {
+
+ 'Stop': player.stop,
+ 'Unpause': player.pause,
+ 'Pause': player.pause,
+ 'PlayPause': player.pause,
+ 'NextTrack': player.playnext,
+ 'PreviousTrack': player.playprevious
+ }
+ if command == 'Seek':
+
+ if player.isPlaying():
+ seek_to = data['SeekPositionTicks']
+ seek_time = seek_to / 10000000.0
+ player.seekTime(seek_time)
+ log.debug("Seek to {0}", seek_time)
+
+ elif command in actions:
+ actions[command]()
+ log.debug("Command: {0} completed", command)
+
+ else:
+ log.debug("Unknown command: {0}", command)
+ return
+
+ def _general_commands(cls, data):
+
+ command = data['Name']
+ arguments = data['Arguments']
+
+ if command in ('Mute',
+ 'Unmute',
+ 'SetVolume',
+ 'SetSubtitleStreamIndex',
+ 'SetAudioStreamIndex',
+ 'SetRepeatMode'):
+
+ player = xbmc.Player()
+ # These commands need to be reported back
+ if command == 'Mute':
+ xbmc.executebuiltin('Mute')
+
+ elif command == 'Unmute':
+ xbmc.executebuiltin('Mute')
+
+ elif command == 'SetVolume':
+ volume = arguments['Volume']
+ xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
+
+ elif command == 'SetAudioStreamIndex':
+ index = int(arguments['Index'])
+ player.setAudioStream(index - 1)
+
+ elif command == 'SetRepeatMode':
+ mode = arguments['RepeatMode']
+ xbmc.executebuiltin('xbmc.PlayerControl(%s)' % mode)
+
+ elif command == 'DisplayMessage':
+
+ header = arguments['Header']
+ text = arguments['Text']
+ # show notification here
+ log.debug("WebSocket DisplayMessage: {0}", text)
+ xbmcgui.Dialog().notification("EmbyCon", text)
+
+ elif command == 'SendString':
+
+ params = {
+
+ 'text': arguments['String'],
+ 'done': False
+ }
+ json_rpc('Input.SendText').execute(params)
+
+ elif command in ('MoveUp', 'MoveDown', 'MoveRight', 'MoveLeft'):
+ # Commands that should wake up display
+ actions = {
+
+ 'MoveUp': "Input.Up",
+ 'MoveDown': "Input.Down",
+ 'MoveRight': "Input.Right",
+ 'MoveLeft': "Input.Left"
+ }
+ json_rpc(actions[command]).execute()
+
+ elif command == 'GoHome':
+ json_rpc('GUI.ActivateWindow').execute({'window': "home"})
+
+ elif command == "Guide":
+ json_rpc('GUI.ActivateWindow').execute({'window': "tvguide"})
+
+ else:
+ builtin = {
+
+ 'ToggleFullscreen': 'Action(FullScreen)',
+ 'ToggleOsdMenu': 'Action(OSD)',
+ 'ToggleContextMenu': 'Action(ContextMenu)',
+ 'Select': 'Action(Select)',
+ 'Back': 'Action(back)',
+ 'PageUp': 'Action(PageUp)',
+ 'NextLetter': 'Action(NextLetter)',
+ 'GoToSearch': 'VideoLibrary.Search',
+ 'GoToSettings': 'ActivateWindow(Settings)',
+ 'PageDown': 'Action(PageDown)',
+ 'PreviousLetter': 'Action(PrevLetter)',
+ 'TakeScreenshot': 'TakeScreenshot',
+ 'ToggleMute': 'Mute',
+ 'VolumeUp': 'Action(VolumeUp)',
+ 'VolumeDown': 'Action(VolumeDown)',
+ }
+ if command in builtin:
+ xbmc.executebuiltin(builtin[command])
+
+ def on_close(self, ws):
+ log.debug("closed")
+
+ def on_open(self, ws):
+ self.post_capabilities()
+
+ def on_error(self, ws, error):
+ log.debug("Error: {0}", error)
+
+ def run(self):
+
+ # websocket.enableTrace(True)
+ download_utils = downloadutils.DownloadUtils()
+
+ token = None
+ while token is None or token == "":
+ token = download_utils.authenticate()
+ if self.monitor.waitForAbort(10):
+ return
+
+ # Get the appropriate prefix for the websocket
+ server = download_utils.getServer()
+ if "https" in server:
+ server = server.replace('https', "wss")
+ else:
+ server = server.replace('http', "ws")
+
+ websocket_url = "%s/embywebsocket?api_key=%s&deviceId=%s" % (server, token, self.device_id)
+ log.debug("websocket url: {0}", websocket_url)
+
+ self._client = websocket.WebSocketApp(websocket_url,
+ on_message=self.on_message,
+ on_error=self.on_error,
+ on_close=self.on_close)
+ self._client.on_open = self.on_open
+ log.debug("Starting WebSocketClient")
+
+ while not self.monitor.abortRequested():
+
+ self._client.run_forever(ping_interval=10)
+
+ if self._stop_websocket:
+ break
+
+ if self.monitor.waitForAbort(20):
+ # Abort was requested, exit
+ break
+
+ log.debug("Reconnecting WebSocket")
+
+ log.debug("WebSocketClient Stopped")
+
+ def stop_client(self):
+
+ self._stop_websocket = True
+ if self._client is not None:
+ self._client.close()
+ log.debug("Stopping WebSocket (stop_client called)")
+
+ def post_capabilities(self):
+
+ url = "{server}/emby/Sessions/Capabilities/Full?format=json"
+ data = {
+ 'SupportsMediaControl': True,
+ 'PlayableMediaTypes': ["Video"],
+ 'SupportedCommands': ["MoveUp",
+ "MoveDown",
+ "MoveLeft",
+ "MoveRight",
+ "Select",
+ "Back",
+ "ToggleContextMenu",
+ "ToggleFullscreen",
+ "ToggleOsdMenu",
+ "GoHome",
+ "PageUp",
+ "NextLetter",
+ "GoToSearch",
+ "GoToSettings",
+ "PageDown",
+ "PreviousLetter",
+ "TakeScreenshot",
+ "VolumeUp",
+ "VolumeDown",
+ "ToggleMute",
+ "SendString",
+ "DisplayMessage",
+ #"SetAudioStreamIndex",
+ #"SetSubtitleStreamIndex",
+ "SetRepeatMode",
+ "Mute",
+ "Unmute",
+ "SetVolume",
+ "PlayNext",
+ "Play",
+ "Playstate",
+ "PlayMediaSource"]
+ }
+
+ download_utils = downloadutils.DownloadUtils()
+ download_utils.downloadUrl(url, postBody=data, method="POST")
+ log.debug("Posted Capabilities: {0}", data)
+
diff --git a/plugin.video.embycon/resources/lib/widgets.py b/plugin.video.embycon/resources/lib/widgets.py
index c3098d4..6a63c2d 100644
--- a/plugin.video.embycon/resources/lib/widgets.py
+++ b/plugin.video.embycon/resources/lib/widgets.py
@@ -12,12 +12,14 @@ from utils import getArt
from datamanager import DataManager
from simple_logging import SimpleLogging
from kodi_utils import HomeWindow
+from resources.lib.error import catch_except
log = SimpleLogging(__name__)
downloadUtils = DownloadUtils()
dataManager = DataManager()
kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
+
def checkForNewContent():
log.debug("checkForNewContent Called")
@@ -33,7 +35,7 @@ def checkForNewContent():
added_result = downloadUtils.downloadUrl(added_url, suppress=True)
result = json.loads(added_result)
- log.debug("LATEST_ADDED_ITEM:" + str(result))
+ log.debug("LATEST_ADDED_ITEM: {0}", result)
last_added_date = ""
if result is not None:
@@ -41,7 +43,7 @@ def checkForNewContent():
if len(items) > 0:
item = items[0]
last_added_date = item.get("Etag", "")
- log.debug("last_added_date: " + last_added_date)
+ log.debug("last_added_date: {0}", last_added_date)
played_url = ('{server}/emby/Users/{userid}/Items' +
'?Recursive=true' +
@@ -55,7 +57,7 @@ def checkForNewContent():
played_result = downloadUtils.downloadUrl(played_url, suppress=True)
result = json.loads(played_result)
- log.debug("LATEST_PLAYED_ITEM:" + str(result))
+ log.debug("LATEST_PLAYED_ITEM: {0}", result)
last_played_date = ""
if result is not None:
@@ -63,29 +65,29 @@ def checkForNewContent():
if len(items) > 0:
item = items[0]
last_played_date = item.get("Etag", "")
- log.debug("last_played_date: " + last_played_date)
+ log.debug("last_played_date: {0}", last_played_date)
home_window = HomeWindow()
current_widget_hash = home_window.getProperty("embycon_widget_reload")
- log.debug("Current Widget Hash: " + str(current_widget_hash))
+ log.debug("Current Widget Hash: {0}", current_widget_hash)
m = hashlib.md5()
m.update(last_played_date + last_added_date)
new_widget_hash = m.hexdigest()
- log.debug("New Widget Hash: " + str(new_widget_hash))
+ log.debug("New Widget Hash: {0}", new_widget_hash)
if current_widget_hash != new_widget_hash:
home_window.setProperty("embycon_widget_reload", new_widget_hash)
- log.debug("Setting New Widget Hash: " + str(new_widget_hash))
+ log.debug("Setting New Widget Hash: {0}", new_widget_hash)
def getWidgetUrlContent(handle, params):
- log.debug("getWidgetUrlContent Called" + str(params))
+ log.debug("getWidgetUrlContent Called: {0}", params)
request = params["url"]
request = urllib.unquote(request)
request = "{server}/emby/" + request + "&ImageTypeLimit=1&format=json"
- log.debug("getWidgetUrlContent URL:" + request)
+ log.debug("getWidgetUrlContent URL: {0}", request)
select_action = params.get("action", None)
@@ -96,7 +98,7 @@ def getWidgetUrlContent(handle, params):
def getSuggestions(handle, params):
- log.debug("getSuggestions Called" + str(params))
+ log.debug("getSuggestions Called: {0}", params)
itemsUrl = ("{server}/emby/Movies/Recommendations" +
"?userId={userid}" +
@@ -112,7 +114,7 @@ def getSuggestions(handle, params):
xbmcplugin.endOfDirectory(handle, cacheToDisc=False)
def getWidgetContentNextUp(handle, params):
- log.debug("getWidgetContentNextUp Called" + str(params))
+ log.debug("getWidgetContentNextUp Called: {0}", params)
itemsUrl = ("{server}/emby/Shows/NextUp?SeriesId=" + params["id"] +
"&userId={userid}" +
@@ -128,7 +130,7 @@ def getWidgetContentNextUp(handle, params):
def getWidgetContentSimilar(handle, params):
- log.debug("getWisgetContentSimilarMovies Called" + str(params))
+ log.debug("getWisgetContentSimilarMovies Called: {0}", params)
itemsUrl = ("{server}/emby/Items/" + params["id"] + "/Similar"
"?userId={userid}" +
@@ -145,18 +147,21 @@ def getWidgetContentSimilar(handle, params):
def getWidgetContentCast(handle, params):
- log.debug("getWigetContentCast Called" + str(params))
+ log.debug("getWigetContentCast Called: {0}", params)
server = downloadUtils.getServer()
id = params["id"]
- jsonData = downloadUtils.downloadUrl("{server}/emby/Users/{userid}/Items/" + id + "?format=json",
- suppress=False, popup=1)
- result = json.loads(jsonData)
- log.debug("ItemInfo: " + str(result))
+ data_manager = DataManager()
+ result = data_manager.GetContent("{server}/emby/Users/{userid}/Items/" + id + "?format=json")
+ log.debug("ItemInfo: {0}", result)
listItems = []
- people = result.get("People")
- if (people != None):
+ if result is not None:
+ people = result.get("People")
+ else:
+ people = None
+
+ if people is not None:
for person in people:
#if (person.get("Type") == "Director"):
# director = director + person.get("Name") + ' '
@@ -181,12 +186,17 @@ def getWidgetContentCast(handle, params):
artLinks["poster"] = person_thumbnail
list_item.setArt(artLinks)
+ labels = {}
+ labels["mediatype"] = "artist"
+ list_item.setInfo(type="music", infoLabels=labels)
+
if person_role:
list_item.setLabel2(person_role)
itemTupple = ("", list_item, False)
listItems.append(itemTupple)
+ xbmcplugin.setContent(handle, 'artists')
xbmcplugin.addDirectoryItems(handle, listItems)
xbmcplugin.endOfDirectory(handle, cacheToDisc=False)
@@ -194,18 +204,20 @@ def getWidgetContentCast(handle, params):
def populateWidgetItems(itemsUrl, override_select_action=None):
server = downloadUtils.getServer()
+ if server is None:
+ return []
+
settings = xbmcaddon.Addon(id='plugin.video.embycon')
select_action = settings.getSetting("widget_select_action")
if override_select_action is not None:
select_action = str(override_select_action)
- log.debug("WIDGET_DATE_URL: " + itemsUrl)
+ log.debug("WIDGET_DATE_URL: {0}", itemsUrl)
# get the items
- jsonData = downloadUtils.downloadUrl(itemsUrl, suppress=False, popup=1)
- log.debug("Widget(Items) jsonData: " + jsonData)
- result = json.loads(jsonData)
+ data_manager = DataManager()
+ result = data_manager.GetContent(itemsUrl)
if result is not None and isinstance(result, dict) and result.get("Items") is not None:
simmilarTo = result.get("BaselineItemName", None)
@@ -219,35 +231,30 @@ def populateWidgetItems(itemsUrl, override_select_action=None):
itemCount = 1
listItems = []
for item in result:
- item_id = item.get("Id")
- name = item.get("Name")
+ item_id = item["Id"]
+ name = item["Name"]
episodeDetails = ""
- log.debug("WIDGET_DATE_NAME: " + name)
+ log.debug("WIDGET_DATE_NAME: {0}", name)
- title = item.get("Name")
+ title = name
tvshowtitle = ""
+ item_type = item["Type"]
+ series_name = item["SeriesName"]
- if (item.get("Type") == "Episode" and item.get("SeriesName") != None):
+ if item_type == "Episode" and series_name is not None:
- eppNumber = "X"
- tempEpisodeNumber = "0"
- if (item.get("IndexNumber") != None):
- eppNumber = item.get("IndexNumber")
- if eppNumber < 10:
- tempEpisodeNumber = "0" + str(eppNumber)
- else:
- tempEpisodeNumber = str(eppNumber)
+ episode_number = item["IndexNumber"]
+ if episode_number is None:
+ episode_number = 0
- seasonNumber = item.get("ParentIndexNumber")
- if seasonNumber < 10:
- tempSeasonNumber = "0" + str(seasonNumber)
- else:
- tempSeasonNumber = str(seasonNumber)
+ season_number = item["ParentIndexNumber"]
+ if season_number is None:
+ season_number = 0
- episodeDetails = "S" + tempSeasonNumber + "E" + tempEpisodeNumber
- name = item.get("SeriesName") + " " + episodeDetails
- tvshowtitle = episodeDetails
- title = item.get("SeriesName")
+ name = series_name + " " + episodeDetails
+ name = "%s S%02dE%02d" % (series_name, season_number, episode_number)
+ tvshowtitle = "S%02dE%02d" % (season_number, episode_number)
+ title = series_name
art = getArt(item, server, widget=True)
@@ -258,31 +265,30 @@ def populateWidgetItems(itemsUrl, override_select_action=None):
# list_item.setLabel2(episodeDetails)
- production_year = item.get("ProductionYear")
- if not production_year and item.get("PremiereDate"):
- production_year = int(item.get("PremiereDate")[:4])
-
- overlay = "0"
- playCount = "0"
+ production_year = item["ProductionYear"]
+ prem_year = item["PremiereDate"]
+ if production_year is None and prem_year is not None:
+ production_year = int(prem_year[:4])
# add progress percent
- userData = item.get("UserData")
- if (userData != None):
- if userData.get("Played") == True:
- playCount = "1"
- overlay = "5"
- else:
- overlay = "6"
-
- playBackTicks = float(userData.get("PlaybackPositionTicks"))
- if (playBackTicks != None and playBackTicks > 0):
- runTimeTicks = float(item.get("RunTimeTicks", "0"))
- if (runTimeTicks > 0):
- playBackPos = int(((playBackTicks / 1000) / 10000) / 60)
- list_item.setProperty('ResumeTime', str(playBackPos))
-
- percentage = int((playBackTicks / runTimeTicks) * 100.0)
- list_item.setProperty("complete_percentage", str(percentage))
+ userData = item["UserData"]
+ if userData["Played"] == True:
+ playCount = "1"
+ overlay = "5"
+ else:
+ playCount = "0"
+ overlay = "6"
+
+ runtime = item["RunTimeTicks"]
+ playBackTicks = userData["PlaybackPositionTicks"]
+
+ if playBackTicks is not None and runtime is not None and runtime > 0:
+ runtime = float(runtime)
+ playBackTicks = float(playBackTicks)
+ playBackPos = int(((playBackTicks / 1000) / 10000) / 60)
+ list_item.setProperty('ResumeTime', str(playBackPos))
+ percentage = int((playBackTicks / runtime) * 100.0)
+ list_item.setProperty("complete_percentage", str(percentage))
video_info_label = {"title": title,
"tvshowtitle": tvshowtitle,
@@ -295,13 +301,14 @@ def populateWidgetItems(itemsUrl, override_select_action=None):
list_item.setProperty('discart', art['discart']) # not avail to setArt
list_item.setArt(art)
# add count
- list_item.setProperty("item_index", str(itemCount))
- itemCount = itemCount + 1
+ #list_item.setProperty("item_index", str(itemCount))
+ #itemCount = itemCount + 1
- list_item.setProperty('IsPlayable', 'true')
+ list_item.setProperty('IsPlayable', 'false')
- totalTime = str(int(float(item.get("RunTimeTicks", "0")) / (10000000 * 60)))
- list_item.setProperty('TotalTime', str(totalTime))
+ if runtime is not None:
+ totalTime = str(int(float(runtime) / (10000000 * 60)))
+ list_item.setProperty('TotalTime', str(totalTime))
list_item.setProperty('id', item_id)
@@ -320,7 +327,7 @@ def populateWidgetItems(itemsUrl, override_select_action=None):
def getWidgetContent(handle, params):
- log.debug("getWigetContent Called" + str(params))
+ log.debug("getWigetContent Called: {0}", params)
type = params.get("type")
if (type == None):
diff --git a/plugin.video.embycon/resources/settings.xml b/plugin.video.embycon/resources/settings.xml
index 301caf6..ddf3b8d 100644
--- a/plugin.video.embycon/resources/settings.xml
+++ b/plugin.video.embycon/resources/settings.xml
@@ -36,26 +36,28 @@
<setting id="promptDeleteMoviePercentage" type="slider" label="30220" default="100" range="5,1,100" option="int" visible="true"/>
<setting label="30121" type="lsep"/>
<setting type="sep" />
+ <setting id="forceAutoResume" type="bool" label="30322" default="true" visible="true" enable="true" />
<setting id="jump_back_amount" type="slider" label="30114" default="15" range="0,1,60" option="int" visible="true"/>
</category>
<category label="30110">
- <setting id="showLoadProgress" type="bool" label="30120" default="false" visible="true" enable="true" />
- <setting id="addCounts" type="bool" label="30116" default="true" visible="true" enable="true" />
- <setting id="addSeasonNumber" type="bool" label="30162" default="false" visible="true" enable="true" />
- <setting id="addEpisodeNumber" type="bool" label="30119" default="true" visible="true" enable="true" />
- <setting id="addResumePercent" type="bool" label="30118" default="true" visible="true" enable="true" />
+ <setting id="addCounts" type="bool" label="30116" default="false" visible="true" enable="true" />
+ <setting id="addResumePercent" type="bool" label="30118" default="false" visible="true" enable="true" />
<setting id="addSubtitleAvailable" type="bool" label="30163" default="false" visible="true" enable="true" />
<setting id="include_overview" type="bool" label="30181" default="true" visible="true" enable="true" />
<setting id="include_media" type="bool" label="30182" default="true" visible="true" enable="true" />
<setting id="include_people" type="bool" label="30183" default="false" visible="true" enable="true" />
+ <setting id="hide_unwatched_details" type="bool" label="30023" default="false" visible="true" enable="true" />
<setting id="flatten_single_season" type="bool" label="30020" default="true" visible="true" enable="true" />
+ <setting id="show_all_episodes" type="bool" label="30021" default="true" visible="true" enable="true" />
<setting id="show_x_filtered_items" type="slider" label="30018" default="20" range="5,1,100" option="int" visible="true"/>
<setting id="show_latest_unplayed" type="bool" label="30027" default="false" visible="true" enable="true" />
- <setting id="widget_select_action" type="enum" label="30026" lvalues="menu|play" default="0" visible="true"/>
- <setting id="episode_name_format" type="text" default="{SeriesName} - s{SeasonIndex}e{EpisodeIndex} - {ItemName}" label="30019" />
+ <setting id="widget_select_action" type="select" label="30026" lvalues="30313|30314" default="0" visible="true"/>
+ <setting id="episode_name_format" type="select" label="30019" default="{ItemName}" values="{ItemName}|s{SeasonIndex}e{EpisodeIndex} - {ItemName}|{SeriesName} - s{SeasonIndex}e{EpisodeIndex} - {ItemName}" />
</category>
<category label="30022"> <!-- Advanced -->
<setting id="profile" type="bool" label="30010" default="false" visible="true" enable="true" />
- <setting id="cacheEmbyData" type="bool" label="30138" default="false" visible="true" enable="true" />
+ <setting id="showLoadProgress" type="bool" label="30120" default="false" visible="true" enable="true" />
+ <setting id="remoteControl" type="bool" label="30310" default="false" visible="true" enable="true" />
+ <setting id="suppressErrors" type="bool" label="30315" default="false" visible="true" enable="true" />
</category>
</settings> \ No newline at end of file
diff --git a/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.1.xml b/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.1.xml
index cb9b540..6742452 100644
--- a/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.1.xml
+++ b/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.1.xml
@@ -88,7 +88,7 @@
<param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&amp;type=inprogress_episodes&amp;reload=$INFO[Window(Home).Property(plugin.video.embycon-embycon_widget_reload)]"/>
<param name="widget_header" value="In Progress"/>
<param name="widget_target" value="videos"/>
- <param name="list_id" value="4100"/>
+ <param name="list_id" value="4300"/>
</include>
<include content="WidgetListEpisodes" condition="true">
<param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&amp;type=nextup_episodes&amp;reload=$INFO[Window(Home).Property(plugin.video.embycon-embycon_widget_reload)]"/>
diff --git a/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3-elec.xml b/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3-elec.xml
index cb996ed..bfc4121 100644
--- a/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3-elec.xml
+++ b/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3-elec.xml
@@ -89,7 +89,7 @@
<param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&amp;type=inprogress_episodes&amp;reload=$INFO[Window(Home).Property(plugin.video.embycon-embycon_widget_reload)]"/>
<param name="widget_header" value="In Progress"/>
<param name="widget_target" value="videos"/>
- <param name="list_id" value="4100"/>
+ <param name="list_id" value="4300"/>
</include>
<include content="WidgetListEpisodes" condition="true">
<param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&amp;type=nextup_episodes&amp;reload=$INFO[Window(Home).Property(plugin.video.embycon-embycon_widget_reload)]"/>
diff --git a/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3.xml b/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3.xml
index 8c73c20..14cf80e 100644
--- a/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3.xml
+++ b/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3.xml
@@ -88,7 +88,7 @@
<param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&amp;type=inprogress_episodes&amp;reload=$INFO[Window(Home).Property(plugin.video.embycon-embycon_widget_reload)]"/>
<param name="widget_header" value="In Progress"/>
<param name="widget_target" value="videos"/>
- <param name="list_id" value="4100"/>
+ <param name="list_id" value="4300"/>
</include>
<include content="WidgetListEpisodes" condition="true">
<param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&amp;type=nextup_episodes&amp;reload=$INFO[Window(Home).Property(plugin.video.embycon-embycon_widget_reload)]"/>
diff --git a/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.6.xml b/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.6.xml
new file mode 100644
index 0000000..13987e7
--- /dev/null
+++ b/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.6.xml
@@ -0,0 +1,1080 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<window>
+ <onunload>ClearProperty(first_load_done, 10000)</onunload>
+ <defaultcontrol>9000</defaultcontrol>
+ <backgroundcolor>background</backgroundcolor>
+ <controls>
+ <control type="button" id="20000">
+ <include>HiddenObject</include>
+ <animation effect="fade" time="300" start="100" end="0">Focus</animation>
+ <onfocus>SetFocus(2000)</onfocus>
+ <onclick>noop</onclick>
+ <visible allowhiddenfocus="true">Control.HasFocus(20000)</visible>
+ </control>
+ <control type="button" id="20001">
+ <include>HiddenObject</include>
+ <animation effect="fade" time="300" start="100" end="0">Focus</animation>
+ <onfocus>SetFocus(2000)</onfocus>
+ <onclick>noop</onclick>
+ <visible allowhiddenfocus="true">Control.HasFocus(20001)</visible>
+ </control>
+ <include>DefaultBackground</include>
+ <control type="multiimage">
+ <depth>DepthBackground</depth>
+ <include>FullScreenDimensions</include>
+ <aspectratio>scale</aspectratio>
+ <fadetime>600</fadetime>
+ <animation effect="zoom" center="auto" end="102,102" time="0" condition="Integer.IsGreater(System.StereoscopicMode,0)">conditional</animation>
+ <animation effect="fade" start="0" end="100" time="400">WindowOpen</animation>
+ <animation effect="fade" start="100" end="0" time="300">WindowClose</animation>
+ <animation effect="fade" time="400">VisibleChange</animation>
+ <imagepath background="true" colordiffuse="bg_overlay">$VAR[HomeFanartVar]</imagepath>
+ <visible>!Player.HasMedia</visible>
+ </control>
+ <control type="group">
+ <animation effect="fade" start="100" end="0" time="200" tween="sine" condition="$EXP[infodialog_active]">Conditional</animation>
+ <control type="group" id="2000">
+ <left>462</left>
+ <animation type="Conditional" condition="Control.IsVisible(20000)" reversible="false">
+ <effect type="slide" end="0,20" time="60" tween="sine" />
+ <effect type="slide" end="0,-20" time="180" tween="sine" delay="80" />
+ </animation>
+ <animation type="Conditional" condition="Control.IsVisible(20001)" reversible="false">
+ <effect type="slide" end="0,-20" time="60" tween="sine" />
+ <effect type="slide" end="0,20" time="180" tween="sine" delay="80" />
+ </animation>
+ <include>OpenClose_Right</include>
+ <!-- Start of Emby Widget Items -->
+ <control type="group" id="3000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),emby_movies)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="emby_movies"/>
+ </include>
+ <control type="grouplist" id="3001">
+ <include>WidgetGroupListCommon</include>
+ <include content="WidgetListPoster" condition="true">
+ <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&amp;type=recent_movies&amp;reload=$INFO[Window(Home).Property(plugin.video.embycon-embycon_widget_reload)]"/>
+ <param name="widget_header" value="Recently Added"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="3800"/>
+ </include>
+ <include content="WidgetListPoster" condition="true">
+ <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&amp;type=inprogress_movies&amp;reload=$INFO[Window(Home).Property(plugin.video.embycon-embycon_widget_reload)]"/>
+ <param name="widget_header" value="In Progress"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="3900"/>
+ </include>
+ <include content="WidgetListPoster" condition="true">
+ <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&amp;type=random_movies&amp;reload=$INFO[Window(Home).Property(plugin.video.embycon-embycon_widget_reload)]"/>
+ <param name="widget_header" value="Random"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="3100"/>
+ </include>
+ </control>
+ </control>
+ <control type="group" id="4000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),emby_tvshows)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="emby_tvshows"/>
+ </include>
+ <control type="grouplist" id="4001">
+ <include>WidgetGroupListCommon</include>
+ <include content="WidgetListEpisodes" condition="true">
+ <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&amp;type=recent_episodes&amp;reload=$INFO[Window(Home).Property(plugin.video.embycon-embycon_widget_reload)]"/>
+ <param name="widget_header" value="Recently Added"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="4900"/>
+ </include>
+ <include content="WidgetListEpisodes" condition="true">
+ <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&amp;type=inprogress_episodes&amp;reload=$INFO[Window(Home).Property(plugin.video.embycon-embycon_widget_reload)]"/>
+ <param name="widget_header" value="In Progress"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="4300"/>
+ </include>
+ <include content="WidgetListEpisodes" condition="true">
+ <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&amp;type=nextup_episodes&amp;reload=$INFO[Window(Home).Property(plugin.video.embycon-embycon_widget_reload)]"/>
+ <param name="widget_header" value="Next Up"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="4200"/>
+ </include>
+ </control>
+ </control>
+ <!-- End of Emby Widget Items -->
+ <control type="group" id="5000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),movies)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="movies"/>
+ </include>
+ <control type="grouplist" id="5001">
+ <include>WidgetGroupListCommon</include>
+ <include content="WidgetListCategories" condition="Library.HasContent(movies)">
+ <param name="content_path" value="library://video/movies/"/>
+ <param name="widget_header" value="$LOCALIZE[31148]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="5900"/>
+ </include>
+ <include content="WidgetListPoster" condition="Library.HasContent(movies)">
+ <param name="content_path" value="special://skin/playlists/inprogress_movies.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[31010]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="5100"/>
+ </include>
+ <include content="WidgetListPoster" condition="Library.HasContent(movies)">
+ <param name="content_path" value="special://skin/playlists/recent_unwatched_movies.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[20386]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="5200"/>
+ </include>
+ <include content="WidgetListPoster" condition="Library.HasContent(movies)">
+ <param name="content_path" value="special://skin/playlists/unwatched_movies.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[31007]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="5300"/>
+ </include>
+ <include content="WidgetListPoster" condition="Library.HasContent(movies)">
+ <param name="content_path" value="special://skin/playlists/random_movies.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[31006]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="5400"/>
+ </include>
+ <include content="WidgetListCategories" condition="Library.HasContent(movies)">
+ <param name="content_path" value="videodb://movies/genres/"/>
+ <param name="widget_header" value="$LOCALIZE[135]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="5500"/>
+ <param name="icon" value="$VAR[WidgetGenreIconVar]"/>
+ <param name="icon_height" value="70"/>
+ </include>
+ <include content="WidgetListPoster" condition="Library.HasContent(movies)">
+ <param name="content_path" value="videodb://movies/sets/"/>
+ <param name="widget_header" value="$LOCALIZE[31075]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="sortby" value="random"/>
+ <param name="list_id" value="5600"/>
+ </include>
+ </control>
+ <include content="ImageWidget" condition="!Library.HasContent(movies)">
+ <param name="text_label" value="$LOCALIZE[31104]" />
+ <param name="button_label" value="$LOCALIZE[31110]" />
+ <param name="button_onclick" value="ActivateWindow(videos,files,return)"/>
+ <param name="button_id" value="5500"/>
+ <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoMovieButton)"/>
+ </include>
+ </control>
+ <control type="group" id="6000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),tvshows)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="tvshows"/>
+ </include>
+ <control type="grouplist" id="6001">
+ <include>WidgetGroupListCommon</include>
+ <include content="WidgetListCategories" condition="Library.HasContent(tvshows)">
+ <param name="content_path" value="library://video/tvshows/"/>
+ <param name="widget_header" value="$LOCALIZE[31148]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="6900"/>
+ </include>
+ <include content="WidgetListPoster" condition="Library.HasContent(tvshows)">
+ <param name="content_path" value="videodb://inprogresstvshows"/>
+ <param name="sortby" value="lastplayed"/>
+ <param name="sortorder" value="descending"/>
+ <param name="widget_header" value="$LOCALIZE[626]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="6100"/>
+ </include>
+ <include content="WidgetListEpisodes" condition="Library.HasContent(tvshows)">
+ <param name="content_path" value="special://skin/playlists/recent_unwatched_episodes.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[20387]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="6200"/>
+ </include>
+ <include content="WidgetListPoster" condition="Library.HasContent(tvshows)">
+ <param name="content_path" value="special://skin/playlists/unwatched_tvshows.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[31122]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="6300"/>
+ </include>
+ <include content="WidgetListCategories" condition="Library.HasContent(tvshows)">
+ <param name="content_path" value="videodb://tvshows/genres/"/>
+ <param name="widget_header" value="$LOCALIZE[135]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="6400"/>
+ <param name="icon" value="$VAR[WidgetGenreIconVar]"/>
+ <param name="icon_height" value="70"/>
+ </include>
+ <include content="WidgetListCategories" condition="Library.HasContent(tvshows)">
+ <param name="content_path" value="videodb://tvshows/studios/"/>
+ <param name="widget_header" value="$LOCALIZE[20388]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="6500"/>
+ <param name="icon" value="$VAR[WidgetStudioIconVar]"/>
+ <param name="icon_height" value="70"/>
+ </include>
+ </control>
+ <include content="ImageWidget" condition="!Library.HasContent(tvshows)">
+ <param name="text_label" value="$LOCALIZE[31104]" />
+ <param name="button_label" value="$LOCALIZE[31110]" />
+ <param name="button_onclick" value="ActivateWindow(videos,files,return)"/>
+ <param name="button_id" value="6400"/>
+ <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoTVShowButton)"/>
+ </include>
+ </control>
+ <control type="group" id="7000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),music)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="music"/>
+ </include>
+ <control type="grouplist" id="7001">
+ <include>WidgetGroupListCommon</include>
+ <include content="WidgetListCategories" condition="Library.HasContent(music)">
+ <param name="content_path" value="library://music/"/>
+ <param name="widget_header" value="$LOCALIZE[31148]"/>
+ <param name="widget_target" value="music"/>
+ <param name="list_id" value="7900"/>
+ </include>
+ <include content="WidgetListSquare" condition="Library.HasContent(music)">
+ <param name="content_path" value="musicdb://recentlyplayedalbums"/>
+ <param name="widget_header" value="$LOCALIZE[517]"/>
+ <param name="widget_target" value="music"/>
+ <param name="list_id" value="7100"/>
+ <param name="fallback_icon" value="DefaultMusicAlbums.png"/>
+ </include>
+ <include content="WidgetListSquare" condition="Library.HasContent(music)">
+ <param name="content_path" value="musicdb://recentlyaddedalbums/"/>
+ <param name="widget_header" value="$LOCALIZE[359]"/>
+ <param name="widget_target" value="music"/>
+ <param name="list_id" value="7200"/>
+ <param name="fallback_icon" value="DefaultMusicAlbums.png"/>
+ </include>
+ <include content="WidgetListSquare" condition="Library.HasContent(music)">
+ <param name="content_path" value="special://skin/playlists/random_albums.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[31012]"/>
+ <param name="widget_target" value="music"/>
+ <param name="list_id" value="7300"/>
+ <param name="fallback_icon" value="DefaultMusicAlbums.png"/>
+ </include>
+ <include content="WidgetListSquare" condition="Library.HasContent(music)">
+ <param name="content_path" value="special://skin/playlists/random_artists.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[31013]"/>
+ <param name="widget_target" value="music"/>
+ <param name="list_id" value="7400"/>
+ <param name="fallback_icon" value="DefaultMusicArtists.png"/>
+ </include>
+ <include content="WidgetListSquare" condition="Library.HasContent(music)">
+ <param name="content_path" value="special://skin/playlists/unplayed_albums.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[31014]"/>
+ <param name="widget_target" value="music"/>
+ <param name="list_id" value="7500"/>
+ <param name="fallback_icon" value="DefaultMusicAlbums.png"/>
+ </include>
+ <include content="WidgetListSquare" condition="Library.HasContent(music)">
+ <param name="content_path" value="special://skin/playlists/mostplayed_albums.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[31011]"/>
+ <param name="widget_target" value="music"/>
+ <param name="list_id" value="7600"/>
+ <param name="fallback_icon" value="DefaultMusicAlbums.png"/>
+ <param name="sortby" value="playcount"/>
+ <param name="sortorder" value="descending"/>
+ </include>
+ </control>
+ <include content="ImageWidget" condition="!Library.HasContent(music)">
+ <param name="text_label" value="$LOCALIZE[31104]" />
+ <param name="button_label" value="$LOCALIZE[31110]" />
+ <param name="button_onclick" value="ActivateWindow(music,files)"/>
+ <param name="button_id" value="7600"/>
+ <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoMusicButton)"/>
+ </include>
+ </control>
+ <control type="group" id="8000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),addons)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="addons"/>
+ </include>
+ <control type="grouplist" id="8001">
+ <include>WidgetGroupListCommon</include>
+ <include content="WidgetListCategories">
+ <param name="widget_header" value="$LOCALIZE[31148]"/>
+ <param name="list_id" value="8900"/>
+ <param name="visible" value="Integer.IsGreater(Container(8100).NumItems,0) | Integer.IsGreater(Container(8200).NumItems,0) | Integer.IsGreater(Container(8300).NumItems,0) | Integer.IsGreater(Container(8400).NumItems,0) | Integer.IsGreater(Container(8500).NumItems,0)"/>
+ <param name="addon_submenu" value="true"/>
+ </include>
+ <include content="WidgetListSquare">
+ <param name="content_path" value="addons://sources/video/"/>
+ <param name="widget_header" value="$LOCALIZE[1037]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="sortby" value="lastused"/>
+ <param name="sortorder" value="descending"/>
+ <param name="list_id" value="8100"/>
+ <param name="fallback_icon" value="DefaultAddon.png"/>
+ </include>
+ <include content="WidgetListSquare">
+ <param name="content_path" value="addons://sources/audio/"/>
+ <param name="widget_header" value="$LOCALIZE[1038]"/>
+ <param name="widget_target" value="music"/>
+ <param name="sortby" value="lastused"/>
+ <param name="sortorder" value="descending"/>
+ <param name="list_id" value="8200"/>
+ <param name="fallback_icon" value="DefaultAddon.png"/>
+ </include>
+ <include content="WidgetListSquare">
+ <param name="content_path" value="addons://sources/executable/"/>
+ <param name="widget_header" value="$LOCALIZE[1043]"/>
+ <param name="widget_target" value="programs"/>
+ <param name="sortby" value="lastused"/>
+ <param name="sortorder" value="descending"/>
+ <param name="list_id" value="8300"/>
+ <param name="fallback_icon" value="DefaultAddon.png"/>
+ </include>
+ <include content="WidgetListSquare" condition="System.Platform.Android">
+ <param name="content_path" value="androidapp://sources/apps/"/>
+ <param name="widget_header" value="$LOCALIZE[20244]"/>
+ <param name="widget_target" value="programs"/>
+ <param name="sortby" value="lastused"/>
+ <param name="sortorder" value="descending"/>
+ <param name="list_id" value="8400"/>
+ <param name="fallback_icon" value="DefaultAddon.png"/>
+ </include>
+ <include content="WidgetListSquare">
+ <param name="content_path" value="addons://sources/image/"/>
+ <param name="widget_header" value="$LOCALIZE[1039]"/>
+ <param name="widget_target" value="pictures"/>
+ <param name="sortby" value="lastused"/>
+ <param name="sortorder" value="descending"/>
+ <param name="list_id" value="8500"/>
+ </include>
+ </control>
+ <include content="ImageWidget">
+ <param name="text_label" value="$LOCALIZE[31119]" />
+ <param name="button_label" value="$LOCALIZE[31118]" />
+ <param name="button_onclick" value="ActivateWindow(addonbrowser)"/>
+ <param name="button_id" value="8600"/>
+ <param name="visible" value="!Integer.IsGreater(Container(8001).NumItems,0)"/>
+ <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoProgramsButton)"/>
+ </include>
+ </control>
+ <control type="group" id="11000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),video)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="video"/>
+ </include>
+ <control type="grouplist" id="11001">
+ <include>WidgetGroupListCommon</include>
+ <include content="WidgetListCategories">
+ <param name="content_path" value="library://video/"/>
+ <param name="widget_header" value="$LOCALIZE[31148]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="11900"/>
+ </include>
+ <include content="WidgetListCategories">
+ <param name="content_path" value="sources://video/"/>
+ <param name="widget_header" value="$LOCALIZE[20094]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="11100"/>
+ </include>
+ <include content="WidgetListCategories">
+ <param name="content_path" value="special://videoplaylists/"/>
+ <param name="widget_header" value="$LOCALIZE[136]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="11200"/>
+ <param name="icon" value="DefaultPlaylist.png"/>
+ </include>
+ </control>
+ <include content="ImageWidget">
+ <param name="text_label" value="$LOCALIZE[31105]" />
+ <param name="button_label" value="$LOCALIZE[31110]" />
+ <param name="button_onclick" value="ActivateWindow(videos,root)"/>
+ <param name="button_id" value="11300"/>
+ <param name="visible" value="!Integer.IsGreater(Container(11001).NumItems,0)"/>
+ <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoVideosButton)"/>
+ </include>
+ </control>
+ <control type="group" id="12000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),livetv)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="livetv"/>
+ </include>
+ <control type="grouplist" id="12001">
+ <include>WidgetGroupListCommon</include>
+ <control type="grouplist" id="12855">
+ <height>390</height>
+ <left>0</left>
+ <right>0</right>
+ <top>36</top>
+ <orientation>horizontal</orientation>
+ <visible>PVR.IsRecordingTV | PVR.HasNonRecordingTVTimer</visible>
+ <align>center</align>
+ <control type="group">
+ <width>674</width>
+ <visible>PVR.IsRecordingTV</visible>
+ <include content="PVRWidget">
+ <param name="icon" value="$INFO[PVR.TVNowRecordingChannelIcon]" />
+ <param name="header" value="$LOCALIZE[19158]" />
+ <param name="label1" value="$INFO[PVR.TVNowRecordingDateTime]" />
+ <param name="label2" value="$INFO[PVR.TVNowRecordingTitle][CR][COLOR=grey]$INFO[PVR.TVNowRecordingChannel][/COLOR]" />
+ </include>
+ </control>
+ <control type="group">
+ <width>674</width>
+ <visible>PVR.HasNonRecordingTVTimer</visible>
+ <include content="PVRWidget">
+ <param name="icon" value="$INFO[PVR.TVNextRecordingChannelIcon]" />
+ <param name="header" value="$LOCALIZE[19157]" />
+ <param name="label1" value="$INFO[PVR.TVNextRecordingDateTime]" />
+ <param name="label2" value="$INFO[PVR.TVNextRecordingTitle][CR][COLOR=grey]$INFO[PVR.TVNextRecordingChannel][/COLOR]" />
+ </include>
+ </control>
+ </control>
+ <include content="WidgetListCategories" condition="System.HasPVRAddon">
+ <param name="widget_header" value="$LOCALIZE[31148]"/>
+ <param name="list_id" value="12900"/>
+ <param name="pvr_submenu" value="true"/>
+ <param name="pvr_type" value="TV"/>
+ </include>
+ <include content="WidgetListChannels" condition="System.HasPVRAddon">
+ <param name="content_path" value="pvr://channels/tv/*?view=lastplayed"/>
+ <param name="sortby" value="lastplayed"/>
+ <param name="sortorder" value="descending"/>
+ <param name="widget_header" value="$LOCALIZE[31016]"/>
+ <param name="widget_target" value="pvr"/>
+ <param name="list_id" value="12200"/>
+ </include>
+ <include content="WidgetListChannels" condition="System.HasPVRAddon">
+ <param name="content_path" value="pvr://recordings/tv/active?view=flat"/>
+ <param name="sortby" value="date"/>
+ <param name="sortorder" value="descending"/>
+ <param name="widget_header" value="$LOCALIZE[31015]"/>
+ <param name="widget_target" value="pvr"/>
+ <param name="list_id" value="12300"/>
+ <param name="label" value="$INFO[ListItem.ChannelName]"/>
+ <param name="label2" value="$INFO[ListItem.Title]$INFO[ListItem.EpisodeName, (,)]"/>
+ </include>
+ </control>
+ <include content="ImageWidget" condition="!System.HasPVRAddon">
+ <param name="text_label" value="$LOCALIZE[31143]" />
+ <param name="button_label" value="$LOCALIZE[31144]" />
+ <param name="button_onclick" value="ActivateWindow(addonbrowser,addons://user/xbmc.pvrclient,return)"/>
+ <param name="button_id" value="12400"/>
+ <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoTVButton)"/>
+ </include>
+ </control>
+ <control type="group" id="13000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),radio)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="radio"/>
+ </include>
+ <control type="grouplist" id="13001">
+ <include>WidgetGroupListCommon</include>
+ <control type="grouplist" id="13855">
+ <height>390</height>
+ <left>25</left>
+ <top>36</top>
+ <orientation>horizontal</orientation>
+ <align>right</align>
+ <width>1360</width>
+ <visible>PVR.IsRecordingRadio | PVR.HasNonRecordingRadioTimer</visible>
+ <control type="group">
+ <width>680</width>
+ <visible>PVR.IsRecordingRadio</visible>
+ <include content="PVRWidget">
+ <param name="icon" value="$INFO[PVR.RadioNowRecordingChannelIcon]" />
+ <param name="header" value="$LOCALIZE[19158]" />
+ <param name="label1" value="$INFO[PVR.RadioNowRecordingDateTime]" />
+ <param name="label2" value="$INFO[PVR.RadioNowRecordingTitle][CR][COLOR=grey]$INFO[PVR.RadioNowRecordingChannel][/COLOR]" />
+ </include>
+ </control>
+ <control type="group">
+ <visible>PVR.HasNonRecordingRadioTimer</visible>
+ <width>680</width>
+ <include content="PVRWidget">
+ <param name="icon" value="$INFO[PVR.RadioNextRecordingChannelIcon]" />
+ <param name="header" value="$LOCALIZE[19157]" />
+ <param name="label1" value="$INFO[PVR.RadioNextRecordingDateTime]" />
+ <param name="label2" value="$INFO[PVR.RadioNextRecordingTitle][CR][COLOR=grey]$INFO[PVR.RadioNextRecordingChannel][/COLOR]" />
+ </include>
+ </control>
+ </control>
+ <include content="WidgetListCategories" condition="System.HasPVRAddon">
+ <param name="widget_header" value="$LOCALIZE[31148]"/>
+ <param name="list_id" value="13900"/>
+ <param name="pvr_submenu" value="true"/>
+ <param name="pvr_type" value="Radio"/>
+ </include>
+ <include content="WidgetListChannels" condition="System.HasPVRAddon">
+ <param name="content_path" value="pvr://channels/radio/*?view=lastplayed"/>
+ <param name="sortby" value="lastplayed"/>
+ <param name="sortorder" value="descending"/>
+ <param name="widget_header" value="$LOCALIZE[31018]"/>
+ <param name="widget_target" value="files"/>
+ <param name="list_id" value="13200"/>
+ </include>
+ <include content="WidgetListChannels" condition="System.HasPVRAddon">
+ <param name="content_path" value="pvr://recordings/radio/active?view=flat"/>
+ <param name="sortby" value="date"/>
+ <param name="sortorder" value="descending"/>
+ <param name="widget_header" value="$LOCALIZE[31015]"/>
+ <param name="widget_target" value="pvr"/>
+ <param name="list_id" value="13300"/>
+ <param name="label" value="$INFO[ListItem.ChannelName]"/>
+ <param name="label2" value="$INFO[ListItem.Title]$INFO[ListItem.EpisodeName, (,)]"/>
+ </include>
+ </control>
+ <include content="ImageWidget" condition="!System.HasPVRAddon">
+ <param name="text_label" value="$LOCALIZE[31143]" />
+ <param name="button_label" value="$LOCALIZE[31144]" />
+ <param name="button_onclick" value="ActivateWindow(addonbrowser,addons://user/xbmc.pvrclient,return)"/>
+ <param name="button_id" value="13400"/>
+ <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoRadioButton)"/>
+ </include>
+ </control>
+ <control type="group" id="14000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),favorites)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="favorites"/>
+ </include>
+ <control type="panel" id="14100">
+ <left>65</left>
+ <top>0</top>
+ <right>0</right>
+ <bottom>0</bottom>
+ <onleft>9000</onleft>
+ <onright>9000</onright>
+ <onup>14100</onup>
+ <ondown>14100</ondown>
+ <onclick>$INFO[ListItem.FileNameAndPath]</onclick>
+ <preloaditems>2</preloaditems>
+ <scrolltime tween="cubic" easing="out">500</scrolltime>
+ <orientation>vertical</orientation>
+ <visible>Integer.IsGreater(Container(14100).NumItems,0) | Container(14100).IsUpdating</visible>
+ <itemlayout width="330" height="396">
+ <control type="group">
+ <top>130</top>
+ <include content="InfoWallMusicLayout">
+ <param name="fallback_image" value="DefaultFavourites.png" />
+ <param name="focused" value="false" />
+ </include>
+ </control>
+ </itemlayout>
+ <focusedlayout width="330" height="396">
+ <control type="group">
+ <depth>DepthContentPopout</depth>
+ <top>130</top>
+ <animation effect="zoom" start="100" end="110" time="200" tween="sine" easing="inout" center="170,320">Focus</animation>
+ <animation effect="zoom" start="110" end="100" time="200" tween="sine" easing="inout" center="170,320">UnFocus</animation>
+ <include content="InfoWallMusicLayout">
+ <param name="fallback_image" value="DefaultFavourites.png" />
+ <param name="focused" value="true" />
+ </include>
+ </control>
+ </focusedlayout>
+ <content>favourites://</content>
+ </control>
+ <include content="ImageWidget">
+ <param name="text_label" value="$LOCALIZE[31025]" />
+ <param name="button_label" value="$LOCALIZE[31116]" />
+ <param name="button_onclick" value=""/>
+ <param name="button_id" value="5500"/>
+ <param name="visible" value="!Integer.IsGreater(Container(14100).NumItems,0) + !Container(14100).IsUpdating"/>
+ <param name="visible_1" value="false"/>
+ <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoFavButton)"/>
+ </include>
+ </control>
+ <control type="group" id="15000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),weather)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="weather"/>
+ </include>
+ <control type="grouplist" id="15001">
+ <include>WidgetGroupListCommon</include>
+ <control type="group" id="16678">
+ <description>Weather info</description>
+ <left>68</left>
+ <right>70</right>
+ <top>102</top>
+ <height>300</height>
+ <visible>!String.IsEmpty(Weather.plugin)</visible>
+ <control type="image">
+ <bottom>90</bottom>
+ <width>100%</width>
+ <texture border="21">dialogs/dialog-bg.png</texture>
+ </control>
+ <control type="label">
+ <left>840</left>
+ <top>60</top>
+ <aligny>center</aligny>
+ <height>24</height>
+ <right>60</right>
+ <align>right</align>
+ <font>font30_title</font>
+ <label>$INFO[Weather.Location]</label>
+ </control>
+ <control type="label">
+ <left>840</left>
+ <top>120</top>
+ <aligny>center</aligny>
+ <height>24</height>
+ <right>60</right>
+ <align>right</align>
+ <font>font14</font>
+ <label>$INFO[Weather.Conditions,, ∙ ]$INFO[Weather.Temperature]</label>
+ </control>
+ <control type="grouplist">
+ <top>50</top>
+ <left>50</left>
+ <right>20</right>
+ <orientation>horizontal</orientation>
+ <align>left</align>
+ <itemgap>-110</itemgap>
+ <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)">
+ <param name="label" value="Window(weather).Property(Current.Wind)" />
+ <param name="texture" value="icons/weather/wind.png" />
+ <param name="header" value="404" />
+ </include>
+ <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)">
+ <param name="label" value="Window(weather).Property(Current.Humidity)" />
+ <param name="texture" value="icons/weather/humidity.png" />
+ <param name="header" value="406" />
+ </include>
+ <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)">
+ <param name="label" value="Window(weather).Property(Current.Precipitation)" />
+ <param name="texture" value="icons/weather/rain.png" />
+ <param name="header" value="33021" />
+ </include>
+ <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)">
+ <param name="label" value="Window(weather).Property(Today.Sunrise)" />
+ <param name="texture" value="icons/weather/sunrise.png" />
+ <param name="header" value="405" />
+ </include>
+ <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)">
+ <param name="label" value="Window(weather).Property(Today.Sunset)" />
+ <param name="texture" value="icons/weather/sunset.png" />
+ <param name="header" value="403" />
+ </include>
+ </control>
+ </control>
+ <include content="WeatherWidget" condition="!String.IsEmpty(Weather.Plugin)">
+ <param name="content_include" value="DailyItems" />
+ <param name="list_id" value="15200" />
+ <param name="widget_header" value="$LOCALIZE[31019]"/>
+ <param name="visible" value="!String.IsEmpty(Window(weather).Property(Daily.IsFetched))" />
+ </include>
+ <include content="WeatherWidget" condition="!String.IsEmpty(Weather.Plugin)">
+ <param name="content_include" value="HourlyItems" />
+ <param name="list_id" value="15100" />
+ <param name="widget_header" value="$LOCALIZE[33036]"/>
+ <param name="visible" value="!String.IsEmpty(Window(weather).Property(Hourly.IsFetched))" />
+ </include>
+ </control>
+ <include content="ImageWidget" condition="String.IsEmpty(Weather.plugin)">
+ <param name="text_label" value="$LOCALIZE[31120]" />
+ <param name="button_label" value="$LOCALIZE[31121]" />
+ <param name="button_onclick" value="ActivateWindow(servicesettings,weather)"/>
+ <param name="button_id" value="15300"/>
+ <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoWeatherButton)"/>
+ </include>
+ </control>
+ <control type="group" id="16000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),musicvideos)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="musicvideos"/>
+ </include>
+ <control type="grouplist" id="16001">
+ <include>WidgetGroupListCommon</include>
+ <include content="WidgetListCategories" condition="Library.HasContent(musicvideos)">
+ <param name="content_path" value="library://music/musicvideos/"/>
+ <param name="widget_header" value="$LOCALIZE[31148]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="list_id" value="16900"/>
+ </include>
+ <include content="WidgetListEpisodes" condition="Library.HasContent(musicvideos)">
+ <param name="content_path" value="videodb://recentlyaddedmusicvideos/"/>
+ <param name="widget_header" value="$LOCALIZE[20390]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="main_label" value="$INFO[ListItem.Label]" />
+ <param name="sub_label" value="$INFO[ListItem.Artist]" />
+ <param name="thumb_label" value="$INFO[ListItem.Year]" />
+ <param name="fallback_image" value="DefaultMusicSongs.png" />
+ <param name="list_id" value="16300"/>
+ </include>
+ <include content="WidgetListEpisodes" condition="Library.HasContent(musicvideos)">
+ <param name="content_path" value="special://skin/playlists/unwatched_musicvideos.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[31151]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="main_label" value="$INFO[ListItem.Label]" />
+ <param name="sub_label" value="$INFO[ListItem.Artist]" />
+ <param name="thumb_label" value="$INFO[ListItem.Year]" />
+ <param name="fallback_image" value="DefaultMusicSongs.png" />
+ <param name="list_id" value="16400"/>
+ </include>
+ <include content="WidgetListSquare" condition="Library.HasContent(musicvideos)">
+ <param name="content_path" value="special://skin/playlists/random_musicvideo_artists.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[31013]"/>
+ <param name="widget_target" value="music"/>
+ <param name="list_id" value="16200"/>
+ <param name="widget_limit" value="10"/>
+ </include>
+ <include content="WidgetListEpisodes" condition="Library.HasContent(musicvideos)">
+ <param name="content_path" value="special://skin/playlists/random_musicvideos.xsp"/>
+ <param name="widget_header" value="$LOCALIZE[31152]"/>
+ <param name="widget_target" value="videos"/>
+ <param name="main_label" value="$INFO[ListItem.Label]" />
+ <param name="sub_label" value="$INFO[ListItem.Artist]" />
+ <param name="thumb_label" value="$INFO[ListItem.Year]" />
+ <param name="fallback_image" value="DefaultMusicSongs.png" />
+ <param name="list_id" value="16500"/>
+ </include>
+ <include content="WidgetListCategories" condition="Library.HasContent(musicvideos)">
+ <param name="content_path" value="videodb://musicvideos/studios/"/>
+ <param name="widget_header" value="$LOCALIZE[20388]"/>
+ <param name="widget_target" value="music"/>
+ <param name="list_id" value="16600"/>
+ <param name="icon" value="$VAR[WidgetStudioIconVar]"/>
+ <param name="icon_height" value="70"/>
+ </include>
+ </control>
+ <include content="ImageWidget" condition="!Library.HasContent(musicvideos)">
+ <param name="text_label" value="$LOCALIZE[31104]" />
+ <param name="button_label" value="$LOCALIZE[31110]" />
+ <param name="button_onclick" value="ActivateWindow(videos,files,return)"/>
+ <param name="button_id" value="16800"/>
+ <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoMusicVideoButton)"/>
+ </include>
+ </control>
+ <control type="group" id="4000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),pictures)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="pictures"/>
+ </include>
+ <control type="grouplist" id="4001">
+ <include>WidgetGroupListCommon</include>
+ <include content="WidgetListCategories" condition="!Skin.HasSetting(HomeMenuNoPicturesButton)">
+ <param name="content_path" value="sources://pictures/"/>
+ <param name="widget_header" value="$LOCALIZE[20094]"/>
+ <param name="widget_target" value="pictures"/>
+ <param name="list_id" value="4100"/>
+ </include>
+ </control>
+ </control>
+ <control type="group" id="21000">
+ <visible>String.IsEqual(Container(9000).ListItem.Property(id),disc)</visible>
+ <include content="Visible_Right_Delayed">
+ <param name="id" value="disc"/>
+ </include>
+ <include content="ImageWidget">
+ <param name="text_label" value="$INFO[System.DVDLabel]" />
+ <param name="button_label" value="$LOCALIZE[341]" />
+ <param name="button_onclick" value="PlayDisc"/>
+ <param name="button_id" value="21100"/>
+ <param name="visible" value="true"/>
+ <param name="button2_label" value="$LOCALIZE[13391]"/>
+ <param name="button2_onclick" value="EjectTray()"/>
+ </include>
+ </control>
+ </control>
+ <control type="group">
+ <depth>DepthContentPanel</depth>
+ <include>OpenClose_Left</include>
+ <include content="ContentPanel">
+ <param name="width" value="522" />
+ </include>
+ <control type="fixedlist" id="9000">
+ <left>0</left>
+ <top>240</top>
+ <width>462</width>
+ <bottom>-10</bottom>
+ <movement>6</movement>
+ <focusposition>1</focusposition>
+ <onfocus>ClearProperty(listposition,home)</onfocus>
+ <onright>SetFocus($INFO[Container(9000).ListItem.Property(menu_id)])</onright>
+ <onup>PageDown</onup>
+ <ondown>PageUp</ondown>
+ <scrolltime tween="cubic" easing="out">500</scrolltime>
+ <focusedlayout height="95">
+ <control type="group">
+ <animation effect="fade" start="100" end="0" time="0">UnFocus</animation>
+ <control type="image">
+ <left>0</left>
+ <top>0</top>
+ <width>462</width>
+ <height>95</height>
+ <texture colordiffuse="button_focus">lists/focus.png</texture>
+ <animation effect="fade" start="100" end="0" time="0" condition="[!Control.HasFocus(9000) + !ControlGroup(700).HasFocus] | System.HasModalDialog">Conditional</animation>
+ </control>
+ <control type="image">
+ <left>-3</left>
+ <top>1</top>
+ <width>95</width>
+ <height>95</height>
+ <texture colordiffuse="button_focus">$INFO[ListItem.Art(thumb)]</texture>
+ <animation effect="fade" start="0" end="100" time="300" reversible="false">Focus</animation>
+ </control>
+ <control type="image">
+ <left>0</left>
+ <top>0</top>
+ <width>95</width>
+ <height>95</height>
+ <texture colordiffuse="51FFFFFF">colors/black.png</texture>
+ <animation effect="fade" start="100" end="0" time="0" condition="[!Control.HasFocus(9000) + !ControlGroup(700).HasFocus] | System.HasModalDialog">Conditional</animation>
+ </control>
+ </control>
+ <control type="image">
+ <left>-3</left>
+ <top>1</top>
+ <width>95</width>
+ <height>95</height>
+ <texture>$INFO[ListItem.Art(thumb)]</texture>
+ </control>
+ <control type="label">
+ <left>104</left>
+ <top>0</top>
+ <height>95</height>
+ <width>560</width>
+ <aligny>center</aligny>
+ <font>font37</font>
+ <label>$INFO[ListItem.Label]</label>
+ <shadowcolor>text_shadow</shadowcolor>
+ </control>
+ </focusedlayout>
+ <itemlayout height="95">
+ <control type="image">
+ <left>-3</left>
+ <top>1</top>
+ <width>95</width>
+ <height>95</height>
+ <texture colordiffuse="44FFFFFF">$INFO[ListItem.Art(thumb)]</texture>
+ </control>
+ <control type="label">
+ <left>104</left>
+ <top>0</top>
+ <height>95</height>
+ <width>560</width>
+ <aligny>center</aligny>
+ <font>font37</font>
+ <label>$INFO[ListItem.Label]</label>
+ <shadowcolor>text_shadow</shadowcolor>
+ </control>
+ </itemlayout>
+ <content>
+ <!-- Start Emby Menu Items -->
+ <item>
+ <label>Emby Movies</label>
+ <onclick condition="String.IsEmpty(Window(Home).Property(first_load_done))">ActivateWindow(Videos,videodb://movies/titles/,return)</onclick>
+ <onclick>ActivateWindow(Videos,plugin://plugin.video.embycon/?mode=SHOW_CONTENT&amp;item_type=Movie&amp;media_type=movies,return)</onclick>
+ <onclick>SetProperty(first_load_done, done, 10000)</onclick>
+ <property name="menu_id">$NUMBER[3000]</property>
+ <thumb>icons/sidemenu/movies.png</thumb>
+ <property name="id">emby_movies</property>
+ </item>
+ <item>
+ <label>Emby TV Shows</label>
+ <onclick condition="String.IsEmpty(Window(Home).Property(first_load_done))">ActivateWindow(Videos,videodb://movies/titles/,return)</onclick>
+ <onclick>ActivateWindow(Videos,plugin://plugin.video.embycon/?mode=SHOW_CONTENT&amp;item_type=Series&amp;media_type=tvshows,return)</onclick>
+ <onclick>SetProperty(first_load_done, done, 10000)</onclick>
+ <property name="menu_id">$NUMBER[4000]</property>
+ <thumb>icons/sidemenu/tv.png</thumb>
+ <property name="id">emby_tvshows</property>
+ </item>
+ <!-- End Emby Menu Items -->
+ <item>
+ <label>$LOCALIZE[342]</label>
+ <onclick condition="Library.HasContent(movies)">ActivateWindow(Videos,videodb://movies/titles/,return)</onclick>
+ <onclick condition="!Library.HasContent(movies)">ActivateWindow(Videos,sources://video/,return)</onclick>
+ <property name="menu_id">$NUMBER[5000]</property>
+ <thumb>icons/sidemenu/movies.png</thumb>
+ <property name="id">movies</property>
+ <visible>!Skin.HasSetting(HomeMenuNoMovieButton)</visible>
+ </item>
+ <item>
+ <label>$LOCALIZE[20343]</label>
+ <onclick condition="Library.HasContent(tvshows)">ActivateWindow(Videos,videodb://tvshows/titles/,return)</onclick>
+ <onclick condition="!Library.HasContent(tvshows)">ActivateWindow(Videos,sources://video/,return)</onclick>
+ <property name="menu_id">$NUMBER[6000]</property>
+ <thumb>icons/sidemenu/tv.png</thumb>
+ <property name="id">tvshows</property>
+ <visible>!Skin.HasSetting(HomeMenuNoTVShowButton)</visible>
+ </item>
+ <item>
+ <label>$LOCALIZE[2]</label>
+ <onclick>ActivateWindow(Music,root,return)</onclick>
+ <property name="menu_id">$NUMBER[7000]</property>
+ <thumb>icons/sidemenu/music.png</thumb>
+ <property name="id">music</property>
+ <visible>!Skin.HasSetting(HomeMenuNoMusicButton)</visible>
+ </item>
+ <item>
+ <label>$LOCALIZE[427]</label>
+ <onclick>PlayDisc</onclick>
+ <property name="menu_id">$NUMBER[21000]</property>
+ <thumb>icons/sidemenu/disc.png</thumb>
+ <property name="id">disc</property>
+ <visible>System.HasMediaDVD</visible>
+ </item>
+ <item>
+ <label>$LOCALIZE[20389]</label>
+ <property name="menu_id">$NUMBER[16000]</property>
+ <onclick>ActivateWindow(Videos,musicvideos,return)</onclick>
+ <thumb>icons/sidemenu/musicvideos.png</thumb>
+ <property name="id">musicvideos</property>
+ <visible>!Skin.HasSetting(HomeMenuNoMusicVideoButton)</visible>
+ </item>
+ <item>
+ <label>$LOCALIZE[19020]</label>
+ <property name="menu_id">$NUMBER[12000]</property>
+ <onclick>ActivateWindow(TVChannels)</onclick>
+ <thumb>icons/sidemenu/livetv.png</thumb>
+ <property name="id">livetv</property>
+ <visible>!Skin.HasSetting(HomeMenuNoTVButton)</visible>
+ </item>
+ <item>
+ <label>$LOCALIZE[19021]</label>
+ <property name="menu_id">$NUMBER[13000]</property>
+ <onclick>ActivateWindow(RadioChannels)</onclick>
+ <thumb>icons/sidemenu/radio.png</thumb>
+ <property name="id">radio</property>
+ <visible>!Skin.HasSetting(HomeMenuNoRadioButton)</visible>
+ </item>
+ <item>
+ <label>$LOCALIZE[24001]</label>
+ <property name="menu_id">$NUMBER[8000]</property>
+ <onclick>ActivateWindow(1100)</onclick>
+ <thumb>icons/sidemenu/addons.png</thumb>
+ <property name="id">addons</property>
+ <visible>!Skin.HasSetting(HomeMenuNoProgramsButton)</visible>
+ </item>
+ <item>
+ <label>$LOCALIZE[1]</label>
+ <onclick>ActivateWindow(Pictures)</onclick>
+ <property name="menu_id">$NUMBER[4000]</property>
+ <thumb>icons/sidemenu/pictures.png</thumb>
+ <property name="id">pictures</property>
+ <visible>!Skin.HasSetting(HomeMenuNoPicturesButton)</visible>
+ </item>
+ <item>
+ <label>$LOCALIZE[3]</label>
+ <onclick>ActivateWindow(Videos,root)</onclick>
+ <property name="menu_id">$NUMBER[11000]</property>
+ <thumb>icons/sidemenu/videos.png</thumb>
+ <property name="id">video</property>
+ <visible>!Skin.HasSetting(HomeMenuNoVideosButton)</visible>
+ </item>
+ <item>
+ <label>$LOCALIZE[10134]</label>
+ <onclick>ActivateWindow(favourites)</onclick>
+ <property name="menu_id">$NUMBER[14000]</property>
+ <thumb>icons/sidemenu/favourites.png</thumb>
+ <property name="id">favorites</property>
+ <visible>!Skin.HasSetting(HomeMenuNoFavButton)</visible>
+ </item>
+ <item>
+ <label>$LOCALIZE[8]</label>
+ <onclick condition="!String.IsEmpty(Weather.Plugin)">ActivateWindow(Weather)</onclick>
+ <onclick condition="String.IsEmpty(Weather.Plugin)">ReplaceWindow(servicesettings,weather)</onclick>
+ <property name="menu_id">$NUMBER[15000]</property>
+ <thumb>icons/sidemenu/weather.png</thumb>
+ <property name="id">weather</property>
+ <visible>!Skin.HasSetting(HomeMenuNoWeatherButton)</visible>
+ </item>
+ <item>
+ <label>Settings</label>
+ <onclick>ActivateWindow(settings)</onclick>
+ <property name="menu_id">$NUMBER[-1]</property>
+ <thumb>icons/sidemenu/manage.png</thumb>
+ <property name="id">power</property>
+ </item>
+ <item>
+ <label>LogOff</label>
+ <onclick>ActivateWindow(shutdownmenu)</onclick>
+ <property name="menu_id">$NUMBER[-1]</property>
+ <thumb>icons/sidemenu/programs.png</thumb>
+ <property name="id">power</property>
+ </item>
+ </content>
+ </control>
+ <!--
+ <control type="grouplist" id="700">
+ <orientation>horizontal</orientation>
+ <itemgap>0</itemgap>
+ <left>-8</left>
+ <width>480</width>
+ <height>110</height>
+ <top>100</top>
+ <onup>SetFocus(9000)</onup>
+ <onup>PageDown</onup>
+ <onup>PageDown</onup>
+ <ondown>SetFocus(9000)</ondown>
+ <ondown>PageUp</ondown>
+ <ondown>PageUp</ondown>
+ <onright>2000</onright>
+ <align>justify</align>
+ <include content="IconButton">
+ <param name="control_id" value="804" />
+ <param name="onclick" value="ActivateWindow(shutdownmenu)" />
+ <param name="icon" value="icons/power.png" />
+ <param name="label" value="$LOCALIZE[33060]" />
+ </include>
+ <include content="IconButton">
+ <param name="control_id" value="802" />
+ <param name="onclick" value="ActivateWindow(settings)" />
+ <param name="icon" value="icons/settings.png" />
+ <param name="label" value="$LOCALIZE[21417]" />
+ </include>
+ <include content="IconButton">
+ <param name="control_id" value="801" />
+ <param name="onclick" value="ActivateWindow(1107)" />
+ <param name="icon" value="icons/search.png" />
+ <param name="label" value="$LOCALIZE[137]" />
+ </include>
+ <include content="IconButton">
+ <param name="control_id" value="803" />
+ <param name="onclick" value="Fullscreen" />
+ <param name="icon" value="icons/now-playing/fullscreen.png" />
+ <param name="label" value="$LOCALIZE[31000]" />
+ <param name="visible" value="Player.HasMedia" />
+ </include>
+ </control>
+ -->
+ </control>
+ <include>BottomBar</include>
+ <include content="TopBar">
+ <param name="breadcrumbs_label" value="" />
+ </include>
+ <control type="group">
+ <depth>DepthBars</depth>
+ <animation effect="slide" end="0,-90" time="300" tween="sine" easing="inout" condition="$EXP[infodialog_active]">conditional</animation>
+ <animation effect="fade" start="0" end="100" time="300">WindowOpen</animation>
+ <animation effect="fade" start="100" end="0" time="200">WindowClose</animation>
+ <top>30</top>
+ <left>90</left>
+ <control type="image">
+ <aspectratio>keep</aspectratio>
+ <width>56</width>
+ <height>56</height>
+ <texture colordiffuse="button_focus">icons/logo.png</texture>
+ </control>
+ <control type="image">
+ <left>40</left>
+ <top>10</top>
+ <aspectratio>keep</aspectratio>
+ <width>192</width>
+ <height>36</height>
+ <texture>icons/logo-text.png</texture>
+ </control>
+ </control>
+ <control type="rss">
+ <animation effect="slide" end="0,90" time="300" tween="sine" easing="inout" condition="$EXP[infodialog_active]">conditional</animation>
+ <animation effect="fade" start="0" end="100" time="400">WindowOpen</animation>
+ <animation effect="fade" start="100" end="0" time="300">WindowClose</animation>
+ <left>0</left>
+ <bottom>0</bottom>
+ <height>39</height>
+ <width>100%</width>
+ <font>font12</font>
+ <urlset>1</urlset>
+ <hitrect x="-100" y="0" w="1" h="1" />
+ <titlecolor>button_focus</titlecolor>
+ <textcolor>button_focus</textcolor>
+ <shadowcolor>text_shadow</shadowcolor>
+ <headlinecolor>FFC0C0C0</headlinecolor>
+ </control>
+ </control>
+ </controls>
+</window>
diff --git a/plugin.video.embycon/service.py b/plugin.video.embycon/service.py
index 60b1575..71b5320 100644
--- a/plugin.video.embycon/service.py
+++ b/plugin.video.embycon/service.py
@@ -1,19 +1,21 @@
# coding=utf-8
# Gnu General Public License - see LICENSE.TXT
-import xbmc
-import xbmcaddon
-import xbmcgui
import time
import json
import traceback
+import binascii
+
+import xbmc
+import xbmcaddon
+import xbmcgui
from resources.lib.downloadutils import DownloadUtils
from resources.lib.simple_logging import SimpleLogging
-from resources.lib.play_utils import playFile
+from resources.lib.play_utils import Service, PlaybackService, sendProgress
from resources.lib.kodi_utils import HomeWindow
-from resources.lib.translation import i18n
from resources.lib.widgets import checkForNewContent
+from resources.lib.websocket_client import WebSocketClient
# clear user and token when logging in
home_window = HomeWindow()
@@ -28,282 +30,46 @@ download_utils = DownloadUtils()
try:
download_utils.authenticate()
except Exception as error:
- log.error("Error with initial service auth: " + str(error))
-
-
-def hasData(data):
- if data is None or len(data) == 0 or data == "None":
- return False
- else:
- return True
-
-
-def sendProgress():
- playing_file = xbmc.Player().getPlayingFile()
- play_data = monitor.played_information.get(playing_file)
-
- if play_data is None:
- return
-
- log.debug("Sending Progress Update")
-
- play_time = xbmc.Player().getTime()
- play_data["currentPossition"] = play_time
-
- item_id = play_data.get("item_id")
- if item_id is None:
- return
-
- ticks = int(play_time * 10000000)
- paused = play_data.get("paused", False)
- playback_type = play_data.get("playback_type")
- play_session_id = play_data.get("play_session_id")
-
- postdata = {
- 'QueueableMediaTypes': "Video",
- 'CanSeek': True,
- 'ItemId': item_id,
- 'MediaSourceId': item_id,
- 'PositionTicks': ticks,
- 'IsPaused': paused,
- 'IsMuted': False,
- 'PlayMethod': playback_type,
- 'PlaySessionId': play_session_id
- }
-
- log.debug("Sending POST progress started: %s." % postdata)
-
- url = "{server}/emby/Sessions/Playing/Progress"
- download_utils.downloadUrl(url, postBody=postdata, method="POST")
-
-def promptForStopActions(item_id, current_possition):
-
- settings = xbmcaddon.Addon(id='plugin.video.embycon')
-
- prompt_next_percentage = int(settings.getSetting('promptPlayNextEpisodePercentage'))
- play_prompt = settings.getSetting('promptPlayNextEpisodePercentage_prompt') == "true"
- prompt_delete_episode_percentage = int(settings.getSetting('promptDeleteEpisodePercentage'))
- prompt_delete_movie_percentage = int(settings.getSetting('promptDeleteMoviePercentage'))
-
- # everything is off so return
- if prompt_next_percentage == 100 and prompt_delete_episode_percentage == 100 and prompt_delete_movie_percentage == 100:
- return
-
- jsonData = download_utils.downloadUrl("{server}/emby/Users/{userid}/Items/" +
- item_id + "?format=json",
- suppress=False, popup=1)
- result = json.loads(jsonData)
- prompt_to_delete = False
- runtime = result.get("RunTimeTicks", 0)
-
- # if no runtime we cant calculate perceantge so just return
- if runtime == 0:
- log.debug("No runtime so returing")
- return
-
- # item percentage complete
- percenatge_complete = int(((current_possition * 10000000) / runtime) * 100)
- log.debug("Episode Percentage Complete: %s" % percenatge_complete)
-
- if (prompt_delete_episode_percentage < 100 and
- result.get("Type", "na") == "Episode" and
- percenatge_complete > prompt_delete_episode_percentage):
- prompt_to_delete = True
-
- if (prompt_delete_movie_percentage < 100 and
- result.get("Type", "na") == "Movie" and
- percenatge_complete > prompt_delete_movie_percentage):
- prompt_to_delete = True
-
- if prompt_to_delete:
- log.debug("Prompting for delete")
- resp = xbmcgui.Dialog().yesno(i18n('confirm_file_delete'), i18n('file_delete_confirm'), autoclose=10000)
- if resp:
- log.debug("Deleting item: %s" % item_id)
- url = "{server}/emby/Items/%s?format=json" % item_id
- download_utils.downloadUrl(url, method="DELETE")
- xbmc.executebuiltin("Container.Refresh")
-
- # prompt for next episode
- if (prompt_next_percentage < 100 and
- result.get("Type", "na") == "Episode" and
- percenatge_complete > prompt_next_percentage):
- parendId = result.get("ParentId", "na")
- item_index = result.get("IndexNumber", -1)
-
- if parendId == "na":
- log.debug("No parent id, can not prompt for next episode")
- return
-
- if item_index == -1:
- log.debug("No episode number, can not prompt for next episode")
- return
-
- jsonData = download_utils.downloadUrl('{server}/emby/Users/{userid}/Items?' +
- '?Recursive=true' +
- '&ParentId=' + parendId +
- #'&Filters=IsUnplayed,IsNotFolder' +
- '&IsVirtualUnaired=false' +
- '&IsMissing=False' +
- '&IncludeItemTypes=Episode' +
- '&ImageTypeLimit=1' +
- '&format=json',
- suppress=False, popup=1)
-
- items_result = json.loads(jsonData)
- log.debug("Prompt Next Item Details: %s" % items_result)
- # find next episode
- item_list = items_result.get("Items", [])
- for item in item_list:
- index = item.get("IndexNumber", -1)
- if index == item_index + 1: # find the very next episode in the season
-
- resp = True
- if play_prompt:
- #next_epp_name = str(index) + " of " + str(item_list[-1].get("IndexNumber", -1)) + " - " + item.get("Name", "n/a")
- next_epp_name = ("%02d - " % (index,)) + item.get("Name", "n/a")
- resp = xbmcgui.Dialog().yesno(i18n("play_next_title"), i18n("play_next_question"), next_epp_name, autoclose=10000)
+ log.error("Error with initial service auth: {0}", error)
- if resp:
- next_item_id = item.get("Id")
- log.debug("Playing Next Episode: %s" % next_item_id)
-
- play_info = {}
- play_info["item_id"] = next_item_id
- play_info["auto_resume"] = "-1"
- play_info["force_transcode"] = False
- play_data = json.dumps(play_info)
-
- home_window = HomeWindow()
- home_window.setProperty("item_id", next_item_id)
- home_window.setProperty("play_item_message", play_data)
-
- break
-
-
-def stopAll(played_information):
- if len(played_information) == 0:
- return
-
- log.debug("played_information : " + str(played_information))
-
- for item_url in played_information:
- data = played_information.get(item_url)
- if data is not None:
- log.debug("item_url : " + item_url)
- log.debug("item_data : " + str(data))
-
- current_possition = data.get("currentPossition", 0)
- emby_item_id = data.get("item_id")
-
- if hasData(emby_item_id):
- log.debug("Playback Stopped at: " + str(int(current_possition * 10000000)))
-
- url = "{server}/emby/Sessions/Playing/Stopped"
- postdata = {
- 'ItemId': emby_item_id,
- 'MediaSourceId': emby_item_id,
- 'PositionTicks': int(current_possition * 10000000)
- }
- download_utils.downloadUrl(url, postBody=postdata, method="POST")
-
- promptForStopActions(emby_item_id, current_possition)
-
- played_information.clear()
-
-
-class Service(xbmc.Player):
- played_information = {}
-
- def __init__(self, *args):
- log.debug("Starting monitor service: " + str(args))
- self.played_information = {}
-
- def onPlayBackStarted(self):
- # Will be called when xbmc starts playing a file
- stopAll(self.played_information)
-
- current_playing_file = xbmc.Player().getPlayingFile()
- log.debug("onPlayBackStarted: " + current_playing_file)
-
- home_window = HomeWindow()
- emby_item_id = home_window.getProperty("item_id")
- playback_type = home_window.getProperty("PlaybackType_" + emby_item_id)
- play_session_id = home_window.getProperty("PlaySessionId_" + emby_item_id)
-
- # if we could not find the ID of the current item then return
- if emby_item_id is None or len(emby_item_id) == 0:
- return
-
- log.debug("Sending Playback Started")
- postdata = {
- 'QueueableMediaTypes': "Video",
- 'CanSeek': True,
- 'ItemId': emby_item_id,
- 'MediaSourceId': emby_item_id,
- 'PlayMethod': playback_type,
- 'PlaySessionId': play_session_id
- }
-
- log.debug("Sending POST play started: %s." % postdata)
-
- url = "{server}/emby/Sessions/Playing"
- download_utils.downloadUrl(url, postBody=postdata, method="POST")
-
- data = {}
- data["item_id"] = emby_item_id
- data["paused"] = False
- data["playback_type"] = playback_type
- data["play_session_id"] = play_session_id
- self.played_information[current_playing_file] = data
-
- log.debug("ADDING_FILE : " + current_playing_file)
- log.debug("ADDING_FILE : " + str(self.played_information))
+# set up all the services
+monitor = Service()
+playback_service = PlaybackService(monitor)
- def onPlayBackEnded(self):
- # Will be called when kodi stops playing a file
- log.debug("EmbyCon Service -> onPlayBackEnded")
- home_window = HomeWindow()
- home_window.clearProperty("item_id")
- stopAll(self.played_information)
+home_window = HomeWindow()
+last_progress_update = time.time()
+last_content_check = time.time()
+websocket_client = WebSocketClient()
- def onPlayBackStopped(self):
- # Will be called when user stops kodi playing a file
- log.debug("onPlayBackStopped")
- home_window = HomeWindow()
- home_window.clearProperty("item_id")
- stopAll(self.played_information)
+# start the WebSocket Client running
+settings = xbmcaddon.Addon(id='plugin.video.embycon')
+remote_control = settings.getSetting('remoteControl') == "true"
+if remote_control:
+ websocket_client.start()
- def onPlayBackPaused(self):
- # Will be called when kodi pauses the video
- log.debug("onPlayBackPaused")
- current_file = xbmc.Player().getPlayingFile()
- play_data = monitor.played_information.get(current_file)
- if play_data is not None:
- play_data['paused'] = True
- sendProgress()
+def get_now_playing():
- def onPlayBackResumed(self):
- # Will be called when kodi resumes the video
- log.debug("onPlayBackResumed")
- current_file = xbmc.Player().getPlayingFile()
- play_data = monitor.played_information.get(current_file)
+ # Get the active player
+ result = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "id": 1, "method": "Player.GetActivePlayers"}')
+ result = unicode(result, 'utf-8', errors='ignore')
+ log.debug("Got active player: {0}", result)
+ result = json.loads(result)
- if play_data is not None:
- play_data['paused'] = False
- sendProgress()
+ if 'result' in result and len(result["result"]) > 0:
+ playerid = result["result"][0]["playerid"]
- def onPlayBackSeek(self, time, seekOffset):
- # Will be called when kodi seeks in video
- log.debug("onPlayBackSeek")
- sendProgress()
+ # Get details of the playing media
+ log.debug("Getting details of now playing media")
+ result = xbmc.executeJSONRPC(
+ '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetItem", "params": {"playerid": ' + str(
+ playerid) + ', "properties": ["showtitle", "tvshowid", "episode", "season", "playcount", "genre", "plotoutline", "uniqueid"] } }')
+ result = unicode(result, 'utf-8', errors='ignore')
+ log.debug("playing_item_details: {0}", result)
+ result = json.loads(result)
+ return result
-monitor = Service()
-home_window = HomeWindow()
-last_progress_update = time.time()
-last_content_check = time.time()
# monitor.abortRequested() is causes issues, it currently triggers for all addon cancelations which causes
# the service to exit when a user cancels an addon load action. This is a bug in Kodi.
@@ -316,29 +82,27 @@ while not xbmc.abortRequested:
# if playing every 10 seconds updated the server with progress
if (time.time() - last_progress_update) > 10:
last_progress_update = time.time()
- sendProgress()
+ sendProgress(monitor)
else:
- # if we have a play item them trigger playback
- play_data = home_window.getProperty("play_item_message")
- if play_data:
- home_window.clearProperty("play_item_message")
- play_info = json.loads(play_data)
- playFile(play_info)
-
# if not playing every 60 seonds check for new widget content
if (time.time() - last_content_check) > 60:
last_content_check = time.time()
checkForNewContent()
+ #get_now_playing()
+
except Exception as error:
- log.error("Exception in Playback Monitor : " + str(error))
- log.error(traceback.format_exc())
+ log.error("Exception in Playback Monitor: {0}", error)
+ log.error("{0}", traceback.format_exc())
xbmc.sleep(1000)
+# stop the WebSocket Client
+websocket_client.stop_client()
+
# clear user and token when loggin off
home_window.clearProperty("userid")
home_window.clearProperty("AccessToken")
home_window.clearProperty("Params")
-log.error("Service shutting down")
+log.debug("Service shutting down")