Refactor
This commit is contained in:
+30
-59
@@ -1,14 +1,11 @@
|
||||
import os
|
||||
import base64
|
||||
import mimetypes
|
||||
import logging
|
||||
import asyncio
|
||||
import io
|
||||
import datetime
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Union
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from tools.utils import format_relative_time
|
||||
from tools.utils import format_relative_time, ToolError, COMMON_FONTS, get_file_info_list, sort_file_list, get_paginated_list
|
||||
|
||||
logger = logging.getLogger("MattCP")
|
||||
|
||||
@@ -18,44 +15,32 @@ async def handle(args: Dict[str, Any]):
|
||||
Grid: 10 columns x 7 rows (70 images total).
|
||||
Sized for optimal token usage (1120 tokens) on llama.cpp.
|
||||
"""
|
||||
dir_path = args.get("path")
|
||||
dir_path_str = args.get("path")
|
||||
page = int(args.get("page", 1))
|
||||
sort_by = args.get("sort_by", "mtime")
|
||||
|
||||
if not dir_path:
|
||||
return {"text": "Error: Missing path argument"}
|
||||
if not os.path.isdir(dir_path):
|
||||
return {"text": f"Error: {dir_path} is not a directory."}
|
||||
if not dir_path_str:
|
||||
raise ToolError("Missing path argument")
|
||||
|
||||
dir_path = Path(dir_path_str)
|
||||
if not dir_path.is_dir():
|
||||
raise ToolError(f"{dir_path_str} is not a directory.")
|
||||
|
||||
exts = ('.png', '.jpg', '.jpeg', '.webp', '.bmp')
|
||||
file_info_list = []
|
||||
for f in os.listdir(dir_path):
|
||||
if f.lower().endswith(exts):
|
||||
full_path = os.path.join(dir_path, f)
|
||||
try:
|
||||
stats = os.stat(full_path)
|
||||
file_info_list.append({
|
||||
"name": f,
|
||||
"path": full_path,
|
||||
"mtime": stats.st_mtime,
|
||||
"size": stats.st_size
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Could not stat {f}: {e}")
|
||||
file_info_list = get_file_info_list(dir_path, extensions=exts)
|
||||
|
||||
if not file_info_list:
|
||||
return {"text": "No supported images found in the directory."}
|
||||
|
||||
# Sorting
|
||||
if sort_by == "mtime":
|
||||
file_info_list.sort(key=lambda x: x["mtime"], reverse=True)
|
||||
sort_label = "Date (newest first)"
|
||||
elif sort_by == "size":
|
||||
file_info_list.sort(key=lambda x: x["size"], reverse=True)
|
||||
sort_label = "Size (largest first)"
|
||||
else:
|
||||
file_info_list.sort(key=lambda x: x["name"].lower())
|
||||
sort_label = "Name (alphabetical)"
|
||||
file_info_list = sort_file_list(file_info_list, sort_by)
|
||||
|
||||
sort_labels = {
|
||||
"mtime": "Date (newest first)",
|
||||
"size": "Size (largest first)",
|
||||
"name": "Name (alphabetical)"
|
||||
}
|
||||
sort_label = sort_labels.get(sort_by, "Name (alphabetical)")
|
||||
|
||||
# Fixed grid dimensions for token optimization
|
||||
COLS = 10
|
||||
@@ -63,15 +48,12 @@ async def handle(args: Dict[str, Any]):
|
||||
PAGE_SIZE = COLS * ROWS
|
||||
|
||||
total_files = len(file_info_list)
|
||||
start_idx = (page - 1) * PAGE_SIZE
|
||||
end_idx = start_idx + PAGE_SIZE
|
||||
paged_files = file_info_list[start_idx:end_idx]
|
||||
paged_files = get_paginated_list(file_info_list, page, PAGE_SIZE)
|
||||
|
||||
if not paged_files:
|
||||
return {"text": f"No images found on page {page}."}
|
||||
|
||||
thumb_size = 192 # 192 / 48 = 4 patches per side
|
||||
|
||||
canvas_w = COLS * thumb_size
|
||||
canvas_h = ROWS * thumb_size
|
||||
|
||||
@@ -80,21 +62,16 @@ async def handle(args: Dict[str, Any]):
|
||||
|
||||
# Font loading
|
||||
font = None
|
||||
font_paths = [
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
||||
"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf",
|
||||
"/usr/share/fonts/truetype/freefont/FreeSansBold.ttf",
|
||||
]
|
||||
for path in font_paths:
|
||||
if os.path.exists(path):
|
||||
try:
|
||||
font = ImageFont.truetype(path, 24)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
for font_candidate in COMMON_FONTS:
|
||||
try:
|
||||
font = ImageFont.truetype(font_candidate, 24)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if font is None:
|
||||
font = ImageFont.load_default()
|
||||
|
||||
start_idx = (page - 1) * PAGE_SIZE
|
||||
for i, info in enumerate(paged_files):
|
||||
full_path = info["path"]
|
||||
row = i // COLS
|
||||
@@ -102,7 +79,6 @@ async def handle(args: Dict[str, Any]):
|
||||
|
||||
x = col * thumb_size
|
||||
y = row * thumb_size
|
||||
|
||||
global_index = start_idx + i + 1
|
||||
|
||||
try:
|
||||
@@ -112,17 +88,13 @@ async def handle(args: Dict[str, Any]):
|
||||
off_y = (thumb_size - img.height) // 2
|
||||
canvas.paste(img, (x + off_x, y + off_y))
|
||||
|
||||
# Index number in lower-left corner
|
||||
text = str(global_index)
|
||||
if font:
|
||||
bbox = draw.textbbox((0, 0), text, font=font)
|
||||
text_w = bbox[2] - bbox[0]
|
||||
text_h = bbox[3] - bbox[1]
|
||||
text_w, text_h = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
||||
else:
|
||||
text_w = len(text) * 8
|
||||
text_h = 15
|
||||
text_w, text_h = len(text) * 8, 15
|
||||
|
||||
# Semi-opaque background rectangle for the number
|
||||
draw.rectangle([x, y + thumb_size - text_h - 5, x + text_w + 5, y + thumb_size - 2], fill=(0, 0, 0, 180))
|
||||
draw.text((x + 2, y + thumb_size - text_h - 7), text, fill=(255, 255, 255), font=font)
|
||||
|
||||
@@ -136,7 +108,6 @@ async def handle(args: Dict[str, Any]):
|
||||
|
||||
total_pages = (total_files + PAGE_SIZE - 1) // PAGE_SIZE
|
||||
|
||||
# Range shown: first and last of the CURRENT PAGE
|
||||
first_on_page = paged_files[0]
|
||||
last_on_page = paged_files[-1]
|
||||
|
||||
@@ -151,7 +122,7 @@ async def handle(args: Dict[str, Any]):
|
||||
last_val = last_on_page['name']
|
||||
|
||||
header_text = (
|
||||
f"Directory: {dir_path}\n"
|
||||
f"Directory: {dir_path_str}\n"
|
||||
f"Page {page} of {total_pages} ({total_files} images total). Sorted by: {sort_label}\n"
|
||||
f"Range shown: {first_val} ... {last_val}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user