Compare commits

...

26 Commits

Author SHA1 Message Date
d0ac564cee fix --sort-dir not respected 2024-02-07 11:22:59 +01:00
53c3a55e07 change exit code 2024-02-07 11:16:53 +01:00
1ae88d2182 fix typo 2023-12-16 01:16:37 +01:00
e0ce0d1b1d improve error handling 2023-12-16 00:54:49 +01:00
e88cf6b98d fix version 2023-12-16 00:34:24 +01:00
5775502dc1 improve dependecy info 2023-12-16 00:33:41 +01:00
c91b3a3f93 configman always return keys 2023-12-16 00:25:21 +01:00
ea80175dc7 bumb version 2023-12-16 00:25:17 +01:00
5f9d528f3e dont exit on user error 2023-12-16 00:25:10 +01:00
39d7471dc8 move line to avoid merge conflicts 2023-12-15 23:59:43 +01:00
f5344f606e refactor with configmanager 2023-12-15 23:50:34 +01:00
da43eae362 move shared variables 2023-12-15 23:50:24 +01:00
fdf3e0fb50 dont use o in example 2023-12-01 21:34:12 +01:00
5f200626b8 merge xdg-open functionality 2023-12-01 21:30:23 +01:00
1fd5e820f4 use argparse and allow sort into other dir 2023-12-01 20:55:57 +01:00
matthias@arch
429a7be16a fix changelog 1.2 2023-11-01 01:27:38 +01:00
matthias@arch
75bc8efbb8 bump version 2023-11-01 01:26:13 +01:00
Matthias Quintern
6be0e503fb
Center image 2023-11-01 01:21:21 +01:00
matthias@arch
6b48fbdec1 add example image 2023-11-01 01:17:09 +01:00
matthias@arch
6795766160 fix typo 2023-11-01 01:06:24 +01:00
matthias@arch
bb049d5e92 fix head 2023-10-23 00:47:18 +02:00
Matthias@Dell
86ab3a0596 add xdg-open 2023-10-23 00:37:53 +02:00
Matthias@Dell
59159e4ad2 rm cache 2023-10-21 13:33:08 +02:00
Matthias@Dell
8f9603976e ueberzug++ support 2023-10-21 13:32:55 +02:00
Matthias@Dell
4d86c53fe4 add ueberzugpp support 2023-10-21 13:32:29 +02:00
Matthias@Dell
5ee8743ed2 add 2023-10-21 13:32:11 +02:00
10 changed files with 342 additions and 171 deletions

0
LICENSE Normal file → Executable file
View File

39
README.md Normal file → Executable file
View File

