From 51e48c57c384d9bab5ab805a230fa654b4fadc6b Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Wed, 8 Feb 2023 17:51:42 -0600 Subject: [PATCH] Icon generation improvements --- src/shellfm/windows/tabs/icons/icon.py | 141 ++++++++++++------ .../tabs/icons/mixins/desktopiconmixin.py | 63 ++++---- 2 files changed, 127 insertions(+), 77 deletions(-) diff --git a/src/shellfm/windows/tabs/icons/icon.py b/src/shellfm/windows/tabs/icons/icon.py index 6e95aa5..fe9d677 100644 --- a/src/shellfm/windows/tabs/icons/icon.py +++ b/src/shellfm/windows/tabs/icons/icon.py @@ -1,6 +1,8 @@ # Python imports -import hashlib +import os from os.path import isfile +import hashlib +import threading # Lib imports import gi @@ -11,7 +13,7 @@ from gi.repository import GdkPixbuf try: from PIL import Image as PImage -except Exception as e: +except ModuleNotFoundError as e: PImage = None # Application imports @@ -21,6 +23,10 @@ from .mixins.desktopiconmixin import DesktopIconMixin +class IconException(Exception): + ... + + class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin): def create_icon(self, dir, file): @@ -29,54 +35,53 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin): def get_icon_image(self, dir, file, full_path): 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.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 thumbnl = self.create_scaled_image(full_path) 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 - thumbnl = self.parse_desktop_files(full_path) + thumbnl = self.find_thumbnail_from_desktop_file(full_path) if not thumbnl: - thumbnl = self.get_system_thumbnail(full_path, self.sys_icon_wh[0]) - - if not thumbnl: - thumbnl = self.return_generic_icon() + raise IconException("No known icons found.") 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: - file_hash = hashlib.sha256(str.encode(full_path)).hexdigest() - hash_img_path = f"{self.ABS_THUMBS_PTH}/{file_hash}.png" - if not isfile(hash_img_path): + path_exists, hash_img_path = self.generate_hash_and_path(full_path) + if not path_exists: self.generate_blender_thumbnail(full_path, hash_img_path) 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( repr(e) ) 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: - file_hash = hashlib.sha256(str.encode(full_path)).hexdigest() - hash_img_path = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg" - if not isfile(hash_img_path): + path_exists, hash_img_path = self.generate_hash_and_path(full_path) + if path_exists and replace: + os.remove(hash_img_path) + path_exists = False + + if not path_exists: self.generate_video_thumbnail(full_path, hash_img_path, scrub_percent) 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( repr(e) ) @@ -97,12 +102,77 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin): return self.image2pixbuf(full_path, wxh) 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( repr(e) ) 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): """Convert Pillow image to GdkPixbuf""" im = PImage.open(full_path) @@ -114,28 +184,3 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin): False, 8, w, h, w * 3) 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) diff --git a/src/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py b/src/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py index 0959a2e..c1f6be0 100644 --- a/src/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py +++ b/src/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py @@ -7,7 +7,6 @@ import hashlib # Lib imports import gi gi.require_version('Gtk', '3.0') - from gi.repository import Gtk from gi.repository import Gio @@ -18,50 +17,56 @@ from .xdg.DesktopEntry import DesktopEntry class DesktopIconMixin: - def parse_desktop_files(self, full_path): + def find_thumbnail_from_desktop_file(self, full_path): try: xdgObj = DesktopEntry(full_path) icon = xdgObj.getIcon() alt_icon_path = "" if "steam" in icon: - name = xdgObj.getName() - 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) + return self.get_steam_img(xdgObj) elif os.path.exists(icon): return self.create_scaled_image(icon, self.sys_icon_wh) else: - gio_icon = Gio.Icon.new_for_string(icon) - gicon = Gtk.Image.new_from_gicon(gio_icon, 32) - pixbuf = gicon.get_pixbuf() + pixbuf, alt_icon_path = self.get_icon_from_traversal(icon) if 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) except Exception as e: print(".desktop icon generation issue:") 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): for (dirpath, dirnames, filenames) in os.walk(path):