Updates and cleanup on thumbnailer logic

This commit is contained in:
itdominator 2023-01-28 22:34:21 -06:00
parent 8fc698f46d
commit d641c3dba8
5 changed files with 84 additions and 45 deletions

View File

@ -1,31 +1,27 @@
# Python Imports # Python Imports
import os, subprocess, threading, hashlib import hashlib
from os.path import isfile from os.path import isfile
# Lib imports # Lib imports
import gi import gi
gi.require_version('GdkPixbuf', '2.0') 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: try:
from PIL import Image as PImage from PIL import Image as PImage
except Exception as e: except Exception as e:
PImage = None PImage = None
# Application imports # Application imports
from .mixins.desktopiconmixin import DesktopIconMixin from .mixins.desktopiconmixin import DesktopIconMixin
from .mixins.videoiconmixin import VideoIconMixin 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): def create_icon(self, dir, file):
full_path = f"{dir}/{file}" full_path = f"{dir}/{file}"
return self.get_icon_image(dir, file, full_path) return self.get_icon_image(dir, file, full_path)
@ -34,10 +30,14 @@ class Icon(DesktopIconMixin, VideoIconMixin):
try: try:
thumbnl = None thumbnl = None
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) thumbnl = self.create_thumbnail(dir, file, 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
thumbnl = self.create_blender_thumbnail(dir, file, 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.parse_desktop_files(full_path)
@ -47,50 +47,58 @@ class Icon(DesktopIconMixin, VideoIconMixin):
return None return None
def create_thumbnail(self, dir, file, scrub_percent = "65%"): def create_blender_thumbnail(self, dir, file, full_path=None):
full_path = f"{dir}/{file}"
try: try:
file_hash = hashlib.sha256(str.encode(full_path)).hexdigest() file_hash = hashlib.sha256(str.encode(full_path)).hexdigest()
hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg" hash_img_path = f"{self.ABS_THUMBS_PTH}/{file_hash}.png"
if isfile(hash_img_pth) == False: if not isfile(hash_img_path):
self.generate_video_thumbnail(full_path, hash_img_pth, scrub_percent) self.generate_blender_thumbnail(full_path, hash_img_path)
thumbnl = self.create_scaled_image(hash_img_pth, self.video_icon_wh) return self.create_scaled_image(hash_img_path, 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
except Exception as e: except Exception as e:
print("Thumbnail generation issue:") print("Blender thumbnail generation issue:")
print( repr(e) ) 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: if not wxh:
wxh = self.video_icon_wh wxh = self.video_icon_wh
if path: if full_path:
try: try:
if path.lower().endswith(".gif"): if full_path.lower().endswith(".gif"):
return GdkPixbuf.PixbufAnimation.new_from_file(path) \ return GdkPixbuf.PixbufAnimation.new_from_file(full_path) \
.get_static_image() \ .get_static_image() \
.scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR) .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR)
elif path.lower().endswith(".webp") and PImage: elif full_path.lower().endswith(".webp") and PImage:
return self.image2pixbuf(path, wxh) 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: except Exception as e:
print("Image Scaling Issue:") print("Image Scaling Issue:")
print( repr(e) ) print( repr(e) )
return None return None
def image2pixbuf(self, path, wxh): def image2pixbuf(self, full_path, wxh):
"""Convert Pillow image to GdkPixbuf""" """Convert Pillow image to GdkPixbuf"""
im = PImage.open(path) im = PImage.open(full_path)
data = im.tobytes() data = im.tobytes()
data = GLib.Bytes.new(data) data = GLib.Bytes.new(data)
w, h = im.size w, h = im.size
@ -100,9 +108,9 @@ class Icon(DesktopIconMixin, VideoIconMixin):
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, path): def create_from_file(self, full_path):
try: try:
return GdkPixbuf.Pixbuf.new_from_file(path) return GdkPixbuf.Pixbuf.new_from_file(full_path)
except Exception as e: except Exception as e:
print("Image from file Issue:") print("Image from file Issue:")
print( repr(e) ) print( repr(e) )
@ -111,3 +119,16 @@ class Icon(DesktopIconMixin, VideoIconMixin):
def return_generic_icon(self): def return_generic_icon(self):
return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICON) 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

View File

@ -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))

View File

@ -1,22 +1,22 @@
# Python Imports # Python Imports
import subprocess import subprocess
# Gtk imports # Lib imports
# Application imports # Application imports
class VideoIconMixin: 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: 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() proc.wait()
except Exception as e: except Exception as e:
self.logger.debug(repr(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 proc = None
try: try:
# Stream duration # Stream duration
@ -44,7 +44,7 @@ class VideoIconMixin:
# Get frame roughly 35% through video # Get frame roughly 35% through video
grabTime = str( int( float( duration.split(".")[0] ) * 0.35) ) 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 = subprocess.Popen(command, stdout=subprocess.PIPE)
proc.wait() proc.wait()
except Exception as e: except Exception as e:

View File

@ -22,8 +22,9 @@ class Settings:
GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1) GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1)
DEFAULT_ICONS = f"{CONFIG_PATH}/icons" DEFAULT_ICONS = f"{CONFIG_PATH}/icons"
DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png" DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png"
FFMPG_THUMBNLR = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary FFMPG_THUMBNLR = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary
REMUX_FOLDER = f"{USER_HOME}/.remuxs" # Remuxed files folder 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"] ICON_DIRS = ["/usr/share/icons", f"{USER_HOME}/.icons" "/usr/share/pixmaps"]
BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails" # Used for thumbnail generation BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails" # Used for thumbnail generation
@ -53,7 +54,8 @@ class Settings:
subpath = config["base_of_home"] subpath = config["base_of_home"]
STEAM_CDN_URL = config["steam_cdn_url"] 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 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"] go_past_home = True if config["go_past_home"] == "" else config["go_past_home"]
lock_folder = True if config["lock_folder"] == "true" else False lock_folder = True if config["lock_folder"] == "true" else False

View File

@ -3,6 +3,7 @@
"base_of_home": "", "base_of_home": "",
"hide_hidden_files": "true", "hide_hidden_files": "true",
"thumbnailer_path": "ffmpegthumbnailer", "thumbnailer_path": "ffmpegthumbnailer",
"blender_thumbnailer_path": "",
"go_past_home": "true", "go_past_home": "true",
"lock_folder": "false", "lock_folder": "false",
"locked_folders": "venv::::flasks", "locked_folders": "venv::::flasks",
@ -23,7 +24,7 @@
"remux_folder_max_disk_usage": "8589934592" "remux_folder_max_disk_usage": "8589934592"
}, },
"filters": { "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"], "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"], "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"], "office": [".doc", ".docx", ".xls", ".xlsx", ".xlt", ".xltx", ".xlm", ".ppt", ".pptx", ".pps", ".ppsx", ".odt", ".rtf"],