mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2025-05-29 20:36:15 +00:00
Begin restructing, fixed some issues related to movement
This commit is contained in:
parent
6be8213b34
commit
84b8093171
72 changed files with 16 additions and 34 deletions
380
jellyfin_kodi/objects/kodi/artwork.py
Normal file
380
jellyfin_kodi/objects/kodi/artwork.py
Normal file
|
@ -0,0 +1,380 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import logging
|
||||
import urllib
|
||||
import Queue
|
||||
import threading
|
||||
|
||||
import xbmc
|
||||
import xbmcvfs
|
||||
|
||||
import queries as QU
|
||||
import queries_texture as QUTEX
|
||||
from helper import settings
|
||||
import requests
|
||||
|
||||
##################################################################################################
|
||||
|
||||
LOG = logging.getLogger("JELLYFIN." + __name__)
|
||||
|
||||
##################################################################################################
|
||||
|
||||
|
||||
class Artwork(object):
|
||||
|
||||
def __init__(self, cursor):
|
||||
|
||||
self.cursor = cursor
|
||||
self.enable_cache = settings('enableTextureCache.bool')
|
||||
self.queue = Queue.Queue()
|
||||
self.threads = []
|
||||
self.kodi = {
|
||||
'username': settings('webServerUser'),
|
||||
'password': settings('webServerPass'),
|
||||
'host': "localhost",
|
||||
'port': settings('webServerPort')
|
||||
}
|
||||
|
||||
def update(self, image_url, kodi_id, media, image):
|
||||
|
||||
''' Update artwork in the video database.
|
||||
Only cache artwork if it changed for the main backdrop, poster.
|
||||
Delete current entry before updating with the new one.
|
||||
Cache fanart and poster in Kodi texture cache.
|
||||
'''
|
||||
if not image_url or image == 'poster' and media in ('song', 'artist', 'album'):
|
||||
return
|
||||
|
||||
cache = False
|
||||
|
||||
try:
|
||||
self.cursor.execute(QU.get_art, (kodi_id, media, image,))
|
||||
url = self.cursor.fetchone()[0]
|
||||
except TypeError:
|
||||
|
||||
cache = True
|
||||
LOG.debug("ADD to kodi_id %s art: %s", kodi_id, image_url)
|
||||
self.cursor.execute(QU.add_art, (kodi_id, media, image, image_url))
|
||||
else:
|
||||
if url != image_url:
|
||||
cache = True
|
||||
|
||||
if image in ('fanart', 'poster'):
|
||||
self.delete_cache(url)
|
||||
|
||||
LOG.info("UPDATE to kodi_id %s art: %s", kodi_id, image_url)
|
||||
self.cursor.execute(QU.update_art, (image_url, kodi_id, media, image))
|
||||
|
||||
if cache and image in ('fanart', 'poster'):
|
||||
self.cache(image_url)
|
||||
|
||||
def add(self, artwork, *args):
|
||||
|
||||
''' Add all artworks.
|
||||
'''
|
||||
KODI = {
|
||||
'Primary': ['thumb', 'poster'],
|
||||
'Banner': "banner",
|
||||
'Logo': "clearlogo",
|
||||
'Art': "clearart",
|
||||
'Thumb': "landscape",
|
||||
'Disc': "discart",
|
||||
'Backdrop': "fanart"
|
||||
}
|
||||
|
||||
for art in KODI:
|
||||
|
||||
if art == 'Backdrop':
|
||||
self.cursor.execute(QU.get_backdrops, args + ("fanart%",))
|
||||
|
||||
if len(self.cursor.fetchall()) > len(artwork['Backdrop']):
|
||||
self.cursor.execute(QU.delete_backdrops, args + ("fanart_",))
|
||||
|
||||
for index, backdrop in enumerate(artwork['Backdrop']):
|
||||
|
||||
if index:
|
||||
self.update(*(backdrop,) + args + ("%s%s" % ("fanart", index),))
|
||||
else:
|
||||
self.update(*(backdrop,) + args + ("fanart",))
|
||||
|
||||
elif art == 'Primary':
|
||||
for kodi_image in KODI['Primary']:
|
||||
self.update(*(artwork['Primary'],) + args + (kodi_image,))
|
||||
|
||||
elif artwork.get(art):
|
||||
self.update(*(artwork[art],) + args + (KODI[art],))
|
||||
|
||||
def delete(self, *args):
|
||||
|
||||
''' Delete artwork from kodi database and remove cache for backdrop/posters.
|
||||
'''
|
||||
self.cursor.execute(QU.get_art_url, args)
|
||||
|
||||
for row in self.cursor.fetchall():
|
||||
if row[1] in ('poster', 'fanart'):
|
||||
self.delete_cache(row[0])
|
||||
|
||||
def cache(self, url):
|
||||
|
||||
''' Cache a single image to texture cache.
|
||||
'''
|
||||
if not url or not self.enable_cache:
|
||||
return
|
||||
|
||||
url = self.double_urlencode(url)
|
||||
self.queue.put(url)
|
||||
self.add_worker()
|
||||
|
||||
def double_urlencode(self, text):
|
||||
|
||||
text = self.single_urlencode(text)
|
||||
text = self.single_urlencode(text)
|
||||
|
||||
return text
|
||||
|
||||
def single_urlencode(self, text):
|
||||
|
||||
''' urlencode needs a utf-string.
|
||||
return the result as unicode
|
||||
'''
|
||||
text = urllib.urlencode({'blahblahblah': text.encode('utf-8')})
|
||||
text = text[13:]
|
||||
|
||||
return text.decode('utf-8')
|
||||
|
||||
def add_worker(self):
|
||||
|
||||
for thread in self.threads:
|
||||
if thread.is_done:
|
||||
self.threads.remove(thread)
|
||||
|
||||
if self.queue.qsize() and len(self.threads) < 2:
|
||||
|
||||
new_thread = GetArtworkWorker(self.kodi, self.queue)
|
||||
new_thread.start()
|
||||
LOG.info("-->[ q:artwork/%s ]", id(new_thread))
|
||||
self.threads.append(new_thread)
|
||||
|
||||
def delete_cache(self, url):
|
||||
|
||||
''' Delete cached artwork.
|
||||
'''
|
||||
from database import Database
|
||||
|
||||
with Database('texture') as texturedb:
|
||||
try:
|
||||
texturedb.cursor.execute(QUTEX.get_cache, (url,))
|
||||
cached = texturedb.cursor.fetchone()[0]
|
||||
except TypeError:
|
||||
LOG.debug("Could not find cached url: %s", url)
|
||||
else:
|
||||
thumbnails = xbmc.translatePath("special://thumbnails/%s" % cached).decode('utf-8')
|
||||
xbmcvfs.delete(thumbnails)
|
||||
texturedb.cursor.execute(QUTEX.delete_cache, (url,))
|
||||
LOG.info("DELETE cached %s", cached)
|
||||
|
||||
|
||||
class GetArtworkWorker(threading.Thread):
|
||||
|
||||
is_done = False
|
||||
|
||||
def __init__(self, kodi, queue):
|
||||
|
||||
self.kodi = kodi
|
||||
self.queue = queue
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
|
||||
''' Prepare the request. Request removes the urlencode which is required in this case.
|
||||
Use a session allows to use a pool of connections.
|
||||
'''
|
||||
with requests.Session() as s:
|
||||
while True:
|
||||
try:
|
||||
url = self.queue.get(timeout=2)
|
||||
except Queue.Empty:
|
||||
|
||||
self.is_done = True
|
||||
LOG.info("--<[ q:artwork/%s ]", id(self))
|
||||
|
||||
return
|
||||
|
||||
try:
|
||||
req = requests.Request(method='HEAD',
|
||||
url="http://%s:%s/image/image://%s" % (self.kodi['host'], self.kodi['port'], url),
|
||||
auth=(self.kodi['username'], self.kodi['password']))
|
||||
prep = req.prepare()
|
||||
prep.url = "http://%s:%s/image/image://%s" % (self.kodi['host'], self.kodi['port'], url)
|
||||
s.send(prep, timeout=(0.01, 0.01))
|
||||
s.content # release the connection
|
||||
except Exception as error:
|
||||
LOG.exception(error)
|
||||
|
||||
self.queue.task_done()
|
||||
|
||||
if xbmc.Monitor().abortRequested():
|
||||
break
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import logging
|
||||
import os
|
||||
import urllib
|
||||
from sqlite3 import OperationalError
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcvfs
|
||||
import requests
|
||||
|
||||
import resources.lib.image_cache_thread as image_cache_thread
|
||||
from resources.lib.helper import _, window, settings, JSONRPC
|
||||
from resources.lib.database import Database
|
||||
from __objs__ import QU
|
||||
|
||||
##################################################################################################
|
||||
|
||||
log = logging.getLogger("JELLYFIN."+__name__)
|
||||
|
||||
##################################################################################################
|
||||
|
||||
|
||||
class Artwork(object):
|
||||
|
||||
xbmc_host = 'localhost'
|
||||
xbmc_port = None
|
||||
xbmc_username = None
|
||||
xbmc_password = None
|
||||
|
||||
image_cache_threads = []
|
||||
image_cache_limit = 0
|
||||
|
||||
|
||||
def __init__(self, server):
|
||||
|
||||
self.server = server
|
||||
self.enable_texture_cache = settings('enableTextureCache') == "true"
|
||||
self.image_cache_limit = int(settings('imageCacheLimit')) * 5
|
||||
log.debug("image cache thread count: %s", self.image_cache_limit)
|
||||
|
||||
if not self.xbmc_port and self.enable_texture_cache:
|
||||
self._set_webserver_details()
|
||||
|
||||
|
||||
def texture_cache_sync(self):
|
||||
# This method will sync all Kodi artwork to textures13.db
|
||||
# and cache them locally. This takes diskspace!
|
||||
if not dialog(type_="yesno",
|
||||
heading="{jellyfin}",
|
||||
line1=_(33042)):
|
||||
return
|
||||
|
||||
log.info("Doing Image Cache Sync")
|
||||
|
||||
pdialog = xbmcgui.DialogProgress()
|
||||
pdialog.create(_(29999), _(33043))
|
||||
|
||||
# ask to rest all existing or not
|
||||
if dialog(type_="yesno", heading="{jellyfin}", line1=_(33044)):
|
||||
log.info("Resetting all cache data first")
|
||||
self.delete_cache()
|
||||
|
||||
# Cache all entries in video DB
|
||||
self._cache_all_video_entries(pdialog)
|
||||
# Cache all entries in music DB
|
||||
self._cache_all_music_entries(pdialog)
|
||||
|
||||
pdialog.update(100, "%s %s" % (_(33046), len(self.image_cache_threads)))
|
||||
log.info("Waiting for all threads to exit")
|
||||
|
||||
while len(self.image_cache_threads):
|
||||
for thread in self.image_cache_threads:
|
||||
if thread.is_finished:
|
||||
self.image_cache_threads.remove(thread)
|
||||
pdialog.update(100, "%s %s" % (_(33046), len(self.image_cache_threads)))
|
||||
log.info("Waiting for all threads to exit: %s", len(self.image_cache_threads))
|
||||
xbmc.sleep(500)
|
||||
|
||||
pdialog.close()
|
||||
|
||||
@classmethod
|
||||
def delete_cache(cls):
|
||||
# Remove all existing textures first
|
||||
path = xbmc.translatePath('special://thumbnails/').decode('utf-8')
|
||||
if xbmcvfs.exists(path):
|
||||
dirs, ignore_files = xbmcvfs.listdir(path)
|
||||
for directory in dirs:
|
||||
ignore_dirs, files = xbmcvfs.listdir(path + directory)
|
||||
for file_ in files:
|
||||
|
||||
if os.path.supports_unicode_filenames:
|
||||
filename = os.path.join(path + directory.decode('utf-8'),
|
||||
file_.decode('utf-8'))
|
||||
else:
|
||||
filename = os.path.join(path.encode('utf-8') + directory, file_)
|
||||
|
||||
xbmcvfs.delete(filename)
|
||||
log.debug("deleted: %s", filename)
|
||||
|
||||
# remove all existing data from texture DB
|
||||
with DatabaseConn('texture') as cursor_texture:
|
||||
cursor_texture.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||
rows = cursor_texture.fetchall()
|
||||
for row in rows:
|
||||
table_name = row[0]
|
||||
if table_name != "version":
|
||||
cursor_texture.execute("DELETE FROM " + table_name)
|
||||
|
||||
def _cache_all_video_entries(self, pdialog):
|
||||
|
||||
with Database('video') as cursor_video:
|
||||
|
||||
cursor_video.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors
|
||||
result = cursor_video.fetchall()
|
||||
total = len(result)
|
||||
log.info("Image cache sync about to process %s images", total)
|
||||
cursor_video.close()
|
||||
|
||||
count = 0
|
||||
for url in result:
|
||||
|
||||
if pdialog.iscanceled():
|
||||
break
|
||||
|
||||
percentage = int((float(count) / float(total))*100)
|
||||
message = "%s of %s (%s)" % (count, total, len(self.image_cache_threads))
|
||||
pdialog.update(percentage, "%s %s" % (_(33045), message))
|
||||
self.cache_texture(url[0])
|
||||
count += 1
|
||||
|
||||
def _cache_all_music_entries(self, pdialog):
|
||||
|
||||
with Database('music') as cursor_music:
|
||||
|
||||
cursor_music.execute("SELECT url FROM art")
|
||||
result = cursor_music.fetchall()
|
||||
total = len(result)
|
||||
|
||||
log.info("Image cache sync about to process %s images", total)
|
||||
|
||||
count = 0
|
||||
for url in result:
|
||||
|
||||
if pdialog.iscanceled():
|
||||
break
|
||||
|
||||
percentage = int((float(count) / float(total))*100)
|
||||
message = "%s of %s" % (count, total)
|
||||
pdialog.update(percentage, "%s %s" % (_(33045), message))
|
||||
self.cache_texture(url[0])
|
||||
count += 1
|
||||
|
||||
"""
|
Loading…
Add table
Add a link
Reference in a new issue