Files
jellycon/resources/lib/downloadutils.py

700 lines
25 KiB
Python

# Gnu General Public License - see LICENSE.TXT
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import httplib
import hashlib
import ssl
import StringIO
import gzip
import json
from urlparse import urlparse
import urllib
from datetime import datetime
from .kodi_utils import HomeWindow
from .clientinfo import ClientInformation
from .simple_logging import SimpleLogging
from .translation import string_load
log = SimpleLogging(__name__)
def save_user_details(settings, user_name, user_password):
save_user_to_settings = settings.getSetting("save_user_to_settings") == "true"
if save_user_to_settings:
settings.setSetting("username", user_name)
settings.setSetting("password", user_password)
else:
settings.setSetting("username", "")
settings.setSetting("password", "")
home_window = HomeWindow()
home_window.setProperty("username", user_name)
home_window.setProperty("password", user_password)
def load_user_details(settings):
save_user_to_settings = settings.getSetting("save_user_to_settings") == "true"
if save_user_to_settings:
user_name = settings.getSetting("username")
user_password = settings.getSetting("password")
else:
home_window = HomeWindow()
user_name = home_window.getProperty("username")
user_password = home_window.getProperty("password")
user_details = {}
user_details["username"] = user_name
user_details["password"] = user_password
return user_details
def getDetailsString():
addonSettings = xbmcaddon.Addon()
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,Taglines"
detailsString += ",RecursiveItemCount,ChildCount,ProductionLocations"
if include_media:
detailsString += ",MediaStreams"
if include_people:
detailsString += ",People"
if include_overview:
detailsString += ",Overview"
return detailsString
class DownloadUtils():
getString = None
def __init__(self, *args):
addon = xbmcaddon.Addon()
self.addon_name = addon.getAddonInfo('name')
def post_capabilities(self):
url = "{server}/emby/Sessions/Capabilities/Full?format=json"
data = {
'IconUrl': "https://raw.githubusercontent.com/faush01/plugin.video.embycon/develop/kodi.png",
'SupportsMediaControl': True,
'PlayableMediaTypes': ["Video", "Audio"],
'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"]
}
self.downloadUrl(url, postBody=data, method="POST")
log.debug("Posted Capabilities: {0}", data)
def get_item_playback_info(self, item_id):
profile = {
"Name": "Kodi",
"MaxStreamingBitrate": 100000000,
"MusicStreamingTranscodingBitrate": 1280000,
"TimelineOffsetSeconds": 5,
"TranscodingProfiles": [
{
"Type": "Audio"
},
{
"Container": "m3u8",
"Type": "Video",
"AudioCodec": "aac,mp3,ac3,opus,flac,vorbis",
"VideoCodec": "h264,mpeg4,mpeg2video",
"MaxAudioChannels": "6"
},
{
"Container": "jpeg",
"Type": "Photo"
}
],
"DirectPlayProfiles": [
{
"Type": "Video"
},
{
"Type": "Audio"
},
{
"Type": "Photo"
}
],
"ResponseProfiles": [],
"ContainerProfiles": [],
"CodecProfiles": [],
"SubtitleProfiles": [
{
"Format": "srt",
"Method": "External"
},
{
"Format": "srt",
"Method": "Embed"
},
{
"Format": "ass",
"Method": "External"
},
{
"Format": "ass",
"Method": "Embed"
},
{
"Format": "sub",
"Method": "Embed"
},
{
"Format": "sub",
"Method": "External"
},
{
"Format": "ssa",
"Method": "Embed"
},
{
"Format": "ssa",
"Method": "External"
},
{
"Format": "smi",
"Method": "Embed"
},
{
"Format": "smi",
"Method": "External"
},
{
"Format": "pgssub",
"Method": "Embed"
},
{
"Format": "pgssub",
"Method": "External"
},
{
"Format": "dvdsub",
"Method": "Embed"
},
{
"Format": "dvdsub",
"Method": "External"
},
{
"Format": "pgs",
"Method": "Embed"
},
{
"Format": "pgs",
"Method": "External"
}
]
}
playback_info = {
'UserId': self.getUserId(),
'DeviceProfile': profile,
'AutoOpenLiveStream': True
}
url = "{server}/emby/Items/%s/PlaybackInfo" % item_id
log.debug("PlaybackInfo : {0}", url)
log.debug("PlaybackInfo : {0}", profile)
play_info_result = self.downloadUrl(url, postBody=playback_info, method="POST")
play_info_result = json.loads(play_info_result)
log.debug("PlaybackInfo : {0}", play_info_result)
return play_info_result
def getServer(self):
settings = xbmcaddon.Addon()
host = settings.getSetting('ipaddress')
if len(host) == 0 or host == "<none>":
return None
port = settings.getSetting('port')
use_https = settings.getSetting('use_https') == 'true'
if not port and use_https:
port = "443"
settings.setSetting("port", port)
elif not port and not use_https:
port = "80"
settings.setSetting("port", port)
# 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 host.lower().strip().startswith("http://"):
settings.setSetting('use_https', 'false')
use_https = False
elif host.lower().strip().startswith("https://"):
settings.setSetting('use_https', 'true')
use_https = True
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)
if use_https:
server = "https://" + host + ":" + port
else:
server = "http://" + host + ":" + port
return server
def getArtwork(self, data, art_type, parent=False, index=0, server=None):
id = data["Id"]
item_type = data["Type"]
if item_type in ["Episode", "Season"]:
if art_type != "Primary" or parent == True:
id = data["SeriesId"]
imageTag = ""
# "e3ab56fe27d389446754d0fb04910a34" # a place holder tag, needs to be in this format
# for episodes always use the parent BG
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 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["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%sImageTag' % art_type
idName = 'Parent%sItemId' % art_type
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 not imageTag and not ((art_type == 'Banner' or art_type == 'Art') and parent is True): # ParentTag not passed for Banner and Art
log.debug("No Image Tag for request:{0} item:{1} parent:{2}", art_type, item_type, parent)
return ""
artwork = "%s/emby/Items/%s/Images/%s/%s?Format=original&Tag=%s" % (server, id, art_type, index, imageTag)
# log.debug("getArtwork: request:{0} item:{1} parent:{2} link:{3}", art_type, item_type, parent, artwork)
'''
# do not return non-existing images
if ( (art_type != "Backdrop" and imageTag == "") |
(art_type == "Backdrop" and data.get("BackdropImageTags") != None and len(data.get("BackdropImageTags")) == 0) |
(art_type == "Backdrop" and data.get("BackdropImageTag") != None and len(data.get("BackdropImageTag")) == 0)
):
artwork = ''
'''
return artwork
def imageUrl(self, id, art_type, index, width, height, imageTag, server):
# test imageTag e3ab56fe27d389446754d0fb04910a34
artwork = "%s/emby/Items/%s/Images/%s/%s?Format=original&Tag=%s" % (server, id, art_type, index, imageTag)
if int(width) > 0:
artwork += '&MaxWidth=%s' % width
if int(height) > 0:
artwork += '&MaxHeight=%s' % height
return artwork
def get_user_artwork(self, user, item_type):
if "PrimaryImageTag" not in user:
return ""
user_id = user.get("Id")
tag = user.get("PrimaryImageTag")
server = self.getServer()
return "%s/emby/Users/%s/Images/%s?Format=original&tag=%s" % (server, user_id, item_type, tag)
def getUserId(self):
WINDOW = HomeWindow()
userid = WINDOW.getProperty("userid")
userImage = WINDOW.getProperty("userimage")
if userid and userImage:
log.debug("EmbyCon DownloadUtils -> Returning saved UserID: {0}", userid)
return userid
settings = xbmcaddon.Addon()
user_details = load_user_details(settings)
user_name = user_details.get("username", "")
if not user_name:
return ""
log.debug("Looking for user name: {0}", user_name)
try:
json_data = self.downloadUrl("{server}/emby/Users/Public?format=json", suppress=True, authenticate=False)
except Exception as msg:
log.error("Get User unable to connect: {0}", msg)
return ""
log.debug("GETUSER_JSONDATA_01: {0}", json_data)
result = []
try:
result = json.loads(json_data)
except Exception as e:
log.debug("Could not load user data: {0}", e)
return ""
if result is None:
return ""
log.debug("GETUSER_JSONDATA_02: {0}", result)
secure = False
for user in result:
if user.get("Name") == unicode(user_name, "utf-8"):
userid = user.get("Id")
userImage = self.get_user_artwork(user, 'Primary')
log.debug("Username Found: {0}", user.get("Name"))
if user.get("HasPassword", False):
secure = True
log.debug("Username Is Secure (HasPassword=True)")
break
if secure or not userid:
authOk = self.authenticate()
if authOk == "":
xbmcgui.Dialog().notification(string_load(30316),
string_load(30044),
icon="special://home/addons/plugin.video.embycon/icon.png")
return ""
if not userid:
userid = WINDOW.getProperty("userid")
if userid and not userImage:
userImage = 'DefaultUser.png'
if userid == "":
xbmcgui.Dialog().notification(string_load(30316),
string_load(30045),
icon="special://home/addons/plugin.video.embycon/icon.png")
log.debug("userid: {0}", userid)
WINDOW.setProperty("userid", userid)
WINDOW.setProperty("userimage", userImage)
return userid
def authenticate(self):
WINDOW = HomeWindow()
token = WINDOW.getProperty("AccessToken")
if token is not None and token != "":
log.debug("EmbyCon DownloadUtils -> Returning saved AccessToken: {0}", token)
return token
settings = xbmcaddon.Addon()
port = settings.getSetting("port")
host = settings.getSetting("ipaddress")
if host is None or host == "" or port is None or port == "":
return ""
url = "{server}/emby/Users/AuthenticateByName?format=json"
user_details = load_user_details(settings)
pwd_sha = hashlib.sha1(user_details.get("password", "")).hexdigest()
user_name = urllib.quote(user_details.get("username", ""))
pwd_text = urllib.quote(user_details.get("password", ""))
messageData = "username=" + user_name + "&password=" + pwd_sha
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)
log.debug("AuthenticateByName: {0}", resp)
accessToken = None
userid = None
try:
result = json.loads(resp)
accessToken = result.get("AccessToken")
#userid = result["SessionInfo"].get("UserId")
userid = result["User"].get("Id")
except:
pass
if accessToken is not None:
log.debug("User Authenticated: {0}", accessToken)
log.debug("User Id: {0}", userid)
WINDOW.setProperty("AccessToken", accessToken)
WINDOW.setProperty("userid", userid)
#WINDOW.setProperty("userimage", "")
self.post_capabilities()
return accessToken
else:
log.debug("User NOT Authenticated")
WINDOW.setProperty("AccessToken", "")
WINDOW.setProperty("userid", "")
WINDOW.setProperty("userimage", "")
return ""
def getAuthHeader(self, authenticate=True):
clientInfo = ClientInformation()
txt_mac = clientInfo.getDeviceId()
version = clientInfo.getVersion()
client = clientInfo.getClient()
settings = xbmcaddon.Addon()
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"
headers["Accept-Charset"] = "UTF-8,*"
if (authenticate == False):
authString = "MediaBrowser Client=\"" + client + "\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
headers["Authorization"] = authString
headers['X-Emby-Authorization'] = authString
return headers
else:
userid = self.getUserId()
authString = "MediaBrowser UserId=\"" + userid + "\",Client=\"" + client + "\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
headers["Authorization"] = authString
headers['X-Emby-Authorization'] = authString
authToken = self.authenticate()
if (authToken != ""):
headers["X-MediaBrowser-Token"] = authToken
log.debug("EmbyCon Authentication Header: {0}", headers)
return headers
def downloadUrl(self, url, suppress=False, postBody=None, method="GET", authenticate=True, headers=None):
log.debug("downloadUrl")
return_data = "null"
settings = xbmcaddon.Addon()
user_details = load_user_details(settings)
username = user_details.get("username", "")
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("{field_filters}") != -1:
filter_string = getDetailsString()
url = url.replace("{field_filters}", filter_string)
if url.find("{random_movies}") != -1:
home_window = HomeWindow()
random_movies = home_window.getProperty("random-movies")
if not random_movies:
return return_data
url = url.replace("{random_movies}", random_movies)
log.debug("After: {0}", url)
try:
if url.startswith('http'):
serversplit = 2
urlsplit = 3
else:
serversplit = 0
urlsplit = 1
server = url.split('/')[serversplit]
urlPath = "/" + "/".join(url.split('/')[urlsplit:])
log.debug("DOWNLOAD_URL: {0}", url)
log.debug("server: {0}", server)
log.debug("urlPath: {0}", urlPath)
# check the server details
tokens = server.split(':')
host = tokens[0]
port = tokens[1]
if host == "<none>" or host == "" or port == "":
return return_data
if authenticate and username == "":
return return_data
use_https = settings.getSetting('use_https') == 'true'
verify_cert = settings.getSetting('verify_cert') == 'true'
if use_https and verify_cert:
log.debug("Connection: HTTPS, Cert checked")
conn = httplib.HTTPSConnection(server, timeout=40)
elif use_https and not verify_cert:
log.debug("Connection: HTTPS, Cert NOT checked")
conn = httplib.HTTPSConnection(server, timeout=40, context=ssl._create_unverified_context())
else:
log.debug("Connection: HTTP")
conn = httplib.HTTPConnection(server, timeout=40)
head = self.getAuthHeader(authenticate)
log.debug("HEADERS: {0}", head)
if (postBody != None):
if isinstance(postBody, dict):
content_type = "application/json"
postBody = json.dumps(postBody)
else:
content_type = "application/x-www-form-urlencoded"
head["Content-Type"] = content_type
log.debug("Content-Type: {0}", content_type)
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: {0}", data.getheaders())
if int(data.status) == 200:
retData = data.read()
contentType = data.getheader('content-encoding')
log.debug("Data Len Before: {0}", len(retData))
if (contentType == "gzip"):
retData = StringIO.StringIO(retData)
gzipper = gzip.GzipFile(fileobj=retData)
return_data = gzipper.read()
else:
return_data = retData
if headers is not None and isinstance(headers, dict):
headers.update(data.getheaders())
log.debug("Data Len After: {0}", len(return_data))
log.debug("====== 200 returned =======")
log.debug("Content-Type: {0}", contentType)
log.debug("{0}", return_data)
log.debug("====== 200 finished ======")
elif int(data.status) >= 400:
if int(data.status) == 401:
# remove any saved password
m = hashlib.md5()
m.update(username)
hashed_username = m.hexdigest()
log.error("HTTP response error 401 auth error, removing any saved passwords for user: {0}", hashed_username)
settings.setSetting("saved_user_password_" + hashed_username, "")
save_user_details(settings, "", "")
log.error("HTTP response error: {0} {1}", data.status, data.reason)
if suppress is False:
xbmcgui.Dialog().notification(string_load(30316),
string_load(30200) % str(data.reason),
icon="special://home/addons/plugin.video.embycon/icon.png")
except Exception as msg:
log.error("Unable to connect to {0} : {1}", server, msg)
if suppress is False:
xbmcgui.Dialog().notification(string_load(30316),
str(msg),
icon="special://home/addons/plugin.video.embycon/icon.png")
finally:
try:
log.debug("Closing HTTP connection: {0}", conn)
conn.close()
except:
pass
return return_data