Icon generation improvements
This commit is contained in:
parent
9ae0182928
commit
51e48c57c3
|
@ -1,6 +1,8 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
import hashlib
|
import os
|
||||||
from os.path import isfile
|
from os.path import isfile
|
||||||
|
import hashlib
|
||||||
|
import threading
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
|
@ -11,7 +13,7 @@ from gi.repository import GdkPixbuf
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import Image as PImage
|
from PIL import Image as PImage
|
||||||
except Exception as e:
|
except ModuleNotFoundError as e:
|
||||||
PImage = None
|
PImage = None
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
|
@ -21,6 +23,10 @@ from .mixins.desktopiconmixin import DesktopIconMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class IconException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
||||||
def create_icon(self, dir, file):
|
def create_icon(self, dir, file):
|
||||||
|
@ -29,54 +35,53 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
||||||
|
|
||||||
def get_icon_image(self, dir, file, full_path):
|
def get_icon_image(self, dir, file, full_path):
|
||||||
try:
|
try:
|
||||||
thumbnl = None
|
thumbnl = self._get_system_thumbnail_gtk_thread(full_path, self.sys_icon_wh[0])
|
||||||
|
|
||||||
if file.lower().endswith(self.fmeshs): # 3D Mesh icon
|
if file.lower().endswith(self.fmeshs): # 3D Mesh icon
|
||||||
...
|
...
|
||||||
if file.lower().endswith(self.fvideos): # Video icon
|
if file.lower().endswith(self.fvideos): # Video icon
|
||||||
thumbnl = self.create_thumbnail(dir, file, full_path)
|
thumbnl = self.create_video_thumbnail(full_path)
|
||||||
elif file.lower().endswith(self.fimages): # Image Icon
|
elif file.lower().endswith(self.fimages): # Image Icon
|
||||||
thumbnl = self.create_scaled_image(full_path)
|
thumbnl = self.create_scaled_image(full_path)
|
||||||
elif file.lower().endswith( (".blend",) ): # Blender icon
|
elif file.lower().endswith( (".blend",) ): # Blender icon
|
||||||
thumbnl = self.create_blender_thumbnail(dir, file, full_path)
|
thumbnl = self.create_blender_thumbnail(full_path)
|
||||||
elif full_path.lower().endswith( ('.desktop',) ): # .desktop file parsing
|
elif full_path.lower().endswith( ('.desktop',) ): # .desktop file parsing
|
||||||
thumbnl = self.parse_desktop_files(full_path)
|
thumbnl = self.find_thumbnail_from_desktop_file(full_path)
|
||||||
|
|
||||||
if not thumbnl:
|
if not thumbnl:
|
||||||
thumbnl = self.get_system_thumbnail(full_path, self.sys_icon_wh[0])
|
raise IconException("No known icons found.")
|
||||||
|
|
||||||
if not thumbnl:
|
|
||||||
thumbnl = self.return_generic_icon()
|
|
||||||
|
|
||||||
return thumbnl
|
return thumbnl
|
||||||
except Exception:
|
except IconException:
|
||||||
...
|
...
|
||||||
|
|
||||||
return self.return_generic_icon()
|
return self.get_generic_icon()
|
||||||
|
|
||||||
def create_blender_thumbnail(self, dir, file, full_path=None):
|
def create_blender_thumbnail(self, full_path):
|
||||||
try:
|
try:
|
||||||
file_hash = hashlib.sha256(str.encode(full_path)).hexdigest()
|
path_exists, hash_img_path = self.generate_hash_and_path(full_path)
|
||||||
hash_img_path = f"{self.ABS_THUMBS_PTH}/{file_hash}.png"
|
if not path_exists:
|
||||||
if not isfile(hash_img_path):
|
|
||||||
self.generate_blender_thumbnail(full_path, hash_img_path)
|
self.generate_blender_thumbnail(full_path, hash_img_path)
|
||||||
|
|
||||||
return self.create_scaled_image(hash_img_path, self.video_icon_wh)
|
return self.create_scaled_image(hash_img_path, self.video_icon_wh)
|
||||||
except Exception as e:
|
except IconException as e:
|
||||||
print("Blender thumbnail generation issue:")
|
print("Blender thumbnail generation issue:")
|
||||||
print( repr(e) )
|
print( repr(e) )
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def create_thumbnail(self, dir, file, full_path=None, scrub_percent = "65%"):
|
def create_video_thumbnail(self, full_path, scrub_percent = "65%", replace=False):
|
||||||
try:
|
try:
|
||||||
file_hash = hashlib.sha256(str.encode(full_path)).hexdigest()
|
path_exists, hash_img_path = self.generate_hash_and_path(full_path)
|
||||||
hash_img_path = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg"
|
if path_exists and replace:
|
||||||
if not isfile(hash_img_path):
|
os.remove(hash_img_path)
|
||||||
|
path_exists = False
|
||||||
|
|
||||||
|
if not path_exists:
|
||||||
self.generate_video_thumbnail(full_path, hash_img_path, scrub_percent)
|
self.generate_video_thumbnail(full_path, hash_img_path, scrub_percent)
|
||||||
|
|
||||||
return self.create_scaled_image(hash_img_path, self.video_icon_wh)
|
return self.create_scaled_image(hash_img_path, self.video_icon_wh)
|
||||||
except Exception as e:
|
except IconException as e:
|
||||||
print("Image/Video thumbnail generation issue:")
|
print("Image/Video thumbnail generation issue:")
|
||||||
print( repr(e) )
|
print( repr(e) )
|
||||||
|
|
||||||
|
@ -97,12 +102,77 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
||||||
return self.image2pixbuf(full_path, wxh)
|
return self.image2pixbuf(full_path, wxh)
|
||||||
|
|
||||||
return GdkPixbuf.Pixbuf.new_from_file_at_scale(full_path, wxh[0], wxh[1], True)
|
return GdkPixbuf.Pixbuf.new_from_file_at_scale(full_path, wxh[0], wxh[1], True)
|
||||||
except Exception as e:
|
except IconException as e:
|
||||||
print("Image Scaling Issue:")
|
print("Image Scaling Issue:")
|
||||||
print( repr(e) )
|
print( repr(e) )
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def create_from_file(self, full_path):
|
||||||
|
try:
|
||||||
|
return GdkPixbuf.Pixbuf.new_from_file(full_path)
|
||||||
|
except IconException as e:
|
||||||
|
print("Image from file Issue:")
|
||||||
|
print( repr(e) )
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_system_thumbnail_gtk_thread(self, full_path, size):
|
||||||
|
def _call_gtk_thread(event, result):
|
||||||
|
result.append( self.get_system_thumbnail(full_path, size) )
|
||||||
|
event.set()
|
||||||
|
|
||||||
|
result = []
|
||||||
|
event = threading.Event()
|
||||||
|
GLib.idle_add(_call_gtk_thread, event, result)
|
||||||
|
event.wait()
|
||||||
|
return result[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_thumbnail(self, full_path, size):
|
||||||
|
try:
|
||||||
|
gio_file = Gio.File.new_for_path(full_path)
|
||||||
|
info = gio_file.query_info('standard::icon' , 0, None)
|
||||||
|
icon = info.get_icon().get_names()[0]
|
||||||
|
data = settings.get_icon_theme().lookup_icon(icon , size , 0)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
icon_path = data.get_filename()
|
||||||
|
return GdkPixbuf.Pixbuf.new_from_file(icon_path)
|
||||||
|
|
||||||
|
raise IconException("No system icon found...")
|
||||||
|
except IconException:
|
||||||
|
...
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_generic_icon(self):
|
||||||
|
return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICON)
|
||||||
|
|
||||||
|
def generate_hash_and_path(self, full_path):
|
||||||
|
file_hash = self.fast_hash(full_path)
|
||||||
|
hash_img_path = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg"
|
||||||
|
path_exists = True if isfile(hash_img_path) else False
|
||||||
|
|
||||||
|
return path_exists, hash_img_path
|
||||||
|
|
||||||
|
|
||||||
|
def fast_hash(self, filename, hash_factory=hashlib.md5, chunk_num_blocks=128, i=1):
|
||||||
|
h = hash_factory()
|
||||||
|
with open(filename,'rb') as f:
|
||||||
|
f.seek(0, 2)
|
||||||
|
mid = int(f.tell() / 2)
|
||||||
|
f.seek(mid, 0)
|
||||||
|
|
||||||
|
while chunk := f.read(chunk_num_blocks*h.block_size):
|
||||||
|
h.update(chunk)
|
||||||
|
if (i == 12):
|
||||||
|
break
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return h.hexdigest()
|
||||||
|
|
||||||
def image2pixbuf(self, full_path, wxh):
|
def image2pixbuf(self, full_path, wxh):
|
||||||
"""Convert Pillow image to GdkPixbuf"""
|
"""Convert Pillow image to GdkPixbuf"""
|
||||||
im = PImage.open(full_path)
|
im = PImage.open(full_path)
|
||||||
|
@ -114,28 +184,3 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
||||||
False, 8, w, h, w * 3)
|
False, 8, w, h, w * 3)
|
||||||
|
|
||||||
return pixbuf.scale_simple(wxh[0], wxh[1], 2) # BILINEAR = 2
|
return pixbuf.scale_simple(wxh[0], wxh[1], 2) # BILINEAR = 2
|
||||||
|
|
||||||
def create_from_file(self, full_path):
|
|
||||||
try:
|
|
||||||
return GdkPixbuf.Pixbuf.new_from_file(full_path)
|
|
||||||
except Exception as e:
|
|
||||||
print("Image from file Issue:")
|
|
||||||
print( repr(e) )
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_system_thumbnail(self, filename, size):
|
|
||||||
try:
|
|
||||||
gio_file = Gio.File.new_for_path(filename)
|
|
||||||
info = gio_file.query_info('standard::icon' , 0, None)
|
|
||||||
icon = info.get_icon().get_names()[0]
|
|
||||||
icon_path = settings.get_icon_theme().lookup_icon(icon , size , 0).get_filename()
|
|
||||||
|
|
||||||
return GdkPixbuf.Pixbuf.new_from_file(icon_path)
|
|
||||||
except Exception:
|
|
||||||
...
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def return_generic_icon(self):
|
|
||||||
return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICON)
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import hashlib
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
|
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import Gio
|
from gi.repository import Gio
|
||||||
|
|
||||||
|
@ -18,50 +17,56 @@ from .xdg.DesktopEntry import DesktopEntry
|
||||||
|
|
||||||
|
|
||||||
class DesktopIconMixin:
|
class DesktopIconMixin:
|
||||||
def parse_desktop_files(self, full_path):
|
def find_thumbnail_from_desktop_file(self, full_path):
|
||||||
try:
|
try:
|
||||||
xdgObj = DesktopEntry(full_path)
|
xdgObj = DesktopEntry(full_path)
|
||||||
icon = xdgObj.getIcon()
|
icon = xdgObj.getIcon()
|
||||||
alt_icon_path = ""
|
alt_icon_path = ""
|
||||||
|
|
||||||
if "steam" in icon:
|
if "steam" in icon:
|
||||||
name = xdgObj.getName()
|
return self.get_steam_img(xdgObj)
|
||||||
file_hash = hashlib.sha256(str.encode(name)).hexdigest()
|
|
||||||
hash_img_pth = f"{self.STEAM_ICONS_PTH}/{file_hash}.jpg"
|
|
||||||
|
|
||||||
if isfile(hash_img_pth) == True:
|
|
||||||
# Use video sizes since headers are bigger
|
|
||||||
return self.create_scaled_image(hash_img_pth, self.video_icon_wh)
|
|
||||||
|
|
||||||
exec_str = xdgObj.getExec()
|
|
||||||
parts = exec_str.split("steam://rungameid/")
|
|
||||||
id = parts[len(parts) - 1]
|
|
||||||
imageLink = f"{self.STEAM_CDN_URL}{id}/header.jpg"
|
|
||||||
proc = subprocess.Popen(["wget", "-O", hash_img_pth, imageLink])
|
|
||||||
proc.wait()
|
|
||||||
|
|
||||||
# Use video thumbnail sizes since headers are bigger
|
|
||||||
return self.create_scaled_image(hash_img_pth, self.video_icon_wh)
|
|
||||||
elif os.path.exists(icon):
|
elif os.path.exists(icon):
|
||||||
return self.create_scaled_image(icon, self.sys_icon_wh)
|
return self.create_scaled_image(icon, self.sys_icon_wh)
|
||||||
else:
|
else:
|
||||||
gio_icon = Gio.Icon.new_for_string(icon)
|
pixbuf, alt_icon_path = self.get_icon_from_traversal(icon)
|
||||||
gicon = Gtk.Image.new_from_gicon(gio_icon, 32)
|
|
||||||
pixbuf = gicon.get_pixbuf()
|
|
||||||
if pixbuf:
|
if pixbuf:
|
||||||
return pixbuf
|
return pixbuf
|
||||||
|
|
||||||
alt_icon_path = ""
|
|
||||||
for dir in self.ICON_DIRS:
|
|
||||||
alt_icon_path = self.traverse_icons_folder(dir, icon)
|
|
||||||
if alt_icon_path != "":
|
|
||||||
break
|
|
||||||
|
|
||||||
return self.create_scaled_image(alt_icon_path, self.sys_icon_wh)
|
return self.create_scaled_image(alt_icon_path, self.sys_icon_wh)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(".desktop icon generation issue:")
|
print(".desktop icon generation issue:")
|
||||||
print( repr(e) )
|
print( repr(e) )
|
||||||
return None
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_steam_img(self, xdgObj):
|
||||||
|
name = xdgObj.getName()
|
||||||
|
file_hash = hashlib.sha256(str.encode(name)).hexdigest()
|
||||||
|
hash_img_pth = f"{self.STEAM_ICONS_PTH}/{file_hash}.jpg"
|
||||||
|
|
||||||
|
if not isfile(hash_img_pth):
|
||||||
|
exec_str = xdgObj.getExec()
|
||||||
|
parts = exec_str.split("steam://rungameid/")
|
||||||
|
id = parts[len(parts) - 1]
|
||||||
|
imageLink = f"{self.STEAM_CDN_URL}{id}/header.jpg"
|
||||||
|
proc = subprocess.Popen(["wget", "-O", hash_img_pth, imageLink])
|
||||||
|
proc.wait()
|
||||||
|
|
||||||
|
return self.create_scaled_image(hash_img_pth, self.video_icon_wh)
|
||||||
|
|
||||||
|
def get_icon_from_traversal(self, icon):
|
||||||
|
gio_icon = Gio.Icon.new_for_string(icon)
|
||||||
|
gicon = Gtk.Image.new_from_gicon(gio_icon, 32)
|
||||||
|
pixbuf = gicon.get_pixbuf()
|
||||||
|
|
||||||
|
alt_icon_path = ""
|
||||||
|
for dir in self.ICON_DIRS:
|
||||||
|
alt_icon_path = self.traverse_icons_folder(dir, icon)
|
||||||
|
if alt_icon_path != "":
|
||||||
|
break
|
||||||
|
|
||||||
|
return pixbuf, alt_icon_path
|
||||||
|
|
||||||
def traverse_icons_folder(self, path, icon):
|
def traverse_icons_folder(self, path, icon):
|
||||||
for (dirpath, dirnames, filenames) in os.walk(path):
|
for (dirpath, dirnames, filenames) in os.walk(path):
|
||||||
|
|
Loading…
Reference in New Issue