diff --git a/src/shellfm/windows/view/Icon.py b/src/shellfm/windows/view/Icon.py index c82aba9..b6cf648 100644 --- a/src/shellfm/windows/view/Icon.py +++ b/src/shellfm/windows/view/Icon.py @@ -1,7 +1,6 @@ # Python Imports -import os, subprocess, hashlib, threading -from os.path import isdir, isfile, join - +import os, subprocess, threading, hashlib +from os.path import isfile, join # Gtk imports import gi @@ -24,118 +23,111 @@ def threaded(fn): return wrapper class Icon: - def __init__(self): - self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + "/" - self.INTERNAL_ICON_PTH = self.SCRIPT_PTH + "./utils/icons/text.png" + def create_icon(self, dir, file): + full_path = dir + "/" + file + return self.get_icon_image(file, full_path) - - def createIcon(self, dir, file): - fullPath = dir + "/" + file - return self.getIconImage(file, fullPath) - - def createThumbnail(self, dir, file): - fullPath = dir + "/" + file + def create_thumbnail(self, dir, file): + full_path = dir + "/" + file try: - fileHash = hashlib.sha256(str.encode(fullPath)).hexdigest() - hashImgPth = self.get_home() + "/.thumbnails/normal/" + fileHash + ".png" + file_hash = hashlib.sha256(str.encode(full_path)).hexdigest() + hash_img_pth = ABS_THUMBS_PTH + "/" + file_hash + ".jpg" + if isfile(hash_img_pth) == False: + self.generate_video_thumbnail(full_path, hash_img_pth) - thumbnl = self.createScaledImage(hashImgPth, self.viIconWH) + thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH) if thumbnl == None: # If no icon whatsoever, return internal default - thumbnl = Gtk.Image.new_from_file(self.SCRIPT_PTH + "./utils/icons/video.png") + thumbnl = Gtk.Image.new_from_file(self.DEFAULT_ICONS + "/video.png") return thumbnl except Exception as e: print("Thumbnail generation issue:") print( repr(e) ) - return Gtk.Image.new_from_file(self.SCRIPT_PTH + "./utils/icons/video.png") + return Gtk.Image.new_from_file(self.DEFAULT_ICONS + "/video.png") - def getIconImage(self, file, fullPath): + def get_icon_image(self, file, full_path): try: thumbnl = None # Video icon if file.lower().endswith(self.fvideos): - thumbnl = Gtk.Image.new_from_file(self.SCRIPT_PTH + "./utils/icons/video.png") + thumbnl = Gtk.Image.new_from_file(self.DEFAULT_ICONS + "/video.png") # Image Icon elif file.lower().endswith(self.fimages): - thumbnl = self.createScaledImage(fullPath, self.viIconWH) + thumbnl = self.create_scaled_image(full_path, self.VIDEO_ICON_WH) # .desktop file parsing - elif fullPath.lower().endswith( ('.desktop',) ): - thumbnl = self.parseDesktopFiles(fullPath) + elif full_path.lower().endswith( ('.desktop',) ): + thumbnl = self.parse_desktop_files(full_path) # System icons else: - thumbnl = self.getSystemThumbnail(fullPath, self.systemIconImageWH[0]) + thumbnl = self.get_system_thumbnail(full_path, self.SYS_ICON_WH[0]) if thumbnl == None: # If no icon whatsoever, return internal default - thumbnl = Gtk.Image.new_from_file(self.INTERNAL_ICON_PTH) + thumbnl = Gtk.Image.new_from_file(self.DEFAULT_ICON) return thumbnl except Exception as e: print("Icon generation issue:") print( repr(e) ) - return Gtk.Image.new_from_file(self.INTERNAL_ICON_PTH) + return Gtk.Image.new_from_file(self.DEFAULT_ICON) - def parseDesktopFiles(self, fullPath): + def parse_desktop_files(self, full_path): try: - xdgObj = DesktopEntry(fullPath) + xdgObj = DesktopEntry(full_path) icon = xdgObj.getIcon() - altIconPath = "" + alt_icon_path = "" if "steam" in icon: - steamIconsDir = self.get_home() + "/.thumbnails/steam_icons/" - name = xdgObj.getName() - fileHash = hashlib.sha256(str.encode(name)).hexdigest() + name = xdgObj.getName() + file_hash = hashlib.sha256(str.encode(name)).hexdigest() + hash_img_pth = self.STEAM_ICONS_PTH + "/" + file_hash + ".jpg" - if isdir(steamIconsDir) == False: - os.mkdir(steamIconsDir) - - hashImgPth = steamIconsDir + fileHash + ".jpg" - if isfile(hashImgPth) == True: + if isfile(hash_img_pth) == True: # Use video sizes since headers are bigger - return self.createScaledImage(hashImgPth, self.viIconWH) + return self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH) - execStr = xdgObj.getExec() - parts = execStr.split("steam://rungameid/") + exec_str = xdgObj.getExec() + parts = exec_str.split("steam://rungameid/") id = parts[len(parts) - 1] - imageLink = "https://steamcdn-a.akamaihd.net/steam/apps/" + id + "/header.jpg" - proc = subprocess.Popen(["wget", "-O", hashImgPth, imageLink]) + imageLink = self.STEAM_BASE_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.createScaledImage(hashImgPth, self.viIconWH) + return self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH) elif os.path.exists(icon): - return self.createScaledImage(icon, self.systemIconImageWH) + return self.create_scaled_image(icon, self.SYS_ICON_WH) else: - iconsDirs = ["/usr/share/pixmaps", "/usr/share/icons", self.get_home() + "/.icons" ,] - altIconPath = "" + alt_icon_path = "" - for iconsDir in iconsDirs: - altIconPath = self.traverseIconsFolder(iconsDir, icon) - if altIconPath is not "": + for dir in self.ICON_DIRS: + alt_icon_path = self.traverse_icons_folder(dir, icon) + if alt_icon_path is not "": break - return self.createScaledImage(altIconPath, self.systemIconImageWH) + return self.create_scaled_image(alt_icon_path, self.SYS_ICON_WH) except Exception as e: + print(self.DEFAULT_ICON) print(".desktop icon generation issue:") print( repr(e) ) return None - def traverseIconsFolder(self, path, icon): - altIconPath = "" + def traverse_icons_folder(self, path, icon): + alt_icon_path = "" for (dirpath, dirnames, filenames) in os.walk(path): for file in filenames: appNM = "application-x-" + icon if icon in file or appNM in file: - altIconPath = dirpath + "/" + file + alt_icon_path = dirpath + "/" + file break - return altIconPath + return alt_icon_path - def getSystemThumbnail(self, filename, size): + def get_system_thumbnail(self, filename, size): try: if os.path.exists(filename): gioFile = Gio.File.new_for_path(filename) @@ -156,17 +148,63 @@ class Icon: return None - def createScaledImage(self, path, wxh): + def generate_video_thumbnail(self, full_path, hash_img_pth): try: - pixbuf = Gtk.Image.new_from_file(path).get_pixbuf() - scaledPixBuf = pixbuf.scale_simple(wxh[0], wxh[1], 2) # 2 = BILINEAR and is best by default - return Gtk.Image.new_from_pixbuf(scaledPixBuf) + proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", "65%", "-s", "300", "-c", "jpg", "-i", full_path, "-o", hash_img_pth]) + proc.wait() + except Exception as e: + self.logger.debug(repr(e)) + self.ffprobe_generate_video_thumbnail(full_path, hash_img_pth) + + + def ffprobe_generate_video_thumbnail(self, full_path, hash_img_pth): + proc = None + try: + # Stream duration + command = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=duration", "-of", "default=noprint_wrappers=1:nokey=1", full_path] + data = subprocess.run(command, stdout=subprocess.PIPE) + duration = data.stdout.decode('utf-8') + + # Format (container) duration + if "N/A" in duration: + command = ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", full_path] + data = subprocess.run(command , stdout=subprocess.PIPE) + duration = data.stdout.decode('utf-8') + + # Stream duration type: image2 + if "N/A" in duration: + command = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-f", "image2", "-show_entries", "stream=duration", "-of", "default=noprint_wrappers=1:nokey=1", full_path] + data = subprocess.run(command, stdout=subprocess.PIPE) + duration = data.stdout.decode('utf-8') + + # Format (container) duration type: image2 + if "N/A" in duration: + command = ["ffprobe", "-v", "error", "-f", "image2", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", full_path] + data = subprocess.run(command , stdout=subprocess.PIPE) + duration = data.stdout.decode('utf-8') + + # 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] + proc = subprocess.Popen(command, stdout=subprocess.PIPE) + proc.wait() + except Exception as e: + print("Video thumbnail generation issue in thread:") + print( repr(e) ) + self.logger.debug(repr(e)) + + + def create_scaled_image(self, path, wxh): + try: + pixbuf = Gtk.Image.new_from_file(path).get_pixbuf() + scaled_pixbuf = pixbuf.scale_simple(wxh[0], wxh[1], 2) # 2 = BILINEAR and is best by default + return Gtk.Image.new_from_pixbuf(scaled_pixbuf) except Exception as e: print("Image Scaling Issue:") print( repr(e) ) return None - def createFromFile(self, path): + def create_from_file(self, path): try: return Gtk.Image.new_from_file(path) except Exception as e: @@ -174,5 +212,5 @@ class Icon: print( repr(e) ) return None - def returnGenericIcon(self): - return Gtk.Image.new_from_file(self.INTERNAL_ICON_PTH) + def return_generic_icon(self): + return Gtk.Image.new_from_file(self.DEFAULT_ICON) diff --git a/src/shellfm/windows/view/View.py b/src/shellfm/windows/view/View.py index 913b7e0..37552b4 100644 --- a/src/shellfm/windows/view/View.py +++ b/src/shellfm/windows/view/View.py @@ -17,6 +17,7 @@ from . import Path, Icon class View(Settings, Launcher, Icon, Path): def __init__(self): self.id = "" + self. logger = None self.files = [] self.dirs = [] self.vids = [] @@ -157,16 +158,7 @@ class View(Settings, Launcher, Icon, Path): return self.hashSet(self.dirs) def get_videos(self): - videos_set = self.hashSet(self.vids) - current_directory = self.get_current_directory() - for video in videos_set: - hashImgPth = join(self.ABS_THUMBS_PTH, video[1]) + ".jpg" - if not os.path.exists(hashImgPth) : - fullPath = join(current_directory, video[0]) - self.logger.debug(f"Hash Path: {hashImgPth}\nFile Path: {fullPath}") - self.generate_video_thumbnail(fullPath, hashImgPth) - - return videos_set + return self.hashSet(self.vids) def get_images(self): return self.hashSet(self.images) diff --git a/src/shellfm/windows/view/utils/Launcher.py b/src/shellfm/windows/view/utils/Launcher.py index f4403a6..ba50e47 100644 --- a/src/shellfm/windows/view/utils/Launcher.py +++ b/src/shellfm/windows/view/utils/Launcher.py @@ -9,7 +9,7 @@ import os, subprocess, threading class Launcher: - def openFilelocally(self, file): + def open_file_locally(self, file): lowerName = file.lower() command = [] @@ -38,7 +38,7 @@ class Launcher: subprocess.Popen(command, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True) - def remuxVideo(self, hash, file): + def remux_video(self, hash, file): remux_vid_pth = self.REMUX_FOLDER + "/" + hash + ".mp4" self.logger.debug(remux_vid_pth) @@ -66,53 +66,6 @@ class Launcher: return True - - def generate_video_thumbnail(self, fullPath, hashImgPth): - try: - proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", "65%", "-s", "300", "-c", "jpg", "-i", fullPath, "-o", hashImgPth]) - proc.wait() - except Exception as e: - self.logger.debug(repr(e)) - self.ffprobe_generate_video_thumbnail(fullPath, hashImgPth) - - - def generate_video_thumbnail(self, fullPath, hashImgPth): - proc = None - try: - # Stream duration - command = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=duration", "-of", "default=noprint_wrappers=1:nokey=1", fullPath] - data = subprocess.run(command, stdout=subprocess.PIPE) - duration = data.stdout.decode('utf-8') - - # Format (container) duration - if "N/A" in duration: - command = ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", fullPath] - data = subprocess.run(command , stdout=subprocess.PIPE) - duration = data.stdout.decode('utf-8') - - # Stream duration type: image2 - if "N/A" in duration: - command = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-f", "image2", "-show_entries", "stream=duration", "-of", "default=noprint_wrappers=1:nokey=1", fullPath] - data = subprocess.run(command, stdout=subprocess.PIPE) - duration = data.stdout.decode('utf-8') - - # Format (container) duration type: image2 - if "N/A" in duration: - command = ["ffprobe", "-v", "error", "-f", "image2", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", fullPath] - data = subprocess.run(command , stdout=subprocess.PIPE) - duration = data.stdout.decode('utf-8') - - # Get frame roughly 35% through video - grabTime = str( int( float( duration.split(".")[0] ) * 0.35) ) - command = ["ffmpeg", "-ss", grabTime, "-an", "-i", fullPath, "-s", "320x180", "-vframes", "1", hashImgPth] - proc = subprocess.Popen(command, stdout=subprocess.PIPE) - proc.wait() - except Exception as e: - print("Video thumbnail generation issue in thread:") - print( repr(e) ) - self.logger.debug(repr(e)) - - def check_remux_space(self): limit = self.remux_folder_max_disk_usage try: @@ -121,7 +74,7 @@ class Launcher: self.logger.debug(e) return - usage = self.getRemuxFolderUsage(self.REMUX_FOLDER) + usage = self.get_remux_folder_usage(self.REMUX_FOLDER) if usage > limit: files = os.listdir(self.REMUX_FOLDER) for file in files: @@ -129,7 +82,7 @@ class Launcher: os.unlink(fp) - def getRemuxFolderUsage(self, start_path = "."): + def get_remux_folder_usage(self, start_path = "."): total_size = 0 for dirpath, dirnames, filenames in os.walk(start_path): for f in filenames: diff --git a/src/shellfm/windows/view/utils/Settings.py b/src/shellfm/windows/view/utils/Settings.py index f2dfc94..12cae7d 100644 --- a/src/shellfm/windows/view/utils/Settings.py +++ b/src/shellfm/windows/view/utils/Settings.py @@ -1,7 +1,7 @@ # System import +import os from os import path - # Lib imports @@ -11,19 +11,33 @@ from os import path class Settings: logger = None - GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1) + lock_folder = False + go_past_home = True + + GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1) ABS_THUMBS_PTH = None # Used for thumbnail generation and is set by passing in REMUX_FOLDER = None # Used for Remuxed files and is set by passing in FFMPG_THUMBNLR = None # Used for thumbnail generator binary and is set by passing in HIDE_HIDDEN_FILES = True - lock_folder = False - go_past_home = True - iconContainerWH = [128, 128] - systemIconImageWH = [56, 56] - viIconWH = [256, 128] + USER_HOME = path.expanduser('~') + CONFIG_PATH = USER_HOME + "/.config/pyfm" + DEFAULT_ICONS = CONFIG_PATH + "/icons" + DEFAULT_ICON = DEFAULT_ICONS + "/text.png" + FFMPG_THUMBNLR = CONFIG_PATH + "/ffmpegthumbnailer" # Thumbnail generator binary + REMUX_FOLDER = USER_HOME + "/.remuxs" # Remuxed files folder - subpath = "" # modify 'home' folder path + STEAM_BASE_URL = "https://steamcdn-a.akamaihd.net/steam/apps/" + ICON_DIRS = ["/usr/share/pixmaps", "/usr/share/icons", USER_HOME + "/.icons" ,] + BASE_THUMBS_PTH = USER_HOME + "/.thumbnails" # Used for thumbnail generation + ABS_THUMBS_PTH = BASE_THUMBS_PTH + "/normal" # Used for thumbnail generation + STEAM_ICONS_PTH = BASE_THUMBS_PTH + "/steam_icons" + CONTAINER_ICON_WH = [128, 128] + VIDEO_ICON_WH = [256, 128] + SYS_ICON_WH = [56, 56] + + subpath = "/LazyShare/Movies-TV-Music/TV/Anime/Barakamon" # modify 'home' folder path + # subpath = "/Desktop" # modify 'home' folder path locked_folders = "venv::::flasks".split("::::") mplayer_options = "-quiet -really-quiet -xy 1600 -geometry 50%:50%".split() music_app = "/opt/deadbeef/bin/deadbeef" @@ -35,10 +49,24 @@ class Settings: file_manager_app = "spacefm" remux_folder_max_disk_usage = "8589934592" - + # Filters fvideos = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', '.mpeg', '.mp4', '.webm') foffice = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx', '.xlm', '.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf') fimages = ('.png', '.jpg', '.jpeg', '.gif', '.ico', '.tga') ftext = ('.txt', '.text', '.sh', '.cfg', '.conf') fmusic = ('.psf', '.mp3', '.ogg', '.flac', '.m4a') fpdf = ('.pdf') + + + # Dire structure check + if path.isdir(REMUX_FOLDER) == False: + os.mkdir(REMUX_FOLDER) + + if path.isdir(BASE_THUMBS_PTH) == False: + os.mkdir(BASE_THUMBS_PTH) + + if path.isdir(ABS_THUMBS_PTH) == False: + os.mkdir(ABS_THUMBS_PTH) + + if path.isdir(STEAM_ICONS_PTH) == False: + os.mkdir(STEAM_ICONS_PTH) diff --git a/src/user_config/pyfm/ffmpegthumbnailer b/src/user_config/pyfm/ffmpegthumbnailer new file mode 100755 index 0000000..0b6e6e2 Binary files /dev/null and b/src/user_config/pyfm/ffmpegthumbnailer differ diff --git a/src/shellfm/windows/view/utils/icons/archive.png b/src/user_config/pyfm/icons/archive.png similarity index 100% rename from src/shellfm/windows/view/utils/icons/archive.png rename to src/user_config/pyfm/icons/archive.png diff --git a/src/shellfm/windows/view/utils/icons/audio.png b/src/user_config/pyfm/icons/audio.png similarity index 100% rename from src/shellfm/windows/view/utils/icons/audio.png rename to src/user_config/pyfm/icons/audio.png diff --git a/src/shellfm/windows/view/utils/icons/bin.png b/src/user_config/pyfm/icons/bin.png similarity index 100% rename from src/shellfm/windows/view/utils/icons/bin.png rename to src/user_config/pyfm/icons/bin.png diff --git a/src/shellfm/windows/view/utils/icons/dir.png b/src/user_config/pyfm/icons/dir.png similarity index 100% rename from src/shellfm/windows/view/utils/icons/dir.png rename to src/user_config/pyfm/icons/dir.png diff --git a/src/shellfm/windows/view/utils/icons/doc.png b/src/user_config/pyfm/icons/doc.png similarity index 100% rename from src/shellfm/windows/view/utils/icons/doc.png rename to src/user_config/pyfm/icons/doc.png diff --git a/src/shellfm/windows/view/utils/icons/pdf.png b/src/user_config/pyfm/icons/pdf.png similarity index 100% rename from src/shellfm/windows/view/utils/icons/pdf.png rename to src/user_config/pyfm/icons/pdf.png diff --git a/src/shellfm/windows/view/utils/icons/presentation.png b/src/user_config/pyfm/icons/presentation.png similarity index 100% rename from src/shellfm/windows/view/utils/icons/presentation.png rename to src/user_config/pyfm/icons/presentation.png diff --git a/src/shellfm/windows/view/utils/icons/spreadsheet.png b/src/user_config/pyfm/icons/spreadsheet.png similarity index 100% rename from src/shellfm/windows/view/utils/icons/spreadsheet.png rename to src/user_config/pyfm/icons/spreadsheet.png diff --git a/src/shellfm/windows/view/utils/icons/text.png b/src/user_config/pyfm/icons/text.png similarity index 100% rename from src/shellfm/windows/view/utils/icons/text.png rename to src/user_config/pyfm/icons/text.png diff --git a/src/shellfm/windows/view/utils/icons/trash.png b/src/user_config/pyfm/icons/trash.png similarity index 100% rename from src/shellfm/windows/view/utils/icons/trash.png rename to src/user_config/pyfm/icons/trash.png diff --git a/src/shellfm/windows/view/utils/icons/video.png b/src/user_config/pyfm/icons/video.png similarity index 100% rename from src/shellfm/windows/view/utils/icons/video.png rename to src/user_config/pyfm/icons/video.png diff --git a/src/shellfm/windows/view/utils/icons/web.png b/src/user_config/pyfm/icons/web.png similarity index 100% rename from src/shellfm/windows/view/utils/icons/web.png rename to src/user_config/pyfm/icons/web.png