From 3ab6b837f21fa0ae2f1bd6b5e1e016c97b61a7a6 Mon Sep 17 00:00:00 2001 From: JustAnyone Date: Mon, 29 Jun 2026 15:42:17 +0300 Subject: [PATCH] feat: add support for opening images in geeqie --- .gitignore | 2 + random_file_picker/cli.py | 141 +++++++--------------------------- random_file_picker/core.py | 123 +++++++++++++++++++++++++++++ random_file_picker/openers.py | 40 ++++++++++ 4 files changed, 192 insertions(+), 114 deletions(-) create mode 100644 random_file_picker/core.py create mode 100644 random_file_picker/openers.py diff --git a/.gitignore b/.gitignore index 481a6e3..c91b0fa 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ !random_file_picker/ !random_file_picker/__init__.py !random_file_picker/cli.py +!random_file_picker/core.py +!random_file_picker/openers.py diff --git a/random_file_picker/cli.py b/random_file_picker/cli.py index 4c5c824..c4fbc23 100755 --- a/random_file_picker/cli.py +++ b/random_file_picker/cli.py @@ -7,30 +7,16 @@ Here's an example usage: ./rand.py -fl 5 -d /path/to/directory --descend --blacklist "*.png" "*.mp4" """ -import io -import os -import random -import requests -import subprocess import argparse +import os +import pathlib +import random import sys -import tty import termios -import fnmatch +import tty -MEME_FOLDER = os.environ.get("RANDOM_FILE_PICKER_MEME_FOLDER", None) -WEBHOOK_URL = os.environ.get("RANDOM_FILE_PICKER_WEBHOOK_URL", None) - -ALLOWED_EXTENSIONS = [ - ".jpg", - ".jpeg", - ".webp", - ".png", - ".gif", - ".mp4", - ".mov", - ".webm", -] +from random_file_picker.core import collect_files, move_file_to_meme_folder, upload_file +from random_file_picker.openers import open_file_with_geeqie, open_path_in_dolphin, open_path_with_default_app IMG_EXTENSIONS = [ ".jpg", @@ -40,20 +26,6 @@ IMG_EXTENSIONS = [ ".gif", ] - -def send_file(file_path: str): - if WEBHOOK_URL is None: - print("Error: RANDOM_FILE_PICKER_WEBHOOK_URL environment variable is not set.") - return - - filename = os.path.basename(file_path) - with open(file_path, "rb") as image_file: - image_data = image_file.read() - files = {'file': (filename, io.BytesIO(image_data))} - response = requests.post(WEBHOOK_URL, files=files, data={}) - response.raise_for_status() # Raise HTTPError for bad responses - print(f"\nSuccessfully uploaded {filename}") - def getch() -> str: fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) @@ -64,25 +36,12 @@ def getch() -> str: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) return ch - -def open_with_gwenview(maybe_process: subprocess.Popen[bytes] | None, file_path: str) -> subprocess.Popen[bytes]: - if maybe_process is not None: - maybe_process.terminate() - maybe_process.wait() - - process = subprocess.Popen(["gwenview", file_path]) - return process - -def open_in_folder(file_path: str): - subprocess.Popen(["dolphin", "--select", file_path]) - #subprocess.run(["dolphin", "--select", file_path]) - def main(): parser = argparse.ArgumentParser(description="Random File Sender") parser.add_argument('-fl', '--file-limit', type=int, default=10, help='Maximum file size in MB') parser.add_argument('-d', '--directory', type=str, default='.', help='Directory to search for files') parser.add_argument('--descend', action='store_true', help='Whether to descend into subdirectories') - #parser.add_argument('--direct', action='store_true', help='Whether to open files directly without xdg-open') + parser.add_argument('--use-geeqie', action='store_true', help='Whether to use Geeqie as the image viewer') parser._positionals.title = 'Positional arguments' parser._optionals.title = 'Optional arguments' @@ -94,65 +53,44 @@ def main(): args = parser.parse_args() file_limit: int = args.file_limit - #open_directly: bool = args.direct directory: str = args.directory descend: bool = args.descend blacklist_patterns: list[str] = args.blacklist if args.blacklist else [] whitelist_patterns: list[str] = args.whitelist if args.whitelist else [] + use_geeqie: bool = args.use_geeqie print(f"File size limit set to {file_limit} MB") - #print(f"Open files directly: {open_directly}") - if not os.path.exists(directory) or not os.path.isdir(directory): + + search_path = pathlib.Path(directory) + if not search_path.exists() or not search_path.is_dir(): print(f"Directory '{directory}' does not exist or is not a directory.") return - - search_path = os.path.abspath(directory) - - # Collect files from the current directory - usable_files: list[str] = [] - for directory, _, files in os.walk(search_path): - if directory != search_path and not descend: - continue - - for f in files: - file_path = os.path.join(directory, f) - - # Check against whitelist patterns - if whitelist_patterns and not any(fnmatch.fnmatch(file_path, pattern) for pattern in whitelist_patterns): - continue - - # Check against blacklist patterns - if any(fnmatch.fnmatch(file_path, pattern) for pattern in blacklist_patterns): - continue - - if not any(f.lower().endswith(ext) for ext in ALLOWED_EXTENSIONS): - continue - - stat_res = os.stat(file_path) - if stat_res.st_size < 1024 * 1024 * file_limit: - usable_files.append(file_path) + usable_files = collect_files( + search_path=search_path, + search_subdirectories=descend, + file_size_limit=file_limit, + blacklist_patterns=blacklist_patterns, + whitelist_patterns=whitelist_patterns, + ) if not usable_files: print("No usable files found.") return - + pickFile = True - gwenview_process = None while pickFile: file = random.choice(usable_files) print(f"Random file: {file}") - - if file.lower().endswith(tuple(IMG_EXTENSIONS)): - gwenview_process = open_with_gwenview(gwenview_process, file) + if use_geeqie and str(file).lower().endswith(tuple(IMG_EXTENSIONS)): + open_file_with_geeqie(file) else: - subprocess.run(["xdg-open", file]) + open_path_with_default_app(file) selectionTime = True while selectionTime: print("Options: [u]pload, [n]ext, [d]elete, [f]older, [q]uit") choice = getch().lower() - # Skip escape sequences (arrow keys) if choice.encode("utf-8") == b'\x1b': @@ -164,7 +102,8 @@ def main(): if choice == "u": print("\nUploading...") - send_file(file) + upload_file(file) + print(f"\nSuccessfully uploaded {file}") print("Upload complete.") #selectionTime = False elif choice == "d": @@ -177,40 +116,14 @@ def main(): selectionTime = False usable_files.remove(file) elif choice == "f": - open_in_folder(file) + open_path_in_dolphin(file) elif choice == "q": print("\nQuitting...") pickFile = False break elif choice == "m": print("\nMoving to meme folder...") - - if MEME_FOLDER is None: - print("Error: RANDOM_FILE_PICKER_MEME_FOLDER environment variable is not set.") - continue - - # First, make sure we're not in the meme folder already - if os.path.abspath(os.path.dirname(file)) == os.path.abspath(MEME_FOLDER): - print("File is already in the meme folder.") - continue - - # Also, ensure the meme folder exists - if not os.path.exists(MEME_FOLDER): - print(f"Meme folder '{MEME_FOLDER}' does not exist.") - continue - - base_name = os.path.basename(file) - new_path = os.path.join(MEME_FOLDER, base_name) - - # Avoid overwriting existing files - if os.path.exists(new_path): - name, ext = os.path.splitext(base_name) - counter = 1 - while os.path.exists(new_path): - new_path = os.path.join(MEME_FOLDER, f"{name}_{counter}{ext}") - counter += 1 - - os.rename(file, new_path) + move_file_to_meme_folder(file) usable_files.remove(file) selectionTime = False else: @@ -218,4 +131,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/random_file_picker/core.py b/random_file_picker/core.py new file mode 100644 index 0000000..d1243ca --- /dev/null +++ b/random_file_picker/core.py @@ -0,0 +1,123 @@ +import fnmatch +import io +import os +import pathlib + +import requests + +WEBHOOK_URL = os.environ.get("RANDOM_FILE_PICKER_WEBHOOK_URL", None) +MEME_FOLDER = os.environ.get("RANDOM_FILE_PICKER_MEME_FOLDER", None) + +SUPPORTED_EXTENSIONS = [ + ".jpg", ".jpeg", + ".webp", + ".png", + ".gif", + ".mp4", + ".mov", + ".webm", +] + +def collect_files( + *, + search_path: pathlib.Path, + search_subdirectories: bool, + file_size_limit: int, + blacklist_patterns: list[str] | None = None, + whitelist_patterns: list[str] | None = None, +) -> list[pathlib.Path]: + """ + Collect files from the specified search_path, applying filters based on file size, blacklist patterns, and whitelist patterns. + + :param search_path: The directory path to search for files. + :param search_subdirectories: If True, search in subdirectories as well. + :param file_size_limit: The maximum file size (in MB) for files to be included in the results. + :param blacklist_patterns: A list of patterns to exclude files from the results. If None, no files will be excluded based on patterns. + :param whitelist_patterns: A list of patterns to include files in the results. If None, all files will be included regardless of patterns. + :return: A list of file paths that meet the specified criteria. + """ + collected_files: list[pathlib.Path] = [] + + # Set default values for blacklist and whitelist patterns if they are None + blacklist_patterns = blacklist_patterns or [] + whitelist_patterns = whitelist_patterns or [] + + search_path = search_path.resolve() + for directory, _, files in search_path.walk(): + if directory != search_path and not search_subdirectories: + continue + + for f in files: + file_path = directory / f + + # Check against whitelist patterns + if whitelist_patterns and not any(fnmatch.fnmatch(str(file_path), pattern) for pattern in whitelist_patterns): + continue + + # Check against blacklist patterns + if any(fnmatch.fnmatch(str(file_path), pattern) for pattern in blacklist_patterns): + continue + + # Make sure the file has a supported extension + if not any(f.lower().endswith(ext) for ext in SUPPORTED_EXTENSIONS): + continue + + stat_res = file_path.stat() + if stat_res.st_size < 1024 * 1024 * file_size_limit: + collected_files.append(file_path) + return collected_files + + +def upload_file(file_path: pathlib.Path) -> None: + """ + Upload the specified file to a Discord webhook. + + :param file_path: The path of the file to be uploaded. + """ + if WEBHOOK_URL is None: + print("Error: RANDOM_FILE_PICKER_WEBHOOK_URL environment variable is not set.") + return + + filename = file_path.name + with open(file_path, "rb") as image_file: + image_data = image_file.read() + files = {"file": (filename, io.BytesIO(image_data))} + response = requests.post(WEBHOOK_URL, files=files, data={}) + response.raise_for_status() # Raise HTTPError for bad responses + +def move_file_to_meme_folder(file_path: pathlib.Path) -> None: + """ + Move the specified file to the meme folder defined by the RANDOM_FILE_PICKER_MEME_FOLDER environment variable. + + :param file_path: The path of the file to be moved. + """ + if MEME_FOLDER is None: + print("Error: RANDOM_FILE_PICKER_MEME_FOLDER environment variable is not set.") + return + meme_dir_path = pathlib.Path(MEME_FOLDER) + + # Make sure the meme folder exists + if not meme_dir_path.exists(): + print(f"Error: Meme folder '{meme_dir_path}' does not exist.") + return + + # Make sure the file being moved is not already in the meme folder + if file_path.parent.resolve() == meme_dir_path.resolve(): + print("File is already in the meme folder.") + return + + # Formulate the destination path and avoid overwriting existing files + base_name = file_path.name + destination_path = meme_dir_path / base_name + if destination_path.exists(): + name, ext = destination_path.stem, destination_path.suffix + counter = 1 + while destination_path.exists(): + destination_path = meme_dir_path / f"{name}_{counter}{ext}" + counter += 1 + + ok = file_path.rename(destination_path) + if not ok: + print(f"Error moving {file_path} to {destination_path}") + else: + print(f"Moved {file_path} to {destination_path}") diff --git a/random_file_picker/openers.py b/random_file_picker/openers.py new file mode 100644 index 0000000..40d9228 --- /dev/null +++ b/random_file_picker/openers.py @@ -0,0 +1,40 @@ +import os +import pathlib +import subprocess + + +def open_path_with_default_app(file_path: pathlib.Path): + """ + Open the specified file path with the default application associated with its file type. + + :param file_path: The path of the file to be opened. + """ + subprocess.run(["/usr/bin/xdg-open", str(file_path)]) + +def open_file_with_geeqie(file_path: pathlib.Path): + """ + Open the specified file path with the Geeqie image viewer. + + :param file_path: The path of the file to be opened. + """ + print("Running subprocess to open file with Geeqie:", file_path) + my_env = os.environ.copy() + my_env["GQ_DISABLE_CLUTTER"] = "y" + subprocess.Popen( + [ + "/usr/bin/geeqie", +# "-t", + # "--without-tools", + "--file", + str(file_path), + ], + env=my_env, + ) + +def open_path_in_dolphin(file_path: pathlib.Path): + """ + Open the specified file path in the Dolphin file manager and select the file. + + :param file_path: The path of the file to be opened in Dolphin. + """ + subprocess.Popen(["/usr/bin/dolphin", "--select", str(file_path)])