Files
jellycon/resources/lib/utils.py

422 lines
12 KiB
Python
Raw Normal View History

# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
2014-09-28 10:30:07 +10:00
import xbmcaddon
2017-11-18 20:07:25 +11:00
import xbmc
import xbmcvfs
2021-12-29 16:54:05 -05:00
from kodi_six.utils import py2_encode, py2_decode
import sys
2019-01-05 13:28:43 +11:00
2021-05-28 23:33:13 -04:00
import binascii
2017-11-14 19:11:25 +11:00
import string
import random
2017-11-18 20:07:25 +11:00
import json
2019-01-05 13:28:43 +11:00
import time
import math
2022-01-17 12:17:21 -05:00
import os
2022-01-17 15:16:51 -05:00
import hashlib
import requests
2019-01-05 13:28:43 +11:00
from datetime import datetime
from dateutil import tz
2019-01-05 13:28:43 +11:00
import re
2021-12-29 16:54:05 -05:00
from uuid import uuid4
2022-01-17 12:17:21 -05:00
from six import ensure_text, ensure_binary, text_type
2021-01-02 09:52:37 -05:00
from six.moves.urllib.parse import urlencode
2014-09-28 10:30:07 +10:00
from .lazylogger import LazyLogger
2021-12-29 16:54:05 -05:00
from .kodi_utils import HomeWindow
2014-09-28 10:30:07 +10:00
# hack to get datetime strptime loaded
2020-06-21 11:27:09 +10:00
throwaway = time.strptime('20110101', '%Y%m%d')
log = LazyLogger(__name__)
2014-09-28 10:30:07 +10:00
def kodi_version():
# Kodistubs returns empty string, causing Python 3 tests to choke on int()
# TODO: Make Kodistubs version configurable for testing purposes
if sys.version_info.major == 2:
default_versionstring = "18"
else:
default_versionstring = "19.1 (19.1.0) Git:20210509-85e05228b4"
version_string = xbmc.getInfoLabel('System.BuildVersion') or default_versionstring
return int(version_string.split(' ', 1)[0].split('.', 1)[0])
2022-02-27 22:15:54 -05:00
def get_jellyfin_url(path, params):
params["format"] = "json"
2020-11-23 17:58:45 -05:00
url_params = urlencode(params)
2022-02-27 22:15:54 -05:00
return '{}?{}'.format(path, url_params)
2020-06-21 11:27:09 +10:00
def get_checksum(item):
userdata = item['UserData']
2017-04-13 19:30:58 +10:00
checksum = "%s_%s_%s_%s_%s_%s_%s" % (
item['Etag'],
userdata['Played'],
userdata['IsFavorite'],
userdata.get('Likes', "-"),
userdata['PlaybackPositionTicks'],
2017-04-13 19:30:58 +10:00
userdata.get('UnplayedItemCount', "-"),
userdata.get("PlayedPercentage", "-")
)
return checksum
2017-11-14 19:11:25 +11:00
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
2017-11-18 20:07:25 +11:00
return ''.join(random.choice(chars) for _ in range(size))
def single_urlencode(text):
# urlencode needs a utf- string
2021-01-02 09:52:37 -05:00
text = urlencode({'blahblahblah': text.encode('utf-8')})
2017-11-18 20:07:25 +11:00
text = text[13:]
2020-06-21 11:27:09 +10:00
return text.decode('utf-8') # return the result again as unicode
2021-05-28 23:33:13 -04:00
def send_event_notification(method, data=None, hexlify=False):
'''
Send events through Kodi's notification system
'''
data = data or {}
if hexlify:
# Used exclusively for the upnext plugin
data = ensure_text(binascii.hexlify(ensure_binary(json.dumps(data))))
sender = 'plugin.video.jellycon'
data = '"[%s]"' % json.dumps(data).replace('"', '\\"')
xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data))
2019-01-05 13:28:43 +11:00
def datetime_from_string(time_string):
2022-01-17 12:17:21 -05:00
# Builtin python library can't handle ISO-8601 well. Make it compatible
2019-01-05 13:28:43 +11:00
if time_string[-1:] == "Z":
time_string = re.sub("[0-9]{1}Z", " UTC", time_string)
elif time_string[-6:] == "+00:00":
time_string = re.sub("[0-9]{1}\+00:00", " UTC", time_string)
try:
dt = datetime.strptime(time_string, "%Y-%m-%dT%H:%M:%S.%f %Z")
except TypeError:
# https://bugs.python.org/issue27400
dt = datetime(*(time.strptime(time_string, "%Y-%m-%dT%H:%M:%S.%f %Z")[0:6]))
2022-06-16 18:15:40 -04:00
# Dates received from the server are in UTC, but parsing them results in naive objects
utc = tz.tzutc()
utc_dt = dt.replace(tzinfo=utc)
2022-01-17 12:17:21 -05:00
2022-06-16 18:15:40 -04:00
return utc_dt
def get_current_datetime():
# Get current time in UTC
now = datetime.utcnow()
utc = tz.tzutc()
now_dt = now.replace(tzinfo=utc)
return now_dt
def convert_size(size_bytes):
2020-06-21 11:27:09 +10:00
if size_bytes == 0:
return "0B"
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
i = int(math.floor(math.log(size_bytes, 1024)))
p = math.pow(1024, i)
s = round(size_bytes / p, 2)
return "%s %s" % (s, size_name[i])
2021-12-29 16:54:05 -05:00
2021-12-30 17:05:10 -05:00
def translate_string(string_id):
2021-12-29 16:54:05 -05:00
try:
addon = xbmcaddon.Addon()
return py2_encode(addon.getLocalizedString(string_id))
except Exception as e:
log.error('Failed String Load: {0} ({1})', string_id, e)
return str(string_id)
def get_device_id():
window = HomeWindow()
2022-08-06 19:43:16 -04:00
username = window.get_property('user_name')
2021-12-29 16:54:05 -05:00
client_id = window.get_property("client_id")
2022-01-17 15:16:51 -05:00
hashed_name = hashlib.md5(username.encode()).hexdigest()
2021-12-29 16:54:05 -05:00
if client_id and username:
2022-01-17 15:16:51 -05:00
return '{}-{}'.format(client_id, hashed_name)
elif client_id and not username:
# Quick Connect, needs to be unique so sessions don't overwrite
rand_id = uuid4().hex
return '{}-{}'.format(client_id, rand_id)
2021-12-29 16:54:05 -05:00
jellyfin_guid_path = py2_decode(translate_path("special://temp/jellycon_guid"))
2021-12-29 16:54:05 -05:00
log.debug("jellyfin_guid_path: {0}".format(jellyfin_guid_path))
guid = xbmcvfs.File(jellyfin_guid_path)
client_id = guid.read()
guid.close()
if not client_id:
2022-01-17 12:17:21 -05:00
client_id = uuid4().hex
2021-12-29 16:54:05 -05:00
log.debug("Generating a new guid: {0}".format(client_id))
guid = xbmcvfs.File(jellyfin_guid_path, 'w')
guid.write(client_id)
guid.close()
log.debug("jellyfin_client_id (NEW): {0}".format(client_id))
else:
log.debug("jellyfin_client_id: {0}".format(client_id))
window.set_property("client_id", client_id)
2022-01-17 15:16:51 -05:00
return '{}-{}'.format(client_id, hashed_name)
2021-12-29 16:54:05 -05:00
2021-12-30 18:40:08 -05:00
2021-12-29 16:54:05 -05:00
def get_version():
addon = xbmcaddon.Addon()
version = addon.getAddonInfo("version")
return version
2022-01-17 12:17:21 -05:00
def save_user_details(user_name, user_id, token):
settings = xbmcaddon.Addon()
save_user_to_settings = settings.getSetting('save_user_to_settings') == 'true'
addon_data = translate_path(xbmcaddon.Addon().getAddonInfo('profile'))
2022-01-17 12:17:21 -05:00
# Save to a config file for reference later if desired
if save_user_to_settings:
try:
with open(os.path.join(addon_data, 'auth.json'), 'rb') as infile:
auth_data = json.load(infile)
except:
# File doesn't exist or is empty
auth_data = {}
auth_data[user_name] = {
'user_id': user_id,
'token': token
}
with open(os.path.join(addon_data, 'auth.json'), 'wb') as outfile:
data = json.dumps(auth_data, sort_keys=True, indent=4, ensure_ascii=False)
if isinstance(data, text_type):
data = data.encode('utf-8')
outfile.write(data)
# Make the username available for easy lookup
window = HomeWindow()
settings.setSetting('username', user_name)
window.set_property('user_name', user_name)
def load_user_details():
settings = xbmcaddon.Addon()
window = HomeWindow()
# Check current variables first, then check settings
user_name = window.get_property('user_name')
if not user_name:
user_name = settings.getSetting('username')
save_user_to_settings = settings.getSetting('save_user_to_settings') == 'true'
addon_data = translate_path(xbmcaddon.Addon().getAddonInfo('profile'))
2022-01-17 12:17:21 -05:00
if save_user_to_settings:
try:
with open(os.path.join(addon_data, 'auth.json'), 'rb') as infile:
auth_data = json.load(infile)
except:
# File doesn't exist yet
return {}
user_data = auth_data.get(user_name, {})
2022-08-06 19:43:16 -04:00
# User doesn't exist yet
if not user_data:
return {}
2022-01-17 12:17:21 -05:00
user_id = user_data.get('user_id')
auth_token = user_data.get('token')
# Payload to return to calling function
user_details = {}
user_details['user_name'] = user_name
user_details['user_id'] = user_id
user_details['token'] = auth_token
return user_details
else:
return {}
2022-02-27 22:15:54 -05:00
def get_saved_users():
settings = xbmcaddon.Addon()
save_user_to_settings = settings.getSetting('save_user_to_settings') == 'true'
addon_data = translate_path(xbmcaddon.Addon().getAddonInfo('profile'))
if not save_user_to_settings:
return []
try:
with open(os.path.join(addon_data, 'auth.json'), 'rb') as infile:
auth_data = json.load(infile)
except:
# File doesn't exist yet
return []
users = []
for user,values in auth_data.items():
users.append(
{
'Name': user,
'Id': values.get('user_id'),
# We need something here for the listitem function
'Configuration': {'Dummy': True}
}
)
return users
2022-05-20 23:40:13 -04:00
def get_current_user_id():
user_details = load_user_details()
user_id = user_details.get('user_id')
return user_id
2022-05-20 23:46:59 -04:00
2022-02-27 22:15:54 -05:00
def get_art_url(data, art_type, parent=False, index=0, server=None):
item_id = data["Id"]
item_type = data["Type"]
if item_type in ["Episode", "Season"]:
if art_type != "Primary" or parent is True:
item_id = data["SeriesId"]
image_tag = ""
# for episodes always use the parent BG
if item_type == "Episode" and art_type == "Backdrop":
item_id = data.get("ParentBackdropItemId")
bg_item_tags = data.get("ParentBackdropImageTags", [])
if bg_item_tags:
image_tag = bg_item_tags[0]
elif art_type == "Backdrop" and parent is True:
item_id = data.get("ParentBackdropItemId")
bg_item_tags = data.get("ParentBackdropImageTags", [])
if bg_item_tags:
image_tag = bg_item_tags[0]
elif art_type == "Backdrop":
bg_tags = data.get("BackdropImageTags", [])
if bg_tags:
image_tag = bg_tags[index]
elif parent is False:
image_tags = data.get("ImageTags", [])
if image_tags:
image_tag_type = image_tags.get(art_type)
if image_tag_type:
image_tag = image_tag_type
elif parent is True:
if (item_type == "Episode" or item_type == "Season") and art_type == 'Primary':
tag_name = 'SeriesPrimaryImageTag'
id_name = 'SeriesId'
else:
tag_name = 'Parent%sImageTag' % art_type
id_name = 'Parent%sItemId' % art_type
parent_image_id = data.get(id_name)
parent_image_tag = data.get(tag_name)
if parent_image_id is not None and parent_image_tag is not None:
item_id = parent_image_id
image_tag = parent_image_tag
# ParentTag not passed for Banner and Art
if not image_tag and not ((art_type == 'Banner' or art_type == 'Art') and parent is True):
return ""
artwork = "{}/Items/{}/Images/{}/{}?Format=original&Tag={}".format(
server, item_id, art_type, index, image_tag)
return artwork
def image_url(item_id, art_type, index, width, height, image_tag, server):
# test imageTag e3ab56fe27d389446754d0fb04910a34
artwork = "{}/Items/{}/Images/{}/{}?Format=original&Tag={}".format(server, item_id, art_type, index, image_tag)
if int(width) > 0:
artwork += '&MaxWidth={}'.format(width)
if int(height) > 0:
artwork += '&MaxHeight={}'.format(height)
return artwork
def get_default_filters():
addon_settings = xbmcaddon.Addon()
include_media = addon_settings.getSetting("include_media") == "true"
include_people = addon_settings.getSetting("include_people") == "true"
include_overview = addon_settings.getSetting("include_overview") == "true"
filer_list = [
"DateCreated",
"EpisodeCount",
"SeasonCount",
"Path",
"Genres",
"Studios",
"Etag",
"Taglines",
"SortName",
"RecursiveItemCount",
"ChildCount",
"ProductionLocations",
"CriticRating",
"OfficialRating",
"CommunityRating",
"PremiereDate",
"ProductionYear",
"AirTime",
"Status",
"Tags"
]
if include_media:
filer_list.append("MediaStreams")
if include_people:
filer_list.append("People")
if include_overview:
filer_list.append("Overview")
return ','.join(filer_list)
def translate_path(path):
'''
Use new library location for translate path starting in Kodi 19
'''
version = kodi_version()
if version > 18:
return xbmcvfs.translatePath(path)
else:
return xbmc.translatePath(path)
def download_external_sub(language, codec, url):
addon_settings = xbmcaddon.Addon()
verify_cert = addon_settings.getSetting('verify_cert') == 'true'
# Download the subtitle file
r = requests.get(url, verify=verify_cert)
r.raise_for_status()
# Write the subtitle file to the local filesystem
file_name = 'Stream.{}.{}'.format(language, codec)
file_path = py2_decode(translate_path('special://temp/{}'.format(file_name)))
with open(file_path, 'wb') as f:
f.write(r.content)
return file_path