243 lines
7.3 KiB
Python
243 lines
7.3 KiB
Python
"""Core logic for Local Image Loader."""
|
|
|
|
import os
|
|
import json
|
|
import torch
|
|
import numpy as np
|
|
from PIL import Image
|
|
from typing import Tuple, Dict, Any, List
|
|
|
|
|
|
def get_supported_extensions() -> Dict[str, List[str]]:
|
|
"""Get supported file extensions by type."""
|
|
return {
|
|
"image": [".jpg", ".jpeg", ".png", ".bmp", ".gif", ".webp"],
|
|
"video": [".mp4", ".webm", ".mov", ".mkv", ".avi"],
|
|
"audio": [".mp3", ".wav", ".ogg", ".flac"],
|
|
}
|
|
|
|
|
|
def load_image_from_path(path: str) -> Tuple[torch.Tensor, Dict[str, Any]]:
|
|
"""
|
|
Load an image from the given path and convert it to a tensor.
|
|
|
|
Args:
|
|
path: Path to the image file
|
|
|
|
Returns:
|
|
Tuple of (image tensor, metadata dict)
|
|
"""
|
|
if not os.path.exists(path):
|
|
raise FileNotFoundError(f"File not found: {path}")
|
|
|
|
with Image.open(path) as img:
|
|
# Convert to appropriate format
|
|
if "A" in img.getbands():
|
|
img_out = img.convert("RGBA")
|
|
else:
|
|
img_out = img.convert("RGB")
|
|
|
|
# Convert to tensor
|
|
img_array = np.array(img_out).astype(np.float32) / 255.0
|
|
image_tensor = torch.from_numpy(img_array)[None,]
|
|
|
|
# Collect metadata
|
|
metadata = {
|
|
"filename": os.path.basename(path),
|
|
"width": img.width,
|
|
"height": img.height,
|
|
"mode": img.mode,
|
|
"format": img.format,
|
|
}
|
|
|
|
# Check for embedded metadata
|
|
if "parameters" in img.info:
|
|
metadata["parameters"] = img.info["parameters"]
|
|
if "prompt" in img.info:
|
|
try:
|
|
metadata["prompt"] = json.loads(img.info["prompt"])
|
|
except (json.JSONDecodeError, TypeError):
|
|
metadata["prompt"] = img.info["prompt"]
|
|
if "workflow" in img.info:
|
|
try:
|
|
metadata["workflow"] = json.loads(img.info["workflow"])
|
|
except (json.JSONDecodeError, TypeError):
|
|
metadata["workflow"] = img.info["workflow"]
|
|
|
|
return image_tensor, metadata
|
|
|
|
|
|
def scan_directory(
|
|
directory: str,
|
|
show_videos: bool = False,
|
|
show_audio: bool = False,
|
|
sort_by: str = "name",
|
|
sort_order: str = "asc",
|
|
hide_dot_folders: bool = True,
|
|
) -> List[Dict[str, Any]]:
|
|
"""
|
|
Scan a directory for supported media files.
|
|
|
|
Args:
|
|
directory: Directory path to scan
|
|
show_videos: Include video files
|
|
show_audio: Include audio files
|
|
sort_by: Sort criteria ('name', 'date', 'size')
|
|
sort_order: Sort order ('asc', 'desc')
|
|
hide_dot_folders: Hide folders starting with a dot
|
|
|
|
Returns:
|
|
List of file information dictionaries
|
|
"""
|
|
if not os.path.isdir(directory):
|
|
raise NotADirectoryError(f"Not a directory: {directory}")
|
|
|
|
extensions = get_supported_extensions()
|
|
items = []
|
|
|
|
for item in os.listdir(directory):
|
|
# Skip dot folders/files if hide_dot_folders is enabled
|
|
if hide_dot_folders and item.startswith("."):
|
|
continue
|
|
|
|
full_path = os.path.join(directory, item)
|
|
|
|
try:
|
|
stats = os.stat(full_path)
|
|
item_data = {
|
|
"path": full_path,
|
|
"name": item,
|
|
"mtime": stats.st_mtime,
|
|
"size": stats.st_size,
|
|
}
|
|
|
|
if os.path.isdir(full_path):
|
|
items.append({**item_data, "type": "dir"})
|
|
else:
|
|
ext = os.path.splitext(item)[1].lower()
|
|
item_type = None
|
|
|
|
if ext in extensions["image"]:
|
|
item_type = "image"
|
|
elif show_videos and ext in extensions["video"]:
|
|
item_type = "video"
|
|
elif show_audio and ext in extensions["audio"]:
|
|
item_type = "audio"
|
|
|
|
if item_type:
|
|
items.append({**item_data, "type": item_type})
|
|
|
|
except (PermissionError, FileNotFoundError):
|
|
continue
|
|
|
|
# Sort items
|
|
reverse = sort_order == "desc"
|
|
if sort_by == "date":
|
|
items.sort(key=lambda x: x["mtime"], reverse=reverse)
|
|
elif sort_by == "size":
|
|
items.sort(key=lambda x: x.get("size", 0), reverse=reverse)
|
|
else: # name
|
|
items.sort(key=lambda x: x["name"].lower(), reverse=reverse)
|
|
|
|
# Directories first
|
|
items.sort(key=lambda x: x["type"] != "dir")
|
|
|
|
return items
|
|
|
|
|
|
def search_files(
|
|
root_directory: str,
|
|
query: str,
|
|
show_videos: bool = False,
|
|
show_audio: bool = False,
|
|
max_results: int = 100,
|
|
) -> List[Dict[str, Any]]:
|
|
"""
|
|
Recursively search for files matching the query.
|
|
|
|
Args:
|
|
root_directory: Root directory to start search
|
|
query: Search query (case-insensitive filename match)
|
|
show_videos: Include video files
|
|
show_audio: Include audio files
|
|
max_results: Maximum number of results to return
|
|
|
|
Returns:
|
|
List of file information dictionaries
|
|
"""
|
|
if not os.path.isdir(root_directory):
|
|
raise NotADirectoryError(f"Not a directory: {root_directory}")
|
|
|
|
if not query or len(query.strip()) == 0:
|
|
return []
|
|
|
|
extensions = get_supported_extensions()
|
|
results = []
|
|
query_lower = query.lower().strip()
|
|
|
|
def search_recursive(directory: str) -> None:
|
|
"""Recursively search directory."""
|
|
if len(results) >= max_results:
|
|
return
|
|
|
|
try:
|
|
items = os.listdir(directory)
|
|
except (PermissionError, FileNotFoundError):
|
|
return
|
|
|
|
for item in items:
|
|
if len(results) >= max_results:
|
|
break
|
|
|
|
full_path = os.path.join(directory, item)
|
|
|
|
try:
|
|
# Check if item name matches query
|
|
if query_lower not in item.lower():
|
|
# If directory, search inside
|
|
if os.path.isdir(full_path):
|
|
search_recursive(full_path)
|
|
continue
|
|
|
|
stats = os.stat(full_path)
|
|
item_data = {
|
|
"path": full_path,
|
|
"name": item,
|
|
"directory": directory,
|
|
"mtime": stats.st_mtime,
|
|
"size": stats.st_size,
|
|
}
|
|
|
|
if os.path.isdir(full_path):
|
|
results.append({**item_data, "type": "dir"})
|
|
# Continue searching inside matching directories
|
|
search_recursive(full_path)
|
|
else:
|
|
ext = os.path.splitext(item)[1].lower()
|
|
item_type = None
|
|
|
|
if ext in extensions["image"]:
|
|
item_type = "image"
|
|
elif show_videos and ext in extensions["video"]:
|
|
item_type = "video"
|
|
elif show_audio and ext in extensions["audio"]:
|
|
item_type = "audio"
|
|
|
|
if item_type:
|
|
results.append({**item_data, "type": item_type})
|
|
|
|
except (PermissionError, FileNotFoundError):
|
|
continue
|
|
|
|
search_recursive(root_directory)
|
|
|
|
# Sort by name
|
|
results.sort(key=lambda x: x["name"].lower())
|
|
|
|
return results
|
|
|
|
|
|
def create_empty_tensor() -> torch.Tensor:
|
|
"""Create an empty tensor for when no image is selected."""
|
|
return torch.zeros(1, 1, 1, 4)
|