2023-01-29 06:10:09 +00:00
|
|
|
# Python imports
|
2023-02-04 22:32:00 +00:00
|
|
|
import os
|
2021-10-10 06:45:55 +00:00
|
|
|
from os.path import isfile
|
2023-02-08 23:52:33 +00:00
|
|
|
import hashlib
|
|
|
|
import threading
|
2021-10-10 06:45:55 +00:00
|
|
|
|
2022-10-01 04:30:38 +00:00
|
|
|
# Lib imports
|
2022-02-11 06:58:49 +00:00
|
|
|
import gi
|
|
|
|
gi.require_version('GdkPixbuf', '2.0')
|
2023-01-29 06:10:09 +00:00
|
|
|
from gi.repository import GLib
|
|
|
|
from gi.repository import Gio
|
|
|
|
from gi.repository import GdkPixbuf
|
2022-10-01 04:30:38 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
from PIL import Image as PImage
|
2023-02-08 23:52:33 +00:00
|
|
|
except ModuleNotFoundError as e:
|
2022-10-01 04:30:38 +00:00
|
|
|
PImage = None
|
|
|
|
|
2021-10-10 06:45:55 +00:00
|
|
|
# Application imports
|
2022-02-11 06:58:49 +00:00
|
|
|
from .mixins.videoiconmixin import VideoIconMixin
|
2023-01-29 06:10:09 +00:00
|
|
|
from .mixins.meshsiconmixin import MeshsIconMixin
|
|
|
|
from .mixins.desktopiconmixin import DesktopIconMixin
|
2021-10-10 06:45:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2023-02-08 23:52:33 +00:00
|
|
|
class IconException(Exception):
|
|
|
|
...
|
|
|
|
|
|
|
|
|
2021-10-10 06:45:55 +00:00
|
|
|
|
2023-01-29 06:10:09 +00:00
|
|
|
class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
2021-10-10 06:45:55 +00:00
|
|
|
def create_icon(self, dir, file):
|
2022-01-19 22:10:43 +00:00
|
|
|
full_path = f"{dir}/{file}"
|
2021-10-10 06:45:55 +00:00
|
|
|
return self.get_icon_image(dir, file, full_path)
|
|
|
|
|
|
|
|
def get_icon_image(self, dir, file, full_path):
|
|
|
|
try:
|
2023-03-05 03:45:29 +00:00
|
|
|
thumbnl = None
|
2021-10-10 06:45:55 +00:00
|
|
|
|
2023-01-29 06:10:09 +00:00
|
|
|
if file.lower().endswith(self.fmeshs): # 3D Mesh icon
|
|
|
|
...
|
2023-03-05 03:45:29 +00:00
|
|
|
elif file.lower().endswith(self.fvideos): # Video icon
|
2023-02-04 22:32:00 +00:00
|
|
|
thumbnl = self.create_video_thumbnail(full_path)
|
2021-10-10 06:45:55 +00:00
|
|
|
elif file.lower().endswith(self.fimages): # Image Icon
|
2022-09-03 22:01:41 +00:00
|
|
|
thumbnl = self.create_scaled_image(full_path)
|
2023-01-29 06:10:09 +00:00
|
|
|
elif file.lower().endswith( (".blend",) ): # Blender icon
|
2023-02-04 22:32:00 +00:00
|
|
|
thumbnl = self.create_blender_thumbnail(full_path)
|
2021-10-10 06:45:55 +00:00
|
|
|
elif full_path.lower().endswith( ('.desktop',) ): # .desktop file parsing
|
2023-02-04 22:32:00 +00:00
|
|
|
thumbnl = self.find_thumbnail_from_desktop_file(full_path)
|
2021-10-10 06:45:55 +00:00
|
|
|
|
2023-01-29 06:10:09 +00:00
|
|
|
if not thumbnl:
|
2023-03-28 01:07:17 +00:00
|
|
|
# TODO: Detect if not in a thread and use directly for speed get_system_thumbnail
|
2024-03-12 01:03:13 +00:00
|
|
|
thumbnl = self.get_system_thumbnail(full_path, self.sys_icon_wh[0])
|
|
|
|
# thumbnl = self._get_system_thumbnail_gtk_thread(full_path, self.sys_icon_wh[0])
|
2023-03-05 03:45:29 +00:00
|
|
|
if not thumbnl:
|
|
|
|
raise IconException("No known icons found.")
|
|
|
|
|
2023-01-29 06:10:09 +00:00
|
|
|
|
2021-10-10 06:45:55 +00:00
|
|
|
return thumbnl
|
2023-02-08 23:52:33 +00:00
|
|
|
except IconException:
|
2022-10-02 03:12:14 +00:00
|
|
|
...
|
|
|
|
|
2023-02-04 22:32:00 +00:00
|
|
|
return self.get_generic_icon()
|
2022-10-02 03:12:14 +00:00
|
|
|
|
2023-02-10 02:55:21 +00:00
|
|
|
def create_blender_thumbnail(self, full_path, returnHashInstead=False):
|
2021-10-10 06:45:55 +00:00
|
|
|
try:
|
2023-02-10 02:55:21 +00:00
|
|
|
path_exists, img_hash, hash_img_path = self.generate_hash_and_path(full_path)
|
2023-02-04 22:32:00 +00:00
|
|
|
if not path_exists:
|
2023-01-29 06:10:09 +00:00
|
|
|
self.generate_blender_thumbnail(full_path, hash_img_path)
|
2021-10-10 06:45:55 +00:00
|
|
|
|
2023-02-10 02:55:21 +00:00
|
|
|
if returnHashInstead:
|
|
|
|
return img_hash, hash_img_path
|
|
|
|
|
2023-01-29 06:10:09 +00:00
|
|
|
return self.create_scaled_image(hash_img_path, self.video_icon_wh)
|
2023-02-08 23:52:33 +00:00
|
|
|
except IconException as e:
|
2023-01-29 06:10:09 +00:00
|
|
|
print("Blender thumbnail generation issue:")
|
|
|
|
print( repr(e) )
|
2021-10-10 06:45:55 +00:00
|
|
|
|
2023-01-29 06:10:09 +00:00
|
|
|
return None
|
|
|
|
|
2023-02-10 02:55:21 +00:00
|
|
|
def create_video_thumbnail(self, full_path, scrub_percent = "65%", replace=False, returnHashInstead=False):
|
2023-01-29 06:10:09 +00:00
|
|
|
try:
|
2023-02-10 02:55:21 +00:00
|
|
|
path_exists, img_hash, hash_img_path = self.generate_hash_and_path(full_path)
|
2023-02-04 22:32:00 +00:00
|
|
|
if path_exists and replace:
|
|
|
|
os.remove(hash_img_path)
|
|
|
|
path_exists = False
|
|
|
|
|
|
|
|
if not path_exists:
|
2023-01-29 06:10:09 +00:00
|
|
|
self.generate_video_thumbnail(full_path, hash_img_path, scrub_percent)
|
|
|
|
|
2023-02-10 02:55:21 +00:00
|
|
|
if returnHashInstead:
|
|
|
|
return img_hash, hash_img_path
|
|
|
|
|
2023-01-29 06:10:09 +00:00
|
|
|
return self.create_scaled_image(hash_img_path, self.video_icon_wh)
|
2023-02-08 23:52:33 +00:00
|
|
|
except IconException as e:
|
2023-01-29 06:10:09 +00:00
|
|
|
print("Image/Video thumbnail generation issue:")
|
2021-10-10 06:45:55 +00:00
|
|
|
print( repr(e) )
|
2022-10-02 03:12:14 +00:00
|
|
|
|
2023-01-29 06:10:09 +00:00
|
|
|
return None
|
2021-10-10 06:45:55 +00:00
|
|
|
|
|
|
|
|
2023-01-29 06:10:09 +00:00
|
|
|
def create_scaled_image(self, full_path, wxh = None):
|
2022-09-03 22:01:41 +00:00
|
|
|
if not wxh:
|
|
|
|
wxh = self.video_icon_wh
|
|
|
|
|
2023-01-29 06:10:09 +00:00
|
|
|
if full_path:
|
2022-10-02 03:12:14 +00:00
|
|
|
try:
|
2023-01-29 06:10:09 +00:00
|
|
|
if full_path.lower().endswith(".gif"):
|
|
|
|
return GdkPixbuf.PixbufAnimation.new_from_file(full_path) \
|
2022-10-02 03:12:14 +00:00
|
|
|
.get_static_image() \
|
|
|
|
.scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR)
|
2023-01-29 06:10:09 +00:00
|
|
|
elif full_path.lower().endswith(".webp") and PImage:
|
|
|
|
return self.image2pixbuf(full_path, wxh)
|
2022-10-02 03:12:14 +00:00
|
|
|
|
2023-03-28 01:07:17 +00:00
|
|
|
pixbuf = None
|
|
|
|
try:
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(full_path, wxh[0], wxh[1], True)
|
|
|
|
except Exception as e:
|
|
|
|
...
|
|
|
|
|
|
|
|
return pixbuf
|
2023-02-08 23:52:33 +00:00
|
|
|
except IconException as e:
|
2022-10-02 03:12:14 +00:00
|
|
|
print("Image Scaling Issue:")
|
|
|
|
print( repr(e) )
|
|
|
|
|
|
|
|
return None
|
2021-10-10 06:45:55 +00:00
|
|
|
|
2023-01-29 06:10:09 +00:00
|
|
|
def create_from_file(self, full_path):
|
2021-10-10 06:45:55 +00:00
|
|
|
try:
|
2023-01-29 06:10:09 +00:00
|
|
|
return GdkPixbuf.Pixbuf.new_from_file(full_path)
|
2023-02-08 23:52:33 +00:00
|
|
|
except IconException as e:
|
2021-10-10 06:45:55 +00:00
|
|
|
print("Image from file Issue:")
|
|
|
|
print( repr(e) )
|
2022-10-02 03:12:14 +00:00
|
|
|
|
|
|
|
return None
|
2021-10-10 06:45:55 +00:00
|
|
|
|
2023-02-08 23:52:33 +00:00
|
|
|
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()
|
2024-01-01 04:20:04 +00:00
|
|
|
return False
|
2023-02-08 23:52:33 +00:00
|
|
|
|
|
|
|
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):
|
2023-01-29 06:10:09 +00:00
|
|
|
try:
|
2023-02-08 23:52:33 +00:00
|
|
|
gio_file = Gio.File.new_for_path(full_path)
|
2023-01-29 06:10:09 +00:00
|
|
|
info = gio_file.query_info('standard::icon' , 0, None)
|
|
|
|
icon = info.get_icon().get_names()[0]
|
2024-03-12 01:03:13 +00:00
|
|
|
data = settings_manager.get_icon_theme().lookup_icon(icon , size, 0)
|
2023-02-08 23:52:33 +00:00
|
|
|
|
|
|
|
if data:
|
|
|
|
icon_path = data.get_filename()
|
2024-03-12 01:03:13 +00:00
|
|
|
return GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, width = size, height = size)
|
2023-01-29 06:10:09 +00:00
|
|
|
|
2023-02-08 23:52:33 +00:00
|
|
|
raise IconException("No system icon found...")
|
|
|
|
except IconException:
|
2023-01-29 06:10:09 +00:00
|
|
|
...
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
2023-02-04 22:32:00 +00:00
|
|
|
def get_generic_icon(self):
|
2021-11-17 03:41:24 +00:00
|
|
|
return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICON)
|
2023-02-04 22:32:00 +00:00
|
|
|
|
|
|
|
def generate_hash_and_path(self, full_path):
|
2023-02-10 02:55:21 +00:00
|
|
|
img_hash = self.fast_hash(full_path)
|
|
|
|
hash_img_path = f"{self.ABS_THUMBS_PTH}/{img_hash}.jpg"
|
2023-02-04 22:32:00 +00:00
|
|
|
path_exists = True if isfile(hash_img_path) else False
|
|
|
|
|
2023-02-10 02:55:21 +00:00
|
|
|
return path_exists, img_hash, hash_img_path
|
2023-02-04 22:32:00 +00:00
|
|
|
|
|
|
|
|
2023-09-30 04:15:14 +00:00
|
|
|
def fast_hash(self, filename: str, hash_factory: callable = hashlib.md5, chunk_num_blocks: int = 128, i: int = 1) -> str:
|
2023-02-04 22:32:00 +00:00
|
|
|
h = hash_factory()
|
|
|
|
with open(filename,'rb') as f:
|
2023-09-30 04:15:14 +00:00
|
|
|
# NOTE: Jump to middle of file
|
2023-02-04 22:32:00 +00:00
|
|
|
f.seek(0, 2)
|
|
|
|
mid = int(f.tell() / 2)
|
|
|
|
f.seek(mid, 0)
|
|
|
|
|
2023-09-30 04:15:14 +00:00
|
|
|
while chunk := f.read(chunk_num_blocks * h.block_size):
|
2023-02-04 22:32:00 +00:00
|
|
|
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)
|
|
|
|
data = im.tobytes()
|
|
|
|
data = GLib.Bytes.new(data)
|
|
|
|
w, h = im.size
|
|
|
|
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB,
|
|
|
|
False, 8, w, h, w * 3)
|
|
|
|
|
2023-12-24 19:23:24 +00:00
|
|
|
return pixbuf.scale_simple(wxh[0], wxh[1], 2) # BILINEAR = 2
|