added initial gtk icon logiv
@ -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]
|
||||
|
@ -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()
|
||||
|
178
src/shellfm/windows/view/Icon.py
Normal file
@ -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)
|
@ -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
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
from .utils import *
|
||||
|
||||
from .Icon import Icon
|
||||
from .Path import Path
|
||||
from .View import View
|
||||
|
@ -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):
|
||||
|
@ -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"
|
||||
|
BIN
src/shellfm/windows/view/utils/icons/archive.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/shellfm/windows/view/utils/icons/audio.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/shellfm/windows/view/utils/icons/bin.png
Normal file
After Width: | Height: | Size: 858 B |
BIN
src/shellfm/windows/view/utils/icons/dir.png
Normal file
After Width: | Height: | Size: 850 B |
BIN
src/shellfm/windows/view/utils/icons/doc.png
Normal file
After Width: | Height: | Size: 702 B |
BIN
src/shellfm/windows/view/utils/icons/pdf.png
Normal file
After Width: | Height: | Size: 925 B |
BIN
src/shellfm/windows/view/utils/icons/presentation.png
Normal file
After Width: | Height: | Size: 882 B |
BIN
src/shellfm/windows/view/utils/icons/spreadsheet.png
Normal file
After Width: | Height: | Size: 707 B |
BIN
src/shellfm/windows/view/utils/icons/text.png
Normal file
After Width: | Height: | Size: 798 B |
BIN
src/shellfm/windows/view/utils/icons/trash.png
Normal file
After Width: | Height: | Size: 989 B |
BIN
src/shellfm/windows/view/utils/icons/video.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/shellfm/windows/view/utils/icons/web.png
Normal file
After Width: | Height: | Size: 1.8 KiB |