diff --git a/src/shellfm/windows/Window.py b/src/shellfm/windows/Window.py index 5284e08..5445881 100644 --- a/src/shellfm/windows/Window.py +++ b/src/shellfm/windows/Window.py @@ -11,12 +11,24 @@ class Window: def create_view(self): view = View() self.views.append(view) + return view def pop_view(self): self.views.pop() - def delete_view(self, index): - del self.views[index] + def delete_view(self, vid): + i = -1 + for view in self.views: + i += 1 + if view.id == vid: + del self.views[i] + break - def get_view(self, index): + + def get_view_by_id(self, vid): + for view in self.views: + if view.id == vid: + return view + + def get_view_by_index(self, index): return self.views[index] diff --git a/src/shellfm/windows/WindowController.py b/src/shellfm/windows/WindowController.py index 587a271..b3b2ee5 100644 --- a/src/shellfm/windows/WindowController.py +++ b/src/shellfm/windows/WindowController.py @@ -24,8 +24,7 @@ class WindowController: def add_view_for_window(self, win_id): for window in self.windows: if window.id == win_id: - window.create_view() - break + return window.create_view() def pop_window(self): self.windows.pop() diff --git a/src/shellfm/windows/view/Icon.py b/src/shellfm/windows/view/Icon.py new file mode 100644 index 0000000..c82aba9 --- /dev/null +++ b/src/shellfm/windows/view/Icon.py @@ -0,0 +1,178 @@ +# Python Imports +import os, subprocess, hashlib, threading +from os.path import isdir, isfile, join + + +# Gtk imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') + +from gi.repository import Gtk +from gi.repository import Gio +from xdg.DesktopEntry import DesktopEntry + + +# Application imports + + + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs).start() + 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 createIcon(self, dir, file): + fullPath = dir + "/" + file + return self.getIconImage(file, fullPath) + + def createThumbnail(self, dir, file): + fullPath = dir + "/" + file + try: + fileHash = hashlib.sha256(str.encode(fullPath)).hexdigest() + hashImgPth = self.get_home() + "/.thumbnails/normal/" + fileHash + ".png" + + thumbnl = self.createScaledImage(hashImgPth, self.viIconWH) + if thumbnl == None: # If no icon whatsoever, return internal default + thumbnl = Gtk.Image.new_from_file(self.SCRIPT_PTH + "./utils/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") + + + def getIconImage(self, file, fullPath): + try: + thumbnl = None + + # Video icon + if file.lower().endswith(self.fvideos): + thumbnl = Gtk.Image.new_from_file(self.SCRIPT_PTH + "./utils/icons/video.png") + # Image Icon + elif file.lower().endswith(self.fimages): + thumbnl = self.createScaledImage(fullPath, self.viIconWH) + # .desktop file parsing + elif fullPath.lower().endswith( ('.desktop',) ): + thumbnl = self.parseDesktopFiles(fullPath) + # System icons + else: + thumbnl = self.getSystemThumbnail(fullPath, self.systemIconImageWH[0]) + + if thumbnl == None: # If no icon whatsoever, return internal default + thumbnl = Gtk.Image.new_from_file(self.INTERNAL_ICON_PTH) + + return thumbnl + except Exception as e: + print("Icon generation issue:") + print( repr(e) ) + return Gtk.Image.new_from_file(self.INTERNAL_ICON_PTH) + + def parseDesktopFiles(self, fullPath): + try: + xdgObj = DesktopEntry(fullPath) + icon = xdgObj.getIcon() + altIconPath = "" + + if "steam" in icon: + steamIconsDir = self.get_home() + "/.thumbnails/steam_icons/" + name = xdgObj.getName() + fileHash = hashlib.sha256(str.encode(name)).hexdigest() + + if isdir(steamIconsDir) == False: + os.mkdir(steamIconsDir) + + hashImgPth = steamIconsDir + fileHash + ".jpg" + if isfile(hashImgPth) == True: + # Use video sizes since headers are bigger + return self.createScaledImage(hashImgPth, self.viIconWH) + + execStr = xdgObj.getExec() + parts = execStr.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]) + proc.wait() + + # Use video thumbnail sizes since headers are bigger + return self.createScaledImage(hashImgPth, self.viIconWH) + elif os.path.exists(icon): + return self.createScaledImage(icon, self.systemIconImageWH) + else: + iconsDirs = ["/usr/share/pixmaps", "/usr/share/icons", self.get_home() + "/.icons" ,] + altIconPath = "" + + for iconsDir in iconsDirs: + altIconPath = self.traverseIconsFolder(iconsDir, icon) + if altIconPath is not "": + break + + return self.createScaledImage(altIconPath, self.systemIconImageWH) + except Exception as e: + print(".desktop icon generation issue:") + print( repr(e) ) + return None + + + def traverseIconsFolder(self, path, icon): + altIconPath = "" + + 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 + break + + return altIconPath + + + def getSystemThumbnail(self, filename, size): + try: + if os.path.exists(filename): + gioFile = Gio.File.new_for_path(filename) + info = gioFile.query_info('standard::icon' , 0, Gio.Cancellable()) + icon = info.get_icon().get_names()[0] + iconTheme = Gtk.IconTheme.get_default() + iconData = iconTheme.lookup_icon(icon , size , 0) + if iconData: + iconPath = iconData.get_filename() + return Gtk.Image.new_from_file(iconPath) # This seems to cause a lot of core dump issues... + else: + return None + else: + return None + except Exception as e: + print("system icon generation issue:") + print( repr(e) ) + return None + + + def createScaledImage(self, path, wxh): + 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) + except Exception as e: + print("Image Scaling Issue:") + print( repr(e) ) + return None + + def createFromFile(self, path): + try: + return Gtk.Image.new_from_file(path) + except Exception as e: + print("Image from file Issue:") + print( repr(e) ) + return None + + def returnGenericIcon(self): + return Gtk.Image.new_from_file(self.INTERNAL_ICON_PTH) diff --git a/src/shellfm/windows/view/View.py b/src/shellfm/windows/view/View.py index 72ffd61..913b7e0 100644 --- a/src/shellfm/windows/view/View.py +++ b/src/shellfm/windows/view/View.py @@ -4,24 +4,38 @@ import os from os import listdir from os.path import isdir, isfile, join +from random import randint + # Lib imports # Application imports from .utils import Settings, Launcher -from . import Path +from . import Path, Icon -class View(Settings, Launcher, Path): +class View(Settings, Launcher, Icon, Path): def __init__(self): + self.id = "" self.files = [] self.dirs = [] self.vids = [] self.images = [] self.desktop = [] self.ungrouped = [] + self.id_length = 10 self.set_to_home() + self.generate_id() + + + def random_with_N_digits(self, n): + range_start = 10**(n-1) + range_end = (10**n)-1 + return randint(range_start, range_end) + + def generate_id(self): + self.id = str(self.random_with_N_digits(self.id_length)) def load_directory(self): path = self.get_path() @@ -128,6 +142,11 @@ class View(Settings, Launcher, Path): home = self.get_home() + "/" return path.replace(home, "") + def get_end_of_path(self): + parts = self.get_current_directory().split("/") + size = len(parts) + return parts[size - 1] + def get_dot_dots(self): return self.hashSet(['.', '..']) @@ -145,7 +164,7 @@ class View(Settings, Launcher, Path): if not os.path.exists(hashImgPth) : fullPath = join(current_directory, video[0]) self.logger.debug(f"Hash Path: {hashImgPth}\nFile Path: {fullPath}") - self.generateVideoThumbnail(fullPath, hashImgPth) + self.generate_video_thumbnail(fullPath, hashImgPth) return videos_set diff --git a/src/shellfm/windows/view/__init__.py b/src/shellfm/windows/view/__init__.py index da63bd2..d4dc901 100644 --- a/src/shellfm/windows/view/__init__.py +++ b/src/shellfm/windows/view/__init__.py @@ -1,4 +1,5 @@ from .utils import * +from .Icon import Icon from .Path import Path from .View import View diff --git a/src/shellfm/windows/view/utils/Launcher.py b/src/shellfm/windows/view/utils/Launcher.py index 6e3dcac..f4403a6 100644 --- a/src/shellfm/windows/view/utils/Launcher.py +++ b/src/shellfm/windows/view/utils/Launcher.py @@ -67,12 +67,50 @@ class Launcher: return True - def generateVideoThumbnail(self, fullPath, hashImgPth): + 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): diff --git a/src/shellfm/windows/view/utils/Settings.py b/src/shellfm/windows/view/utils/Settings.py index 41a4663..f2dfc94 100644 --- a/src/shellfm/windows/view/utils/Settings.py +++ b/src/shellfm/windows/view/utils/Settings.py @@ -11,15 +11,20 @@ from os import path class Settings: logger = None + 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 = True - go_past_home = False + lock_folder = False + go_past_home = True - subpath = "/LazyShare" # modify 'home' folder path - locked_folders = "Synced Backup::::venv::::flasks".split("::::") + iconContainerWH = [128, 128] + systemIconImageWH = [56, 56] + viIconWH = [256, 128] + + subpath = "" # 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" media_app = "mpv" diff --git a/src/shellfm/windows/view/utils/icons/archive.png b/src/shellfm/windows/view/utils/icons/archive.png new file mode 100644 index 0000000..7943e4e Binary files /dev/null and b/src/shellfm/windows/view/utils/icons/archive.png differ diff --git a/src/shellfm/windows/view/utils/icons/audio.png b/src/shellfm/windows/view/utils/icons/audio.png new file mode 100644 index 0000000..c010134 Binary files /dev/null and b/src/shellfm/windows/view/utils/icons/audio.png differ diff --git a/src/shellfm/windows/view/utils/icons/bin.png b/src/shellfm/windows/view/utils/icons/bin.png new file mode 100644 index 0000000..d6954e3 Binary files /dev/null and b/src/shellfm/windows/view/utils/icons/bin.png differ diff --git a/src/shellfm/windows/view/utils/icons/dir.png b/src/shellfm/windows/view/utils/icons/dir.png new file mode 100644 index 0000000..a9b5e9f Binary files /dev/null and b/src/shellfm/windows/view/utils/icons/dir.png differ diff --git a/src/shellfm/windows/view/utils/icons/doc.png b/src/shellfm/windows/view/utils/icons/doc.png new file mode 100644 index 0000000..f838826 Binary files /dev/null and b/src/shellfm/windows/view/utils/icons/doc.png differ diff --git a/src/shellfm/windows/view/utils/icons/pdf.png b/src/shellfm/windows/view/utils/icons/pdf.png new file mode 100644 index 0000000..9f40122 Binary files /dev/null and b/src/shellfm/windows/view/utils/icons/pdf.png differ diff --git a/src/shellfm/windows/view/utils/icons/presentation.png b/src/shellfm/windows/view/utils/icons/presentation.png new file mode 100644 index 0000000..3a339af Binary files /dev/null and b/src/shellfm/windows/view/utils/icons/presentation.png differ diff --git a/src/shellfm/windows/view/utils/icons/spreadsheet.png b/src/shellfm/windows/view/utils/icons/spreadsheet.png new file mode 100644 index 0000000..710efa6 Binary files /dev/null and b/src/shellfm/windows/view/utils/icons/spreadsheet.png differ diff --git a/src/shellfm/windows/view/utils/icons/text.png b/src/shellfm/windows/view/utils/icons/text.png new file mode 100644 index 0000000..2546fcd Binary files /dev/null and b/src/shellfm/windows/view/utils/icons/text.png differ diff --git a/src/shellfm/windows/view/utils/icons/trash.png b/src/shellfm/windows/view/utils/icons/trash.png new file mode 100644 index 0000000..c6514b9 Binary files /dev/null and b/src/shellfm/windows/view/utils/icons/trash.png differ diff --git a/src/shellfm/windows/view/utils/icons/video.png b/src/shellfm/windows/view/utils/icons/video.png new file mode 100644 index 0000000..55afa98 Binary files /dev/null and b/src/shellfm/windows/view/utils/icons/video.png differ diff --git a/src/shellfm/windows/view/utils/icons/web.png b/src/shellfm/windows/view/utils/icons/web.png new file mode 100644 index 0000000..17017ce Binary files /dev/null and b/src/shellfm/windows/view/utils/icons/web.png differ