From d641c3dba8427bf1555379b3ac3e18355115a55c Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 28 Jan 2023 22:34:21 -0600 Subject: [PATCH] Updates and cleanup on thumbnailer logic --- src/shellfm/windows/tabs/icons/icon.py | 91 ++++++++++++------- .../tabs/icons/mixins/meshsiconmixin.py | 15 +++ .../tabs/icons/mixins/videoiconmixin.py | 12 +-- src/shellfm/windows/tabs/utils/settings.py | 8 +- src/user_config/shellfm/settings.json | 3 +- 5 files changed, 84 insertions(+), 45 deletions(-) create mode 100644 src/shellfm/windows/tabs/icons/mixins/meshsiconmixin.py diff --git a/src/shellfm/windows/tabs/icons/icon.py b/src/shellfm/windows/tabs/icons/icon.py index 0d392ac..b17bd62 100644 --- a/src/shellfm/windows/tabs/icons/icon.py +++ b/src/shellfm/windows/tabs/icons/icon.py @@ -1,31 +1,27 @@ # Python Imports -import os, subprocess, threading, hashlib +import hashlib from os.path import isfile # Lib imports import gi gi.require_version('GdkPixbuf', '2.0') -from gi.repository import GdkPixbuf, GLib - +from gi.repository import GLib +from gi.repository import Gio +from gi.repository import GdkPixbuf try: from PIL import Image as PImage except Exception as e: PImage = None - # Application imports from .mixins.desktopiconmixin import DesktopIconMixin from .mixins.videoiconmixin import VideoIconMixin -def threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs).start() - return wrapper -class Icon(DesktopIconMixin, VideoIconMixin): +class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin): def create_icon(self, dir, file): full_path = f"{dir}/{file}" return self.get_icon_image(dir, file, full_path) @@ -34,10 +30,14 @@ class Icon(DesktopIconMixin, VideoIconMixin): try: thumbnl = None + if file.lower().endswith(self.fmeshs): # 3D Mesh icon + ... if file.lower().endswith(self.fvideos): # Video icon - thumbnl = self.create_thumbnail(dir, file) + thumbnl = self.create_thumbnail(dir, file, 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) elif full_path.lower().endswith( ('.desktop',) ): # .desktop file parsing thumbnl = self.parse_desktop_files(full_path) @@ -47,50 +47,58 @@ class Icon(DesktopIconMixin, VideoIconMixin): return None - def create_thumbnail(self, dir, file, scrub_percent = "65%"): - full_path = f"{dir}/{file}" - + def create_blender_thumbnail(self, dir, file, full_path=None): try: - file_hash = hashlib.sha256(str.encode(full_path)).hexdigest() - hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg" - if isfile(hash_img_pth) == False: - self.generate_video_thumbnail(full_path, hash_img_pth, scrub_percent) + 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): + self.generate_blender_thumbnail(full_path, hash_img_path) - thumbnl = self.create_scaled_image(hash_img_pth, self.video_icon_wh) - if thumbnl == None: # If no icon whatsoever, return internal default - thumbnl = GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png") - - return thumbnl + return self.create_scaled_image(hash_img_path, self.video_icon_wh) except Exception as e: - print("Thumbnail generation issue:") + print("Blender thumbnail generation issue:") print( repr(e) ) - return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png") + return None + + def create_thumbnail(self, dir, file, full_path=None, scrub_percent = "65%"): + 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): + 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: + print("Image/Video thumbnail generation issue:") + print( repr(e) ) + + return None - def create_scaled_image(self, path, wxh = None): + def create_scaled_image(self, full_path, wxh = None): if not wxh: wxh = self.video_icon_wh - if path: + if full_path: try: - if path.lower().endswith(".gif"): - return GdkPixbuf.PixbufAnimation.new_from_file(path) \ + if full_path.lower().endswith(".gif"): + return GdkPixbuf.PixbufAnimation.new_from_file(full_path) \ .get_static_image() \ .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR) - elif path.lower().endswith(".webp") and PImage: - return self.image2pixbuf(path, wxh) + elif full_path.lower().endswith(".webp") and PImage: + return self.image2pixbuf(full_path, wxh) - return GdkPixbuf.Pixbuf.new_from_file_at_scale(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: print("Image Scaling Issue:") print( repr(e) ) return None - def image2pixbuf(self, path, wxh): + def image2pixbuf(self, full_path, wxh): """Convert Pillow image to GdkPixbuf""" - im = PImage.open(path) + im = PImage.open(full_path) data = im.tobytes() data = GLib.Bytes.new(data) w, h = im.size @@ -100,9 +108,9 @@ class Icon(DesktopIconMixin, VideoIconMixin): return pixbuf.scale_simple(wxh[0], wxh[1], 2) # BILINEAR = 2 - def create_from_file(self, path): + def create_from_file(self, full_path): try: - return GdkPixbuf.Pixbuf.new_from_file(path) + return GdkPixbuf.Pixbuf.new_from_file(full_path) except Exception as e: print("Image from file Issue:") print( repr(e) ) @@ -111,3 +119,16 @@ class Icon(DesktopIconMixin, VideoIconMixin): def return_generic_icon(self): return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICON) + + 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 diff --git a/src/shellfm/windows/tabs/icons/mixins/meshsiconmixin.py b/src/shellfm/windows/tabs/icons/mixins/meshsiconmixin.py new file mode 100644 index 0000000..07bc757 --- /dev/null +++ b/src/shellfm/windows/tabs/icons/mixins/meshsiconmixin.py @@ -0,0 +1,15 @@ +# Python Imports +import subprocess + +# Lib imports + +# Application imports + + +class MeshsIconMixin: + def generate_blender_thumbnail(self, full_path, hash_img_path): + try: + proc = subprocess.Popen([self.BLENDER_THUMBNLR, full_path, hash_img_path]) + proc.wait() + except Exception as e: + self.logger.debug(repr(e)) diff --git a/src/shellfm/windows/tabs/icons/mixins/videoiconmixin.py b/src/shellfm/windows/tabs/icons/mixins/videoiconmixin.py index a1cc2eb..f1e3492 100644 --- a/src/shellfm/windows/tabs/icons/mixins/videoiconmixin.py +++ b/src/shellfm/windows/tabs/icons/mixins/videoiconmixin.py @@ -1,22 +1,22 @@ # Python Imports import subprocess -# Gtk imports +# Lib imports # Application imports class VideoIconMixin: - def generate_video_thumbnail(self, full_path, hash_img_pth, scrub_percent = "65%"): + def generate_video_thumbnail(self, full_path, hash_img_path, scrub_percent = "65%"): try: - proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", scrub_percent, "-s", "300", "-c", "jpg", "-i", full_path, "-o", hash_img_pth]) + proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", scrub_percent, "-s", "300", "-c", "jpg", "-i", full_path, "-o", hash_img_path]) proc.wait() except Exception as e: self.logger.debug(repr(e)) - self.ffprobe_generate_video_thumbnail(full_path, hash_img_pth) + self.ffprobe_generate_video_thumbnail(full_path, hash_img_path) - def ffprobe_generate_video_thumbnail(self, full_path, hash_img_pth): + def ffprobe_generate_video_thumbnail(self, full_path, hash_img_path): proc = None try: # Stream duration @@ -44,7 +44,7 @@ class VideoIconMixin: # Get frame roughly 35% through video grabTime = str( int( float( duration.split(".")[0] ) * 0.35) ) - command = ["ffmpeg", "-ss", grabTime, "-an", "-i", full_path, "-s", "320x180", "-vframes", "1", hash_img_pth] + command = ["ffmpeg", "-ss", grabTime, "-an", "-i", full_path, "-s", "320x180", "-vframes", "1", hash_img_path] proc = subprocess.Popen(command, stdout=subprocess.PIPE) proc.wait() except Exception as e: diff --git a/src/shellfm/windows/tabs/utils/settings.py b/src/shellfm/windows/tabs/utils/settings.py index 89bd32b..78c90df 100644 --- a/src/shellfm/windows/tabs/utils/settings.py +++ b/src/shellfm/windows/tabs/utils/settings.py @@ -22,8 +22,9 @@ class Settings: GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1) DEFAULT_ICONS = f"{CONFIG_PATH}/icons" DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png" - FFMPG_THUMBNLR = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary - REMUX_FOLDER = f"{USER_HOME}/.remuxs" # Remuxed files folder + FFMPG_THUMBNLR = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary + BLENDER_THUMBNLR = f"{CONFIG_PATH}/blender-thumbnailer" # Blender thumbnail generator binary + REMUX_FOLDER = f"{USER_HOME}/.remuxs" # Remuxed files folder ICON_DIRS = ["/usr/share/icons", f"{USER_HOME}/.icons" "/usr/share/pixmaps"] BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails" # Used for thumbnail generation @@ -53,7 +54,8 @@ class Settings: subpath = config["base_of_home"] STEAM_CDN_URL = config["steam_cdn_url"] - FFMPG_THUMBNLR = FFMPG_THUMBNLR if config["thumbnailer_path"] == "" else config["thumbnailer_path"] + FFMPG_THUMBNLR = FFMPG_THUMBNLR if config["thumbnailer_path"] == "" else config["thumbnailer_path"] + BLENDER_THUMBNLR = BLENDER_THUMBNLR if config["blender_thumbnailer_path"] == "" else config["blender_thumbnailer_path"] HIDE_HIDDEN_FILES = True if config["hide_hidden_files"] == "true" else False go_past_home = True if config["go_past_home"] == "" else config["go_past_home"] lock_folder = True if config["lock_folder"] == "true" else False diff --git a/src/user_config/shellfm/settings.json b/src/user_config/shellfm/settings.json index 1428f6f..ab27ec7 100644 --- a/src/user_config/shellfm/settings.json +++ b/src/user_config/shellfm/settings.json @@ -3,6 +3,7 @@ "base_of_home": "", "hide_hidden_files": "true", "thumbnailer_path": "ffmpegthumbnailer", + "blender_thumbnailer_path": "", "go_past_home": "true", "lock_folder": "false", "locked_folders": "venv::::flasks", @@ -23,7 +24,7 @@ "remux_folder_max_disk_usage": "8589934592" }, "filters": { - "meshs": [".blend", ".dae", ".fbx", ".gltf", ".obj", ".stl"], + "meshs": [".dae", ".fbx", ".gltf", ".obj", ".stl"], "code": [".cpp", ".css", ".c", ".go", ".html", ".htm", ".java", ".js", ".json", ".lua", ".md", ".py", ".rs"], "videos": [".mkv", ".mp4", ".webm", ".avi", ".mov", ".m4v", ".mpg", ".mpeg", ".wmv", ".flv"], "office": [".doc", ".docx", ".xls", ".xlsx", ".xlt", ".xltx", ".xlm", ".ppt", ".pptx", ".pps", ".ppsx", ".odt", ".rtf"],