Some checks failed
Build JellyCon / build (py2) (push) Has been cancelled
Build JellyCon / build (py3) (push) Has been cancelled
CodeQL Analysis / analyze (python, 3.9) (push) Has been cancelled
Release Drafter / Update release draft (push) Has been cancelled
Test JellyCon / test (3.9) (push) Has been cancelled
Adds new advanced setting to disable disk-based caching, keeping data only in RAM. When enabled, prevents writing .pickle cache files and disables artwork preloading. Significantly reduces SD card wear on devices like Raspberry Pi while maintaining performance through RAM caching.
361 lines
13 KiB
Python
361 lines
13 KiB
Python
from __future__ import (
|
|
division, absolute_import, print_function, unicode_literals
|
|
)
|
|
|
|
import base64
|
|
import sys
|
|
import threading
|
|
import time
|
|
|
|
import xbmcgui
|
|
import xbmcplugin
|
|
import xbmc
|
|
import xbmcaddon
|
|
import requests
|
|
from six.moves.urllib.parse import unquote
|
|
|
|
from .jellyfin import api
|
|
from .lazylogger import LazyLogger
|
|
from .jsonrpc import JsonRpc, get_value
|
|
from .utils import translate_string, load_user_details
|
|
from .kodi_utils import HomeWindow
|
|
from .item_functions import get_art
|
|
|
|
log = LazyLogger(__name__)
|
|
|
|
|
|
class CacheArtwork(threading.Thread):
|
|
|
|
stop_all_activity = False
|
|
|
|
def __init__(self):
|
|
log.debug("CacheArtwork init")
|
|
self.stop_all_activity = False
|
|
super(CacheArtwork, self).__init__()
|
|
|
|
def stop_activity(self):
|
|
self.stop_all_activity = True
|
|
|
|
def run(self):
|
|
log.debug("CacheArtwork background thread started")
|
|
last_update = 0
|
|
home_window = HomeWindow()
|
|
settings = xbmcaddon.Addon()
|
|
|
|
# Check if disk caching is disabled
|
|
disable_disk_cache = settings.getSetting('disable_disk_cache') == 'true'
|
|
if disable_disk_cache:
|
|
log.debug("CacheArtwork : Disk caching disabled, artwork caching skipped")
|
|
return
|
|
|
|
latest_content_hash = "never"
|
|
check_interval = int(settings.getSetting('cacheImagesOnScreenSaver_interval'))
|
|
check_interval = check_interval * 60
|
|
monitor = xbmc.Monitor()
|
|
monitor.waitForAbort(5)
|
|
|
|
while not self.stop_all_activity and not monitor.abortRequested() and xbmc.getCondVisibility("System.ScreenSaverActive"):
|
|
content_hash = home_window.get_property("jellycon_widget_reload")
|
|
if (check_interval != 0 and (time.time() - last_update) > check_interval) or (latest_content_hash != content_hash):
|
|
log.debug("CacheArtwork background thread - triggered")
|
|
if monitor.waitForAbort(10):
|
|
break
|
|
if self.stop_all_activity or monitor.abortRequested():
|
|
break
|
|
self.cache_artwork_background()
|
|
last_update = time.time()
|
|
latest_content_hash = content_hash
|
|
|
|
monitor.waitForAbort(5)
|
|
|
|
log.debug("CacheArtwork background thread exited : stop_all_activity : {0}".format(self.stop_all_activity))
|
|
|
|
@staticmethod
|
|
def delete_cached_images(item_id):
|
|
log.debug("cache_delete_for_links")
|
|
|
|
progress = xbmcgui.DialogProgress()
|
|
progress.create(translate_string(30281))
|
|
progress.update(30, translate_string(30347))
|
|
|
|
item_image_url_part = "Items/%s/Images/" % item_id
|
|
item_image_url_part = item_image_url_part.replace("/", "%2f")
|
|
log.debug("texture ids: {0}".format(item_image_url_part))
|
|
|
|
# is the web server enabled
|
|
web_query = {"setting": "services.webserver"}
|
|
result = JsonRpc('Settings.GetSettingValue').execute(web_query)
|
|
xbmc_webserver_enabled = result['result']['value']
|
|
if not xbmc_webserver_enabled:
|
|
xbmcgui.Dialog().ok(translate_string(30294), translate_string(30295))
|
|
return
|
|
|
|
params = {"properties": ["url"]}
|
|
json_result = JsonRpc('Textures.GetTextures').execute(params)
|
|
textures = json_result.get("result", {}).get("textures", [])
|
|
log.debug("texture ids: {0}".format(textures))
|
|
|
|
progress.update(70, translate_string(30346))
|
|
|
|
delete_count = 0
|
|
for texture in textures:
|
|
texture_id = texture["textureid"]
|
|
texture_url = texture["url"]
|
|
if item_image_url_part in texture_url:
|
|
delete_count += 1
|
|
log.debug("removing texture id: {0}".format(texture_id))
|
|
params = {"textureid": int(texture_id)}
|
|
JsonRpc('Textures.RemoveTexture').execute(params)
|
|
|
|
del textures
|
|
|
|
progress.update(100, translate_string(30125))
|
|
progress.close()
|
|
|
|
xbmcgui.Dialog().ok(translate_string(30281), '{}: {}'.format(translate_string(30344), delete_count))
|
|
|
|
def cache_artwork_interactive(self):
|
|
log.debug("cache_artwork_interactive")
|
|
|
|
xbmcplugin.endOfDirectory(int(sys.argv[1]), cacheToDisc=False)
|
|
|
|
# is the web server enabled
|
|
web_query = {"setting": "services.webserver"}
|
|
result = JsonRpc('Settings.GetSettingValue').execute(web_query)
|
|
xbmc_webserver_enabled = result['result']['value']
|
|
if not xbmc_webserver_enabled:
|
|
xbmcgui.Dialog().ok(translate_string(30294), '{} - {}'.format(translate_string(30295), translate_string(30355)))
|
|
xbmc.executebuiltin('ActivateWindow(servicesettings)')
|
|
return
|
|
|
|
result_report = []
|
|
|
|
# ask questions
|
|
question_delete_unused = xbmcgui.Dialog().yesno(translate_string(30296), translate_string(30297))
|
|
question_cache_images = xbmcgui.Dialog().yesno(translate_string(30299), translate_string(30300))
|
|
|
|
delete_canceled = False
|
|
# now do work - delete unused
|
|
if question_delete_unused:
|
|
delete_pdialog = xbmcgui.DialogProgress()
|
|
delete_pdialog.create(translate_string(30298), "")
|
|
index = 0
|
|
|
|
params = {"properties": ["url"]}
|
|
json_result = JsonRpc('Textures.GetTextures').execute(params)
|
|
textures = json_result.get("result", {}).get("textures", [])
|
|
|
|
jellyfin_texture_urls = self.get_jellyfin_artwork(delete_pdialog)
|
|
|
|
log.debug("kodi textures: {0}".format(textures))
|
|
log.debug("jellyfin texture urls: {0}".format(jellyfin_texture_urls))
|
|
|
|
if jellyfin_texture_urls is not None:
|
|
|
|
unused_texture_ids = set()
|
|
for texture in textures:
|
|
url = texture.get("url")
|
|
url = unquote(url)
|
|
url = url.replace("image://", "")
|
|
url = url[0:-1]
|
|
if url.find("/") > -1 and url not in jellyfin_texture_urls or url.find("localhost:24276") > -1:
|
|
unused_texture_ids.add(texture["textureid"])
|
|
|
|
total = len(unused_texture_ids)
|
|
log.debug("unused texture ids: {0}".format(unused_texture_ids))
|
|
|
|
for texture_id in unused_texture_ids:
|
|
params = {"textureid": int(texture_id)}
|
|
JsonRpc('Textures.RemoveTexture').execute(params)
|
|
percentage = int((float(index) / float(total)) * 100)
|
|
message = "%s of %s" % (index, total)
|
|
delete_pdialog.update(percentage, message)
|
|
|
|
index += 1
|
|
if delete_pdialog.iscanceled():
|
|
delete_canceled = True
|
|
break
|
|
|
|
result_report.append(translate_string(30385) + str(len(textures)))
|
|
result_report.append(translate_string(30386) + str(len(unused_texture_ids)))
|
|
result_report.append(translate_string(30387) + str(index))
|
|
|
|
del textures
|
|
del jellyfin_texture_urls
|
|
del unused_texture_ids
|
|
delete_pdialog.close()
|
|
del delete_pdialog
|
|
|
|
if delete_canceled:
|
|
xbmc.sleep(2000)
|
|
|
|
# now do work - cache images
|
|
if question_cache_images:
|
|
cache_pdialog = xbmcgui.DialogProgress()
|
|
cache_pdialog.create(translate_string(30301), "")
|
|
cache_report = self.cache_artwork(cache_pdialog)
|
|
cache_pdialog.close()
|
|
del cache_pdialog
|
|
if cache_report:
|
|
result_report.extend(cache_report)
|
|
|
|
if len(result_report) > 0:
|
|
msg = "\r\n".join(result_report)
|
|
xbmcgui.Dialog().textviewer(translate_string(30125), msg, usemono=True)
|
|
|
|
def cache_artwork_background(self):
|
|
log.debug("cache_artwork_background")
|
|
dp = xbmcgui.DialogProgressBG()
|
|
dp.create(translate_string(30301), "")
|
|
result_text = None
|
|
try:
|
|
result_text = self.cache_artwork(dp)
|
|
except Exception as err:
|
|
log.error("Cache Images Failed : {0}".format(err))
|
|
dp.close()
|
|
del dp
|
|
if result_text is not None:
|
|
log.debug("Cache Images result : {0}".format(" - ".join(result_text)))
|
|
|
|
def get_jellyfin_artwork(self, progress):
|
|
log.debug("get_jellyfin_artwork")
|
|
user_details = load_user_details()
|
|
user_id = user_details.get('user_id')
|
|
|
|
url = ""
|
|
url += "/Users/{}/Items".format(user_id)
|
|
url += "?Recursive=true"
|
|
url += "&EnableUserData=False"
|
|
url += "&Fields=BasicSyncInfo"
|
|
url += "&IncludeItemTypes=Movie,Series,Episode,BoxSet"
|
|
url += "&ImageTypeLimit=1"
|
|
url += "&format=json"
|
|
|
|
results = api.get(url)
|
|
if results is None:
|
|
results = []
|
|
|
|
if isinstance(results, dict):
|
|
results = results.get("Items")
|
|
|
|
settings = xbmcaddon.Addon()
|
|
server = settings.getSetting('server_address')
|
|
log.debug("Jellyfin Item Count Count: {0}".format(len(results)))
|
|
|
|
if self.stop_all_activity:
|
|
return None
|
|
|
|
progress.update(0, translate_string(30359))
|
|
|
|
texture_urls = set()
|
|
|
|
for item in results:
|
|
art = get_art(item, server)
|
|
for art_type in art:
|
|
texture_urls.add(art[art_type])
|
|
|
|
return texture_urls
|
|
|
|
def cache_artwork(self, progress):
|
|
log.debug("cache_artwork")
|
|
|
|
# is the web server enabled
|
|
if not get_value("services.webserver"):
|
|
log.error("Kodi web server not enabled, can not cache images")
|
|
return
|
|
|
|
# get the port
|
|
xbmc_port = get_value("services.webserverport")
|
|
log.debug("xbmc_port: {0}".format(xbmc_port))
|
|
|
|
# get the user
|
|
xbmc_username = get_value("services.webserverusername")
|
|
log.debug("xbmc_username: {0}".format(xbmc_username))
|
|
|
|
# get the password
|
|
xbmc_password = get_value("services.webserverpassword")
|
|
|
|
progress.update(0, translate_string(30356))
|
|
|
|
params = {"properties": ["url"]}
|
|
json_result = JsonRpc('Textures.GetTextures').execute(params)
|
|
textures = json_result.get("result", {}).get("textures", [])
|
|
log.debug("Textures.GetTextures Count: {0}".format(len(textures)))
|
|
|
|
if self.stop_all_activity:
|
|
return
|
|
|
|
progress.update(0, translate_string(30357))
|
|
|
|
texture_urls = set()
|
|
for texture in textures:
|
|
url = texture.get("url")
|
|
url = unquote(url)
|
|
url = url.replace("image://", "")
|
|
url = url[0:-1]
|
|
texture_urls.add(url)
|
|
|
|
del textures
|
|
del json_result
|
|
|
|
log.debug("texture_urls Count: {0}".format(len(texture_urls)))
|
|
|
|
if self.stop_all_activity:
|
|
return
|
|
|
|
progress.update(0, translate_string(30358))
|
|
|
|
jellyfin_texture_urls = self.get_jellyfin_artwork(progress)
|
|
if jellyfin_texture_urls is None:
|
|
return
|
|
|
|
missing_texture_urls = set()
|
|
|
|
for image_url in jellyfin_texture_urls:
|
|
if image_url not in texture_urls and not image_url.endswith("&Tag=") and len(image_url) > 0:
|
|
missing_texture_urls.add(image_url)
|
|
|
|
if self.stop_all_activity:
|
|
return
|
|
|
|
log.debug("texture_urls: {0}".format(texture_urls))
|
|
log.debug("missing_texture_urls: {0}".format(missing_texture_urls))
|
|
log.debug("Number of existing textures: {0}".format(len(texture_urls)))
|
|
log.debug("Number of missing textures: {0}".format(len(missing_texture_urls)))
|
|
|
|
kodi_http_server = "localhost:" + str(xbmc_port)
|
|
headers = {}
|
|
if xbmc_password:
|
|
auth = "%s:%s" % (xbmc_username, xbmc_password)
|
|
headers = {'Authorization': 'Basic %s' % base64.b64encode(auth)}
|
|
|
|
total = len(missing_texture_urls)
|
|
|
|
count_done = 0
|
|
for index, get_url in enumerate(missing_texture_urls, 1):
|
|
kodi_texture_url = "/image/image://{0}".format(get_url)
|
|
log.debug("kodi_texture_url: {0}".format(kodi_texture_url))
|
|
|
|
percentage = int((float(index) / float(total)) * 100)
|
|
message = "%s of %s" % (index, total)
|
|
progress.update(percentage, message)
|
|
|
|
cache_url = "http://%s%s" % (kodi_http_server, kodi_texture_url)
|
|
data = requests.get(cache_url, timeout=20, headers=headers)
|
|
|
|
if data.status_code == 200:
|
|
count_done += 1
|
|
log.debug("Get Image Result: {0}".format(data.status_code))
|
|
|
|
if isinstance(progress, xbmcgui.DialogProgress) and progress.iscanceled():
|
|
break
|
|
|
|
if self.stop_all_activity:
|
|
break
|
|
|
|
result_report = []
|
|
result_report.append(translate_string(30302) + str(len(texture_urls)))
|
|
result_report.append(translate_string(30303) + str(len(missing_texture_urls)))
|
|
result_report.append(translate_string(30304) + str(count_done))
|
|
return result_report
|