From b8a5eaf9a6b3ab1d2754fb38a27316ac7a1320bb Mon Sep 17 00:00:00 2001 From: Shaun Johnston Date: Thu, 25 Dec 2025 19:41:01 +1000 Subject: [PATCH] fix subdir thumbnail gen --- py/api.py | 33 ++++++++++++++++++++++----------- web/admin.html | 20 +++++++++++++++++--- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/py/api.py b/py/api.py index e0e10f7..71cde29 100644 --- a/py/api.py +++ b/py/api.py @@ -1021,11 +1021,13 @@ class PromptManagerAPI: thumbnails_dir = output_path / "thumbnails" if thumbnails_dir.exists(): thumbnail_ext = '.jpg' if is_video else extension - thumbnail_rel_path = f"thumbnails/{media_path.stem}_thumb{thumbnail_ext}" + # Use full relative path to support subdirectories + thumbnail_rel_path = Path("thumbnails") / rel_path.parent / f"{media_path.stem}_thumb{thumbnail_ext}" thumbnail_abs_path = output_path / thumbnail_rel_path if thumbnail_abs_path.exists(): - thumbnail_url = f'/prompt_manager/images/serve/{Path(thumbnail_rel_path).as_posix()}' + from urllib.parse import quote + thumbnail_url = f'/prompt_manager/images/serve/{quote(thumbnail_rel_path.as_posix(), safe="/")}' image_info = { 'id': str(hash(str(media_path))), @@ -1146,7 +1148,9 @@ class PromptManagerAPI: # Also try to delete associated thumbnail if it exists try: - thumbnail_path = output_path / "thumbnails" / f"{file_path.stem}_thumb{file_path.suffix}" + # Use full relative path to support subdirectories + rel_path = file_path.relative_to(output_path) + thumbnail_path = output_path / "thumbnails" / rel_path.parent / f"{file_path.stem}_thumb{file_path.suffix}" if thumbnail_path.exists(): os.remove(thumbnail_path) self.logger.debug(f"Deleted associated thumbnail: {thumbnail_path}") @@ -1906,12 +1910,14 @@ class PromptManagerAPI: thumbnail_url = None if thumbnails_dir.exists(): # For videos, look for thumbnail with .jpg extension + # Use full relative path to support subdirectories thumbnail_ext = '.jpg' if is_video else extension - thumbnail_rel_path = f"thumbnails/{media_path.stem}_thumb{thumbnail_ext}" + thumbnail_rel_path = Path("thumbnails") / rel_path.parent / f"{media_path.stem}_thumb{thumbnail_ext}" thumbnail_abs_path = output_path / thumbnail_rel_path if thumbnail_abs_path.exists(): - thumbnail_url = f'/prompt_manager/images/serve/{Path(thumbnail_rel_path).as_posix()}' + from urllib.parse import quote + thumbnail_url = f'/prompt_manager/images/serve/{quote(thumbnail_rel_path.as_posix(), safe="/")}' images.append({ 'id': str(hash(str(media_path))), # Simple hash for ID @@ -2159,10 +2165,11 @@ class PromptManagerAPI: is_video = any(media_file.name.lower().endswith(ext) for ext in video_extensions) # For videos, always save thumbnail as .jpg + # Include parent directory structure to avoid collisions with same-named files if is_video: - thumbnail_path = thumbnails_dir / f"{rel_path.stem}_thumb.jpg" + thumbnail_path = thumbnails_dir / rel_path.parent / f"{rel_path.stem}_thumb.jpg" else: - thumbnail_path = thumbnails_dir / f"{rel_path.stem}_thumb{rel_path.suffix}" + thumbnail_path = thumbnails_dir / rel_path.parent / f"{rel_path.stem}_thumb{rel_path.suffix}" # Skip if thumbnail already exists and is newer than original if (thumbnail_path.exists() and @@ -2375,10 +2382,11 @@ class PromptManagerAPI: rel_path = media_file.relative_to(output_path) # For videos, always save thumbnail as .jpg + # Include parent directory structure to avoid collisions with same-named files if is_video: - thumbnail_path = thumbnails_dir / f"{rel_path.stem}_thumb.jpg" + thumbnail_path = thumbnails_dir / rel_path.parent / f"{rel_path.stem}_thumb.jpg" else: - thumbnail_path = thumbnails_dir / f"{rel_path.stem}_thumb{rel_path.suffix}" + thumbnail_path = thumbnails_dir / rel_path.parent / f"{rel_path.stem}_thumb{rel_path.suffix}" # SAFETY: Ensure thumbnail path is within our thumbnails directory try: @@ -4739,9 +4747,12 @@ class PromptManagerAPI: thumbnail_url = None thumbnails_dir = output_path / "thumbnails" if thumbnails_dir.exists(): - thumbnail_path = thumbnails_dir / f"{image_path.stem}_thumb{image_path.suffix}" + # Use full relative path to support subdirectories + thumbnail_rel = Path("thumbnails") / rel_path.parent / f"{image_path.stem}_thumb{image_path.suffix}" + thumbnail_path = output_path / thumbnail_rel if thumbnail_path.exists(): - thumbnail_url = f'/prompt_manager/images/serve/thumbnails/{image_path.stem}_thumb{image_path.suffix}' + from urllib.parse import quote + thumbnail_url = f'/prompt_manager/images/serve/{quote(thumbnail_rel.as_posix(), safe="/")}' images.append({ 'filename': image_path.name, diff --git a/web/admin.html b/web/admin.html index 154ee95..46c4423 100644 --- a/web/admin.html +++ b/web/admin.html @@ -4713,14 +4713,28 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; // If thumbnail_url already exists, use it if (image.thumbnail_url) return image.thumbnail_url; - // Try to construct thumbnail URL from filename - // Thumbnails are stored as: thumbnails/{stem}_thumb{ext} + // Try to construct thumbnail URL from filename and relative_path + // Thumbnails are stored as: thumbnails/{parent_dir}/{stem}_thumb{ext} + // to avoid collisions with same-named files in different directories if (image.filename) { const lastDot = image.filename.lastIndexOf('.'); if (lastDot > 0) { const stem = image.filename.substring(0, lastDot); const ext = image.filename.substring(lastDot); - return `/prompt_manager/images/serve/thumbnails/${stem}_thumb${ext}`; + + // Get parent directory from relative_path if available + let parentDir = ''; + if (image.relative_path) { + const relPath = image.relative_path.replace(/\\/g, '/'); + const lastSlash = relPath.lastIndexOf('/'); + if (lastSlash > 0) { + parentDir = relPath.substring(0, lastSlash) + '/'; + } + } + + // URL encode the path components (but preserve slashes) + const encodedParentDir = parentDir.split('/').map(p => encodeURIComponent(p)).join('/'); + return `/prompt_manager/images/serve/thumbnails/${encodedParentDir}${encodeURIComponent(stem)}_thumb${ext}`; } }