@ -1,6 +1,10 @@
# imgsort - Image Sorter # imgsort - Image Sorter
This is a python program that lets you easily sort images from one directory into other directories. This is a python program for Linux that lets you easily sort images from one directory into other directories.
For example, you could go through your phone's camera folder and sort the images into different folders, like *Family*, *Landscapes*, *Friends* etc. It lets you go through a folder of images and simply move them using a single key press, which you define at program startup.
This is very useful when you want to sort your phone's camera folder or messenger media folders.
For example, you could quickly go through your WhatsApp media (after copying it to your pc) and sort the images into different directories like *Selfies*, *Landscapes*, *Friends* etc.
<img src="imgsort-example.jpg" width="70%" style="margin-left: auto; margin-right: auto;" />
## Usage ## Usage
1. Navigate to the folder containing the images and run "imgsort". 1. Navigate to the folder containing the images and run "imgsort".
@ -12,24 +16,41 @@ imgsort
For example, you could use: For example, you could use:
- `f` = `~/Pictures/Family` - `f` = `~/Pictures/Family`
- `v` = `~/Pictures/Vacation_2019` - `v` = `~/Pictures/Vacation_2019`
- `o` = `~/Pictures/Other` - `O` = `~/Pictures/Other`
Note that `s`, `u` and `q` are reserved for *skip*, *undo* and *quit*, but you can use `S`, `U` and `Q` instead. Note that `s`, `u`, `o` and `q` are reserved for *skip*, *undo*, *open* and *quit*, but you can use `S`, `U` and `Q` instead.
3. Save the config if you might want to use it again. The config file will be stored in `~/.config/imgsort`. 3. Save the config if you might want to use it again. The config file will be stored in `$XDG_CONFIG_DIR` or `~/.config/imgsort`.
4. Enjoy the slideshow! 4. Enjoy the slideshow!
## Installation ## Installation
Clone this repository and install it using python-pip. Clone this repository and install it using python-pip.
pip should also install https://github.com/seebye/ueberzug, which lets you view images in a terminal. This project depends on ueberzug to display the images in the terminal.
The original ueberzug is no longer maintained, but there is [a continuation](https://github.com/ueber-devel/ueberzug/) as well as a [new C++ alternative](https://github.com/jstkdng/ueberzugpp) available.
You need to manually install one of them and then choose the corresponding `imgsort` branch.
I would recommend the `ueberzugpp` as it also works on Wayland.
For the version supporting the original **ueberzug**:
```shell ```shell
cd ~/Downloads
git clone https://github.com/MatthiasQuintern/imgsort.git git clone https://github.com/MatthiasQuintern/imgsort.git
cd imgsort cd imgsort
python3 -m pip install . python3 -m pip install .
``` ```
You can also install it system-wide using `sudo python3 -m pip install.` For the version supporting the new **ueberzug++**:
```shell
git clone --branch ueberzugpp https://github.com/MatthiasQuintern/imgsort.git
cd imgsort
python3 -m pip install .
```
## Changelog ## Changelog
### 1.2
#### 1.2.1
- Refactored configuration management
#### 1.2.0
- Support ueberzugpp
- Added option to open file with `xdg-open`
- Use pyproject.toml for installation
### 1.1 ### 1.1
- Terminal does not break anymore when program exits - Terminal does not break anymore when program exits
- Todo-Images are now sorted by filename - Todo-Images are now sorted by filename
@ -37,5 +58,5 @@ You can also install it system-wide using `sudo python3 -m pip install.`
### 1.0 ### 1.0
- Initial Release - Initial Release
## Importand Notice: ## Important Notice:
This software comes with no warranty! This software comes with no warranty!

BIN
imgsort-example.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -1,101 +1,169 @@
from os import path, getcwd, listdir, mkdir, makedirs, rename import enum
from os import EX_CANTCREAT, path, getcwd, listdir, rename
from sys import exit
import re import re
def read_config(filepath): from .globals import version, settings_map
if not path.isfile(filepath): return False from .globals import warning, error, user_error, info, create_dir
file = open(filepath, 'r') class ConfigManager():
keys = {} """Manage config files for imgsort"""
for line in file.readlines(): def __init__(self, config_dir: str):
line = line.replace("\n", "") """TODO: to be defined.
match = re.match(r". = /?([a-z-A-ZöÖäÄüÜ0-9/: _-]+/)*[a-zA-ZöÖäÄüÜ0-9/: _-]+/?", line)
if match:
key, value = line.split(" = ")
keys[key] = value
return keys
def write_config(filepath, keys): @param config_path TODO
file = open(filepath, 'w')
file.write("Config written by imgsort.\n")
for k, v in keys.items() :
file.write(f"{k} = {v}\n")
def create_config(): """
keys = {} self._config_dir = config_dir
print( if not path.isdir(self._config_dir):
""" if path.exists(self._config_dir):
error(f"Config '{self._config_dir}' exists but is not a directory.")
info(f"Creating config dir '{self._config_dir}' since it does not exist")
try:
create_dir(self._config_dir)
except PermissionError as e:
error(f"Could not create '{self._config_dir}': PermissionError: {e}")
except Exception as e:
error(f"Could not create '{self._config_dir}': {e}")
self._configs = [ e for e in listdir(self._config_dir) if path.isfile(path.normpath(self._config_dir + "/" + e)) and e.endswith(".conf") ]
self._configs.sort()
def present_config_selection(self, root_directory="."):
"""
Returns to path to an existing config or False if a new config should be created
"""
# get configs
if len(self._configs) == 0:
info(f"No config valid file found in '{self._config_dir}'")
return self.create_config(root_directory)
print(" 0: create new configuration")
for i, c in enumerate(self._configs):
print(f"{i+1:2}: {c}")
while True:
choice = input("Please select a config: ")
try:
choice = int(choice)
except ValueError:
user_error(f"Invalid choice: '{choice}'. Choice must be a number between 0 and {len(self._configs)}")
continue
if not 0 <= choice <= len(self._configs):
user_error(f"Invalid choice: '{choice}'. Choice must be a number between 0 and {len(self._configs)}")
continue
if choice == 0:
return self.create_config(root_directory)
return self.load_config(self._configs[choice-1], root_directory)
def _make_name(self, config_name: str):
return path.normpath(self._config_dir + "/" + config_name.removesuffix(".conf") + ".conf")
def write_config(self, config_name: str, keys: dict[str,str]):
file = open(path.normpath(self._config_dir + "/" + config_name), 'w')
file.write(f"# Config written by imgsort {version}\n")
for k, v in keys.items() :
file.write(f"{k} = {v}\n")
def load_config(self, config_name: str, root_directory="."):
"""
@param root_directory Make all relative paths relative to this one
"""
if type(config_name) != str:
error(f"load config got wrong type: '{type(config_name)}'")
config_file = self._make_name(config_name)
if not path.isfile(config_file):
error(f"File '{config_file}' does not exist")
try:
file = open(config_file, 'r')
except Exception as e:
error(f"Could not open file '{config_file}': {e}")
keys: dict[str, str] = {}
for i, line in enumerate(file.readlines()):
line = line.replace("\n", "")
match = re.fullmatch(r". = [^*?<>|]+", line)
if match:
key, value = line.split(" = ")
keys[key] = value
elif not line[0] == "#":
warning(f"In config file '{config_file}': Invalid line ({i+1}): '{line}'")
self.validate_config(keys, root_directory)
return keys
def create_config(self, root_directory="."):
keys: dict[str, str] = {}
print(
f"""
=================================================================================================== ===================================================================================================
Creating a new config Creating a new config
You can now map keys to directories.
The key must be one single letter, a single digit number or some other keyboard key like .-#+&/ ...
The key can not be one of '{' '.join(settings_map.keys())}'.
The directory must be a valid path to a directory, but is does not have to exist.
You can use an absolute path (starting with '/', not '~') or a relative path (from here).
=================================================================================================== ===================================================================================================
Please enter at least one key and one directory.
The key must be one single letter, a single digit number or some other keyboard key like .-#+&/ ...
The key can not be 'q', 's' or 'u'.
The directory must be a valid path to a directory, but is does not have to exist.
You can use an absolute path (starting with '/') or a relative path (from here).
Do not use '~'!
""" """
) )
done = False while True:
while not done: # ask for key
key = input("Please enter a key or 'q' when you are done: ")
if (len(key) != 1):
user_error(f"Invalid key: '{key}' has a length other than 1")
continue
# if done
elif key == 'q':
if len(keys) == 0:
warning(f"No keys were mapped - exiting")
exit(0)
save = input(f"\nDo you want to save the config to {self._config_dir}/<name>.conf?\nType a name to save the config or type 'q' to not save the config: ")
if save != 'q':
self.write_config(save + ".conf", keys)
break
elif key in settings_map.keys():
user_error(f"Invalid key: '{key}' is reserved and can not be mapped")
continue
# ask for key # ask for directory
key = input("Please enter a key or 'q' when you are done: ") directory = input("Please enter the directory/path: ")
if (len(key) != 1): # match = re.match(r"/?([a-z-A-ZöÖäÄüÜ0-9/: _\-]+/)*[a-z-A-ZöÖäÄüÜ0-9/: _\-]+/?", directory)
print("Invalid key: " + key) INVALID_PATH_CHARS = r":*?<>|"
continue if any(c in INVALID_PATH_CHARS for c in directory):
# if done user_error(f"Invalid directory path: '{directory}' contains at least one of '{INVALID_PATH_CHARS}'")
elif key == 'q': continue
save = input("\nDo you want to save the config to ~/.config/imgsort/<name>.conf?\nType a name to save the config or type 'q' to not save the config: ") keys[key] = directory
if not save == 'q': print(f"Added: {key}: '{directory}'\n")
config_path = path.expanduser("~") + "/.config/imgsort"
if not path.isdir(config_path):
mkdir(config_path)
write_config(path.normpath(config_path + "/" + save + ".conf"), keys) self.validate_config(keys, root_directory)
done = True
continue
# ask for directory return keys
directory = input("Please enter the directory path: ")
match = re.match(r"/?([a-z-A-ZöÖäÄüÜ0-9/: _\-]+/)*[a-z-A-ZöÖäÄüÜ0-9/: _\-]+/?", directory)
if not match:
print("Invalid directory path: " + directory)
continue
keys[key] = directory
print(f"Added: ({key}: {directory})\n")
return keys
def select_config(): def validate_config(self, keys, root_directory):
""" """
Returns to path to an existing config or False if a new config should be created Create the directories that dont exist.
""" """
# get configs missing = []
config_path = path.expanduser("~") + "/.config/imgsort" for k, d in keys.items():
if not path.isdir(config_path) or len(listdir(config_path)) == 0: d = path.expanduser(d)
return False if not path.isabs(d):
d = path.normpath(root_directory + "/" + d)
keys[k] = d
if not path.isdir(d):
missing.append(d)
if len(missing) == 0: return
print(f"The following directories do not exist:")
for d in missing: print(f"\t{d}")
decision = input(f"Create the ({len(missing)}) missing directories? y/*: ")
if (decision == "y"):
for d in missing:
create_dir(d)
else:
error("Exiting - can not use non-existing directories.")
configs = {}
i = 1
for file in listdir(config_path):
if not re.match(r"[a-zA-ZöÖäÄüÜ0-9_\- ]+\.conf", file): continue
configs[str(i)] = file
i += 1
# print configs
print("0: Create new config")
for n, conf in configs.items():
print(f"{n}: {conf}")
choice = input("Please select a config: ")
if choice == "0": return False
elif choice in configs:
return path.normpath(config_path + "/" + configs[choice])
else:
print("Invalid choice - creating new config")
return False

