added initial gtk icon logiv
|
@ -11,12 +11,24 @@ class Window:
|
||||||
def create_view(self):
|
def create_view(self):
|
||||||
view = View()
|
view = View()
|
||||||
self.views.append(view)
|
self.views.append(view)
|
||||||
|
return view
|
||||||
|
|
||||||
def pop_view(self):
|
def pop_view(self):
|
||||||
self.views.pop()
|
self.views.pop()
|
||||||
|
|
||||||
def delete_view(self, index):
|
def delete_view(self, vid):
|
||||||
del self.views[index]
|
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]
|
return self.views[index]
|
||||||
|
|
|
@ -24,8 +24,7 @@ class WindowController:
|
||||||
def add_view_for_window(self, win_id):
|
def add_view_for_window(self, win_id):
|
||||||
for window in self.windows:
|
for window in self.windows:
|
||||||
if window.id == win_id:
|
if window.id == win_id:
|
||||||
window.create_view()
|
return window.create_view()
|
||||||
break
|
|
||||||
|
|
||||||
def pop_window(self):
|
def pop_window(self):
|
||||||
self.windows.pop()
|
self.windows.pop()
|
||||||
|
|
|
@ -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 import listdir
|
||||||
from os.path import isdir, isfile, join
|
from os.path import isdir, isfile, join
|
||||||
|
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
|
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from .utils import Settings, Launcher
|
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):
|
def __init__(self):
|
||||||
|
self.id = ""
|
||||||
self.files = []
|
self.files = []
|
||||||
self.dirs = []
|
self.dirs = []
|
||||||
self.vids = []
|
self.vids = []
|
||||||
self.images = []
|
self.images = []
|
||||||
self.desktop = []
|
self.desktop = []
|
||||||
self.ungrouped = []
|
self.ungrouped = []
|
||||||
|
self.id_length = 10
|
||||||
|
|
||||||
self.set_to_home()
|
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):
|
def load_directory(self):
|
||||||
path = self.get_path()
|
path = self.get_path()
|
||||||
|
@ -128,6 +142,11 @@ class View(Settings, Launcher, Path):
|
||||||
home = self.get_home() + "/"
|
home = self.get_home() + "/"
|
||||||
return path.replace(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):
|
def get_dot_dots(self):
|
||||||
return self.hashSet(['.', '..'])
|
return self.hashSet(['.', '..'])
|
||||||
|
|
||||||
|
@ -145,7 +164,7 @@ class View(Settings, Launcher, Path):
|
||||||
if not os.path.exists(hashImgPth) :
|
if not os.path.exists(hashImgPth) :
|
||||||
fullPath = join(current_directory, video[0])
|
fullPath = join(current_directory, video[0])
|
||||||
self.logger.debug(f"Hash Path: {hashImgPth}\nFile Path: {fullPath}")
|
self.logger.debug(f"Hash Path: {hashImgPth}\nFile Path: {fullPath}")
|
||||||
self.generateVideoThumbnail(fullPath, hashImgPth)
|
self.generate_video_thumbnail(fullPath, hashImgPth)
|
||||||
|
|
||||||
return videos_set
|
return videos_set
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from .utils import *
|
from .utils import *
|
||||||
|
|
||||||
|
from .Icon import Icon
|
||||||
from .Path import Path
|
from .Path import Path
|
||||||
from .View import View
|
from .View import View
|
||||||
|
|
|
@ -67,12 +67,50 @@ class Launcher:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def generateVideoThumbnail(self, fullPath, hashImgPth):
|
def generate_video_thumbnail(self, fullPath, hashImgPth):
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", "65%", "-s", "300", "-c", "jpg", "-i", fullPath, "-o", hashImgPth])
|
proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", "65%", "-s", "300", "-c", "jpg", "-i", fullPath, "-o", hashImgPth])
|
||||||
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(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):
|
def check_remux_space(self):
|
||||||
|
|
|
@ -11,15 +11,20 @@ from os import path
|
||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
logger = None
|
logger = None
|
||||||
|
GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1)
|
||||||
ABS_THUMBS_PTH = None # Used for thumbnail generation and is set by passing in
|
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
|
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
|
FFMPG_THUMBNLR = None # Used for thumbnail generator binary and is set by passing in
|
||||||
HIDE_HIDDEN_FILES = True
|
HIDE_HIDDEN_FILES = True
|
||||||
lock_folder = True
|
lock_folder = False
|
||||||
go_past_home = False
|
go_past_home = True
|
||||||
|
|
||||||
subpath = "/LazyShare" # modify 'home' folder path
|
iconContainerWH = [128, 128]
|
||||||
locked_folders = "Synced Backup::::venv::::flasks".split("::::")
|
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()
|
mplayer_options = "-quiet -really-quiet -xy 1600 -geometry 50%:50%".split()
|
||||||
music_app = "/opt/deadbeef/bin/deadbeef"
|
music_app = "/opt/deadbeef/bin/deadbeef"
|
||||||
media_app = "mpv"
|
media_app = "mpv"
|
||||||
|
|
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 858 B |
After Width: | Height: | Size: 850 B |
After Width: | Height: | Size: 702 B |
After Width: | Height: | Size: 925 B |
After Width: | Height: | Size: 882 B |
After Width: | Height: | Size: 707 B |
After Width: | Height: | Size: 798 B |
After Width: | Height: | Size: 989 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.8 KiB |