From 19c321e7ae30c563ec3bf9716acc57ba128f0d65 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 30 Aug 2025 22:10:29 +0200 Subject: [PATCH] wallpaper selector: progress indicator for thumbnail generation --- .../ii/modules/common/ThumbnailImage.qml | 5 +++-- .../WallpaperDirectoryItem.qml | 6 +++++ .../WallpaperSelectorContent.qml | 11 ++++++++-- .../thumbnails/generate-thumbnails-magick.sh | 8 ++++++- .../ii/scripts/thumbnails/thumbgen.py | 22 ++++++++++++++----- .config/quickshell/ii/services/Wallpapers.qml | 22 ++++++++++++++++++- 6 files changed, 62 insertions(+), 12 deletions(-) diff --git a/.config/quickshell/ii/modules/common/ThumbnailImage.qml b/.config/quickshell/ii/modules/common/ThumbnailImage.qml index ee928eef1..506c89c41 100644 --- a/.config/quickshell/ii/modules/common/ThumbnailImage.qml +++ b/.config/quickshell/ii/modules/common/ThumbnailImage.qml @@ -16,8 +16,9 @@ Image { property string thumbnailSizeName: Images.thumbnailSizeNameForDimensions(sourceSize.width, sourceSize.height) property string thumbnailPath: { if (sourcePath.length == 0) return; - const resolvedUrl = Qt.resolvedUrl(sourcePath); - const md5Hash = Qt.md5(resolvedUrl); + const resolvedUrlWithoutFileProtocol = FileUtils.trimFileProtocol(`${Qt.resolvedUrl(sourcePath)}`); + const encodedUrlWithoutFileProtocol = resolvedUrlWithoutFileProtocol.split("/").map(part => encodeURIComponent(part)).join("/"); + const md5Hash = Qt.md5(`file://${encodedUrlWithoutFileProtocol}`); return `${Directories.genericCache}/thumbnails/${thumbnailSizeName}/${md5Hash}.png`; } source: thumbnailPath diff --git a/.config/quickshell/ii/modules/wallpaperSelector/WallpaperDirectoryItem.qml b/.config/quickshell/ii/modules/wallpaperSelector/WallpaperDirectoryItem.qml index 0b3f877f4..de87d6a24 100644 --- a/.config/quickshell/ii/modules/wallpaperSelector/WallpaperDirectoryItem.qml +++ b/.config/quickshell/ii/modules/wallpaperSelector/WallpaperDirectoryItem.qml @@ -80,6 +80,12 @@ MouseArea { thumbnailImage.source = ""; thumbnailImage.source = thumbnailImage.thumbnailPath; } + function onThumbnailGeneratedFile(filePath) { + if (thumbnailImage.status !== Image.Error) return; + if (Qt.resolvedUrl(thumbnailImage.sourcePath) !== Qt.resolvedUrl(filePath)) return; + thumbnailImage.source = ""; + thumbnailImage.source = thumbnailImage.thumbnailPath; + } } layer.enabled: true diff --git a/.config/quickshell/ii/modules/wallpaperSelector/WallpaperSelectorContent.qml b/.config/quickshell/ii/modules/wallpaperSelector/WallpaperSelectorContent.qml index 82bf7d438..eef284c78 100644 --- a/.config/quickshell/ii/modules/wallpaperSelector/WallpaperSelectorContent.qml +++ b/.config/quickshell/ii/modules/wallpaperSelector/WallpaperSelectorContent.qml @@ -205,7 +205,9 @@ Item { Layout.fillWidth: true Layout.fillHeight: true - StyledProgressBar { + StyledIndeterminateProgressBar { + id: indeterminateProgressBar + visible: Wallpapers.thumbnailGenerationRunning && value == 0 anchors { bottom: parent.top left: parent.left @@ -213,7 +215,12 @@ Item { leftMargin: 4 rightMargin: 4 } - indeterminate: true + } + + StyledProgressBar { + visible: Wallpapers.thumbnailGenerationRunning && value > 0 + value: Wallpapers.thumbnailGenerationProgress + anchors.fill: indeterminateProgressBar } GridView { diff --git a/.config/quickshell/ii/scripts/thumbnails/generate-thumbnails-magick.sh b/.config/quickshell/ii/scripts/thumbnails/generate-thumbnails-magick.sh index 31a346ca1..a5c858132 100755 --- a/.config/quickshell/ii/scripts/thumbnails/generate-thumbnails-magick.sh +++ b/.config/quickshell/ii/scripts/thumbnails/generate-thumbnails-magick.sh @@ -47,6 +47,12 @@ 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 @@ -58,7 +64,7 @@ generate_thumbnail() { if [ -f "$out" ]; then return fi - magick "$abs_path" -resize ${THUMBNAIL_SIZE}x${THUMBNAIL_SIZE} "$out" + magick "$abs_path" -resize "${THUMBNAIL_SIZE}x${THUMBNAIL_SIZE}" "$out" } # Parse arguments diff --git a/.config/quickshell/ii/scripts/thumbnails/thumbgen.py b/.config/quickshell/ii/scripts/thumbnails/thumbgen.py index 4dc3ccd56..4d6326aba 100755 --- a/.config/quickshell/ii/scripts/thumbnails/thumbgen.py +++ b/.config/quickshell/ii/scripts/thumbnails/thumbgen.py @@ -1,4 +1,4 @@ -#!/usr/bin/env -S\_/bin/sh\_-xc\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@"" +#!/usr/bin/env -S\_/bin/sh\_-c\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@"" # 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. @@ -57,13 +57,22 @@ def make_thumbnail(fpath: str) -> bool: @logger.catch() -def thumbnail_folder(*, dir_path: Path, workers: int, only_images: bool, recursive: bool) -> None: +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] - with Pool(processes=workers) as p: - list(tqdm(p.imap(make_thumbnail, all_files), total=len(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]: @@ -96,12 +105,13 @@ def get_all_files(*, dir_path: Path, recursive: bool) -> List[Path]: "-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") -def main(img_dirs: str, size: str, workers: str, only_images: bool, recursive: bool) -> None: +@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) + thumbnail_folder(dir_path=img_dir, workers=workers, only_images=only_images, recursive=recursive, machine_progress=machine_progress) print("Thumbnail Generation Completed!") diff --git a/.config/quickshell/ii/services/Wallpapers.qml b/.config/quickshell/ii/services/Wallpapers.qml index dc8734433..910e9eaa2 100644 --- a/.config/quickshell/ii/services/Wallpapers.qml +++ b/.config/quickshell/ii/services/Wallpapers.qml @@ -23,9 +23,12 @@ Singleton { "jpg", "jpeg", "png", "webp", "avif", "bmp", "svg" ] property list wallpapers: [] // List of absolute file paths (without file://) + readonly property bool thumbnailGenerationRunning: thumbgenProc.running + property real thumbnailGenerationProgress: 0 signal changed() signal thumbnailGenerated(directory: string) + signal thumbnailGeneratedFile(filePath: string) // Executions Process { @@ -126,13 +129,30 @@ Singleton { thumbgenProc.running = false thumbgenProc.command = [ "bash", "-c", - `${thumbgenScriptPath} --size ${size} -d ${root.directory} || ${generateThumbnailsMagicScriptPath} --size ${size} -d ${root.directory}`, + `${thumbgenScriptPath} --size ${size} --machine_progress -d ${root.directory} || ${generateThumbnailsMagicScriptPath} --size ${size} -d ${root.directory}`, ] + root.thumbnailGenerationProgress = 0 thumbgenProc.running = true } Process { id: thumbgenProc property string directory + stdout: SplitParser { + onRead: data => { + // print("thumb gen proc:", data) + let match = data.match(/PROGRESS (\d+)\/(\d+)/) + if (match) { + const completed = parseInt(match[1]) + const total = parseInt(match[2]) + root.thumbnailGenerationProgress = completed / total + } + match = data.match(/FILE (.+)/) + if (match) { + const filePath = match[1] + root.thumbnailGeneratedFile(filePath) + } + } + } onExited: (exitCode, exitStatus) => { root.thumbnailGenerated(thumbgenProc.directory) }