32
imgsort/globals.py Normal file
View File

@ -0,0 +1,32 @@
version = "1.2.1"
fullversion = f"{version}"
settings_map = {
"q": "quit",
"s": "skip",
"u": "undo",
"o": "open"
}
from os import makedirs
def error(*args, exitcode=2, **kwargs):
print("\033[31mError: \033[0m", *args, **kwargs)
exit(exitcode)
def user_error(*args, **kwargs):
print("\033[31mError: \033[0m", *args, **kwargs)
def warning(*args, **kwargs):
print("\033[33mWarning: \033[0m", *args, **kwargs)
def info(*args, **kwargs):
print("\033[34mInfo: \033[0m", *args, **kwargs)
def create_dir(d):
try:
makedirs(d)
except PermissionError as e:
error(f"Could not create '{d}': PermissionError: {e}")
except Exception as e:
error(f"Could not create '{d}': {e}")

View File

@ -1,19 +1,23 @@
#!/bin/python3 #!/bin/python3
import argparse
import curses as c import curses as c
import ueberzug.lib.v0 as uz import os
from os import path, getcwd, listdir, makedirs, rename
import subprocess
from imgsort.configs import read_config, write_config, select_config, create_config if __name__ == "__main__": # make relative imports work as described here: https://peps.python.org/pep-0366/#proposed-change
if __package__ is None:
__package__ = "imgsort"
import sys
filepath = path.realpath(path.abspath(__file__))
sys.path.insert(0, path.dirname(path.dirname(filepath)))
from os import path, getcwd, listdir, mkdir, makedirs, rename from .ueberzug import UeberzugLayer
from sys import argv from .configs import ConfigManager
from .globals import version, fullversion, settings_map
settings = { from .globals import warning, error, user_error, info, create_dir
"q": "quit",
"s": "skip",
"u": "undo",
}
# Size settings # Size settings
FOOTER_LEFT = 0 FOOTER_LEFT = 0
@ -27,7 +31,7 @@ CURSOR_Y = 2
KEYS_BEGIN = 5 KEYS_BEGIN = 5
class Sorter: class Sorter:
def __init__(self, wdir, canvas, config): def __init__(self, wdir, config):
self.wd = wdir self.wd = wdir
self.images = [] # old paths self.images = [] # old paths
@ -37,9 +41,7 @@ class Sorter:
self.keys = config self.keys = config
self.settings = settings self.settings = settings_map
self.validate_dirs()
# info about last action # info about last action
self.last_dir = "" self.last_dir = ""
@ -56,32 +58,14 @@ class Sorter:
c.echo() c.echo()
# ueberzug # ueberzug
self.canvas = canvas self._ueberzug = UeberzugLayer(pid_file="/tmp/ueberzug-imgsort.pid")
self._img_x = SIDEBAR_WIDTH + 1
self._img_y = 2
self._img_width = self.win_x - SIDEBAR_WIDTH - 1
self._img_height = self.win_y - FOOTER_HEIGHT - 2
self._img_identifier = "imgsort_preview"
self.placement = self.canvas.create_placement("p1", x=0, y=0, path="")
self.placement.visibility = uz.Visibility.VISIBLE
self.placement.scaler = uz.ScalerOption.FIT_CONTAIN.value
self.placement.x = SIDEBAR_WIDTH + 1
self.placement.y = 2
self.placement.width = self.win_x - SIDEBAR_WIDTH - 1
self.placement.height = self.win_y - FOOTER_HEIGHT - 2
# version
self.version = "Image Sorter 1.1"
def validate_dirs(self):
"""
Create the directories that dont exist.
"""
for d in self.keys.values():
if not path.isdir(d):
print(f"Directory '{d}' does not exist.")
decision = input(f"Create directory '{path.abspath(d)}'? y/n: ")
if (decision == "y"):
makedirs(d)
else:
print("Exiting - can not use non-existing directory.")
exit(1)
def get_images(self): def get_images(self):
@ -101,9 +85,9 @@ class Sorter:
# print(self.images) # print(self.images)
def display_image(self): def display_image(self):
with self.canvas.lazy_drawing: # issue ueberzug command AFTER with-statement self._ueberzug.display_image(self.image, x=self._img_x, y=self._img_y, max_width=self._img_width, max_height=self._img_height, identifier=self._img_identifier)
self.placement.path = self.image self.window.addnstr(0, SIDEBAR_WIDTH + 1, self.image, self.win_x - SIDEBAR_WIDTH - 1)
self.window.addnstr(0, SIDEBAR_WIDTH + 1, self.placement.path, self.win_x - SIDEBAR_WIDTH - 1)
def sort(self): def sort(self):
""" """
@ -117,7 +101,7 @@ class Sorter:
self.pressed_key = self.window.getkey() # wait until user presses something self.pressed_key = self.window.getkey() # wait until user presses something
# check for quit, skip or undo # check for quit, skip, undo or open
if self.pressed_key in self.settings: if self.pressed_key in self.settings:
if self.settings[self.pressed_key] == "quit": if self.settings[self.pressed_key] == "quit":
self.quit(f"Key '{self.pressed_key}' pressed. Canceling image sorting") self.quit(f"Key '{self.pressed_key}' pressed. Canceling image sorting")
@ -125,7 +109,7 @@ class Sorter:
self.image_iter += 1 self.image_iter += 1
self.message = "Skipped image" self.message = "Skipped image"
continue continue
elif settings[self.pressed_key] == "undo": elif settings_map[self.pressed_key] == "undo":
if self.image_iter > 0: if self.image_iter > 0:
self.image_iter -= 1 # using last image self.image_iter -= 1 # using last image
rename(self.images_new[self.image_iter], self.images[self.image_iter]) rename(self.images_new[self.image_iter], self.images[self.image_iter])
@ -135,6 +119,13 @@ class Sorter:
else: else:
self.message = "Nothing to undo!" self.message = "Nothing to undo!"
continue continue
elif settings_map[self.pressed_key] == "open":
try:
subprocess.run(['xdg-open', self.image], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
self.message = "Opening with xdg-open"
except Exception as e:
print(f"open: Error: {e}")
continue
# move to folder # move to folder
elif self.pressed_key in self.keys: elif self.pressed_key in self.keys:
@ -143,7 +134,7 @@ class Sorter:
self.images_new[self.image_iter] = new_filepath self.images_new[self.image_iter] = new_filepath
self.message = f"Moved image to {self.keys[self.pressed_key]}" self.message = f"Moved image to {self.keys[self.pressed_key]}"
else: else:
self.message = f"ERROR: Failed to move '{self.image}' to '{keys[self.pressed_key]}'." self.message = f"ERROR: Failed to move '{self.image}' to '{self.keys[self.pressed_key]}'."
self.image_iter += 1 self.image_iter += 1
self.quit("All done!") self.quit("All done!")
@ -153,14 +144,16 @@ class Sorter:
Draw lines and text Draw lines and text
""" """
self.window.erase() self.window.erase()
self.win_y, self.win_x = self.window.getmaxyx()
# lines # lines
self.window.hline(self.win_y - FOOTER_HEIGHT, FOOTER_LEFT, '=', self.win_x) self.window.hline(self.win_y - FOOTER_HEIGHT, FOOTER_LEFT, '=', self.win_x)
self.window.vline(0, SIDEBAR_WIDTH, '|', self.win_y - FOOTER_HEIGHT + 1) self.window.vline(0, SIDEBAR_WIDTH, '|', self.win_y - FOOTER_HEIGHT + 1)
# version # version
x = self.win_x - len(self.version) - 1 version_str = f"imgsort {version}"
self.window.addstr(self.win_y - 1, x, self.version) x = self.win_x - len(version_str) - 1
self.window.addstr(self.win_y - 1, x, version_str)
# wd # wd
wdstring = f"Sorting {self.wd} - {len(self.images)} files - {len(self.images) - self.image_iter} remaining." wdstring = f"Sorting {self.wd} - {len(self.images)} files - {len(self.images) - self.image_iter} remaining."
@ -202,6 +195,8 @@ class Sorter:
for k, v in self.keys.items(): for k, v in self.keys.items():
if i >= self.win_y - KEYS_BEGIN - FOOTER_HEIGHT: # dont write into footer if i >= self.win_y - KEYS_BEGIN - FOOTER_HEIGHT: # dont write into footer
break break
# show only last part
v = v.split("/")[-1]
if k == self.pressed_key: if k == self.pressed_key:
self.window.addnstr(KEYS_BEGIN + i, 0, f" {k}: {v}", SIDEBAR_WIDTH, c.A_STANDOUT) self.window.addnstr(KEYS_BEGIN + i, 0, f" {k}: {v}", SIDEBAR_WIDTH, c.A_STANDOUT)
else: else:
@ -222,44 +217,50 @@ class Sorter:
return new_path return new_path
def quit(self, message = ""): def quit(self, message = ""):
print(message)
print(f"Quitting imgsort {version}")
exit(0)
def __del__(self):
self.window.clear() self.window.clear()
self.window.refresh() self.window.refresh()
c.endwin() c.endwin()
print(message)
print("Quitting " + self.version)
exit(0)
def main(): def main():
# set working directory # set working directory
print(""" print(f"""
=================================================================================================== ===================================================================================================
Image Sorter Image Sorter {fullversion}
=================================================================================================== ===================================================================================================
""") """)
if len(argv) > 1: config_dir = os.path.join(os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), "imgsort")
wd = path.abspath(argv[1]) # check if environment variables are set and use them if they are
if 'IMGSORT_CONFIG_DIR' in os.environ: config_dir = os.environ['IMGSORT_CONFIG_DIR']
parser = argparse.ArgumentParser("imgsort")
parser.add_argument("-c", "--config", action="store", help="name of the config file in ($IMGSORT_CONFIG_DIR > $XDG_CONFIG_HOME/imgsort > ~/.config/imgsort)", default=None)
parser.add_argument("-i", "--sort-dir", action="store", help="directory in which the subdirectories from a config are created")
args = parser.parse_args()
wd = getcwd();
if args.sort_dir:
args.sort_dir = path.abspath(args.sort_dir)
else: else:
wd = getcwd(); args.sort_dir = getcwd()
config_name = select_config() confman = ConfigManager(config_dir)
if type(config_name) == str:
config = read_config(config_name) # configuration
if type(args.config) == str:
config = confman.load_config(args.config, args.sort_dir)
else: else:
config = create_config() config = confman.present_config_selection(args.sort_dir)
if not config: sorter = Sorter(wd, config)
print("Error reading the config:") sorter.get_images()
print(" Config Name:", config_name) sorter.sort()
print(" Config:", config)
exit(1)
with uz.Canvas() as canvas:
sorter = Sorter(wd, canvas, config)
sorter.get_images()
sorter.sort()
if __name__ == "__main__": if __name__ == "__main__":

