Rearrange for tidier structure (#2212)

This commit is contained in:
clsty
2025-10-16 07:19:55 +08:00
parent 13065d7e5a
commit 8b493e091d
529 changed files with 165 additions and 138 deletions
@@ -0,0 +1,128 @@
#!/usr/bin/env bash
# Generate thumbnails for files using ImageMagick, following Freedesktop spec
# Usage:
# ./generate-thumbnails-magick.sh --file <path>
# ./generate-thumbnails-magick.sh --directory <path>
set -e
# Thumbnail sizes mapping
get_thumbnail_size() {
case "$1" in
normal) echo 128 ;;
large) echo 256 ;;
x-large) echo 512 ;;
xx-large) echo 1024 ;;
*) echo 128 ;;
esac
}
usage() {
echo "Usage: $0 --file <path> | --directory <path>"
exit 1
}
md5() {
# Calculate md5 hash of the file's absolute path
echo -n "$1" | md5sum | awk '{print $1}'
}
urlencode() {
# Percent-encode a string for use in a URI, but do not encode slashes
local str="$1"
local encoded=""
local c
for ((i=0; i<${#str}; i++)); do
c="${str:$i:1}"
case "$c" in
[a-zA-Z0-9.~_-]|/) encoded+="$c" ;;
*) printf -v hex '%%%02X' "'${c}'"; encoded+="$hex" ;;
esac
done
echo "$encoded"
}
generate_thumbnail() {
local src="$1"
local abs_path
abs_path="$(realpath "$src")"
# Skip files with multiple frames (GIFs, videos, etc.)
case "${abs_path,,}" in
*.gif|*.mp4|*.webm|*.mkv|*.avi|*.mov)
return
;;
esac
local encoded_path
encoded_path="$(urlencode "$abs_path")"
local uri
uri="file://$encoded_path"
local hash
hash="$(md5 "$uri")"
local out="$CACHE_DIR/$hash.png"
mkdir -p "$CACHE_DIR"
if [ -f "$out" ]; then
return
fi
magick "$abs_path" -resize "${THUMBNAIL_SIZE}x${THUMBNAIL_SIZE}" "$out"
}
# Parse arguments
SIZE_NAME="normal"
MODE=""
TARGET=""
while [[ $# -gt 0 ]]; do
case "$1" in
--file|-f)
MODE="file"
TARGET="$2"
shift 2
;;
--directory|-d)
MODE="dir"
TARGET="$2"
shift 2
;;
--size|-s)
SIZE_NAME="$2"
shift 2
;;
*)
usage
;;
esac
# Only one mode allowed
[[ -n "$MODE" ]] && break
done
THUMBNAIL_SIZE="$(get_thumbnail_size "$SIZE_NAME")"
CACHE_DIR="$HOME/.cache/thumbnails/$SIZE_NAME"
if [ -z "$MODE" ] || [ -z "$TARGET" ]; then
usage
fi
case "$MODE" in
file)
if [ ! -f "$TARGET" ]; then
echo "File not found: $TARGET"
exit 2
fi
generate_thumbnail "$TARGET"
;;
dir)
if [ ! -d "$TARGET" ]; then
echo "Directory not found: $TARGET"
exit 2
fi
for f in "$TARGET"/*; do
[ -f "$f" ] || continue
generate_thumbnail "$f" &
done
wait
;;
*)
usage
;;
esac
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate
"$SCRIPT_DIR/thumbgen.py" "$@"
deactivate
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
# From https://github.com/difference-engine/thumbnail-generator-ubuntu (MIT License)
# Since the script is small and the maintainers seem inactive to accept my PR (#11) I decided to just copy it over.
# When it gets merged and the python package gets updated we can just use it
import os
import sys
from multiprocessing import Pool
from pathlib import Path
from typing import List, Union
import click
import gi
from loguru import logger
from tqdm import tqdm
gi.require_version("GnomeDesktop", "3.0")
from gi.repository import Gio, GnomeDesktop # isort:skip
thumbnail_size_map = {
"normal": GnomeDesktop.DesktopThumbnailSize.NORMAL,
"large": GnomeDesktop.DesktopThumbnailSize.LARGE,
"x-large": GnomeDesktop.DesktopThumbnailSize.XLARGE,
"xx-large": GnomeDesktop.DesktopThumbnailSize.XXLARGE,
}
factory = None
logger.remove()
logger.add(sys.stdout, level="INFO")
logger.add("/tmp/thumbgen.log", level="DEBUG", rotation="100 MB")
def make_thumbnail(fpath: str) -> bool:
mtime = os.path.getmtime(fpath)
# Use Gio to determine the URI and mime type
f = Gio.file_new_for_path(str(fpath))
uri = f.get_uri()
info = f.query_info("standard::content-type", Gio.FileQueryInfoFlags.NONE, None)
mime_type = info.get_content_type()
if factory.lookup(uri, mtime) is not None:
logger.debug("FRESH {}".format(uri))
return False
if not factory.can_thumbnail(uri, mime_type, mtime):
logger.debug("UNSUPPORTED {}".format(uri))
return False
thumbnail = factory.generate_thumbnail(uri, mime_type)
if thumbnail is None:
logger.debug("ERROR {}".format(uri))
return False
logger.debug("OK {}".format(uri))
factory.save_thumbnail(thumbnail, uri, mtime)
return True
@logger.catch()
def thumbnail_folder(*, dir_path: Path, workers: int, only_images: bool, recursive: bool, machine_progress: bool = False) -> None:
all_files = get_all_files(dir_path=dir_path, recursive=recursive)
if only_images:
all_files = get_all_images(all_files=all_files)
all_files = [str(fpath) for fpath in all_files]
if machine_progress:
completed = 0
total = len(all_files)
with Pool(processes=workers) as p:
for result in p.imap(make_thumbnail, all_files):
completed += 1
print(f"PROGRESS {completed}/{total} FILE {all_files[completed-1]}")
sys.stdout.flush()
else:
with Pool(processes=workers) as p:
list(tqdm(p.imap(make_thumbnail, all_files), total=len(all_files)))
def get_all_images(*, all_files: List[Path]) -> List[Path]:
img_suffixes = [".jpg", ".jpeg", ".png", ".gif"]
all_images = [fpath for fpath in all_files if fpath.suffix in img_suffixes]
print("Found {} images".format(len(all_images)))
return all_images
def get_all_files(*, dir_path: Path, recursive: bool) -> List[Path]:
if not (dir_path.exists() and dir_path.is_dir()):
raise ValueError("{} doesn't exist or isn't a valid directory!".format(dir_path.resolve()))
if recursive:
all_files = dir_path.rglob("*")
else:
all_files = dir_path.glob("*")
all_files = [fpath for fpath in all_files if fpath.is_file()]
print("Found {} files in the directory: {}".format(len(all_files), dir_path.resolve()))
return all_files
@click.command()
@click.option(
"-d", "--img_dirs", required=True, help='directories to generate thumbnails seperated by space, eg: "dir1/dir2 dir3"'
)
@click.option(
"-s", "--size", default="normal", type=click.Choice(["normal", "large", "x-large", "xx-large"]), help="Thumbnail size: normal, large, x-large, xx-large"
)
@click.option("-w", "--workers", default=1, help="no of cpus to use for processing")
@click.option(
"-i", "--only_images", is_flag=True, default=False, help="Whether to only look for images to be thumbnailed"
)
@click.option("-r", "--recursive", is_flag=True, default=False, help="Whether to recursively look for files")
@click.option("--machine_progress", is_flag=True, default=False, help="Print machine-readable progress lines instead of a progress bar")
def main(img_dirs: str, size: str, workers: str, only_images: bool, recursive: bool, machine_progress: bool) -> None:
img_dirs = [Path(img_dir) for img_dir in img_dirs.split()]
global factory
factory = GnomeDesktop.DesktopThumbnailFactory.new(thumbnail_size_map[size])
for img_dir in img_dirs:
thumbnail_folder(dir_path=img_dir, workers=workers, only_images=only_images, recursive=recursive, machine_progress=machine_progress)
print("Thumbnail Generation Completed!")
if __name__ == "__main__":
main()