49
imgsort/ueberzug.py Normal file
View File

@ -0,0 +1,49 @@
import subprocess
from os import path
from .globals import error
class UeberzugLayer():
"""Wrapper for Ueberzug++"""
def __init__(self, pid_file = "/tmp/ueberzug-py.pid", socket="/tmp/ueberzugpp-%pid%.socket", no_opencv=True):
self._socket = None
self._pid_file = pid_file
self._pid = None
ret = subprocess.run(["ueberzug", "layer", "--pid-file", pid_file, "--no-stdin", "--no-opencv" if no_opencv else ""], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if not ret.returncode == 0:
error(f"UeberzugLayer.init: ueberzug layer exited with {ret.returncode}")
if not path.isfile(pid_file):
error(f"UeberzugLayer.init: can not find ueberzug pid file at '{pid_file}'")
with open(pid_file, "r") as file:
try:
self._pid = int(file.read())
except ValueError as e:
raise Exception(f"Invalid content of pid file {pid_file}: {e}")
self._socket = socket.replace("%pid%", str(self._pid))
# if not path.exists(self._socket):
# raise Exception(f"Ueberzug socket not found: {self._socket}")
def display_image(self, image, x=0, y=0, max_width=0, max_height=0, identifier="Image"):
ret = subprocess.run(["ueberzug", "cmd", "-s", self._socket, "-a", "add", "-i", identifier, "-f", image, "-x", str(x), "-y", str(y), "--max-width", str(max_width), "--max-height", str(max_height)])
if not ret.returncode == 0:
self._socket = None
error(f"UeberzugLayer.display_image: ueberzug layer exited with {ret.returncode}")
def remove_image(self, identifier="Image"):
ret = subprocess.run(["ueberzug", "cmd", "-s", self._socket, "-a", "remove", "-i", identifier])
if not ret.returncode == 0:
self._socket = None
error(f"UeberzugLayer.remove_image: ueberzug layer exited with {ret.returncode}")
def __del__(self):
from os import remove
try:
remove(self._pid_file)
except:
pass
if self._socket is not None:
import subprocess # might be unloaded
ret = subprocess.run(["ueberzug", "cmd", "-s", self._socket, "-a", "exit"])
if not ret.returncode == 0:
error(f"UeberzugLayer.__del__: ueberzug layer exited with {ret.returncode}")

View File

@ -3,7 +3,7 @@ requires = ["setuptools"]
[project] [project]
name = "imgsort" name = "imgsort"
version = "1.2.0" version = "1.2.1"
description = "A program that lets you easily sort images into different folders." description = "A program that lets you easily sort images into different folders."
requires-python = ">=3.10" requires-python = ">=3.10"
readme = "README.md" readme = "README.md"