README, config file
This commit is contained in:
+20
-7
@@ -5,7 +5,9 @@ import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
||||
from tools.utils import format_relative_time, ToolError, COMMON_FONTS, get_file_info_list, sort_file_list, get_paginated_list
|
||||
import config
|
||||
from tools.utils import format_relative_time, ToolError, get_file_info_list, sort_file_list, get_paginated_list
|
||||
|
||||
|
||||
logger = logging.getLogger("MattCP")
|
||||
|
||||
@@ -43,9 +45,10 @@ async def handle(args: Dict[str, Any]):
|
||||
sort_label = sort_labels.get(sort_by, "Name (alphabetical)")
|
||||
|
||||
# Fixed grid dimensions for token optimization
|
||||
COLS = 10
|
||||
ROWS = 7
|
||||
COLS = config.CONTACT_SHEET_COLS
|
||||
ROWS = config.CONTACT_SHEET_ROWS
|
||||
PAGE_SIZE = COLS * ROWS
|
||||
|
||||
|
||||
total_files = len(file_info_list)
|
||||
paged_files = get_paginated_list(file_info_list, page, PAGE_SIZE)
|
||||
@@ -62,15 +65,25 @@ async def handle(args: Dict[str, Any]):
|
||||
|
||||
# Font loading
|
||||
font = None
|
||||
for font_candidate in COMMON_FONTS:
|
||||
# Try generic system font names first
|
||||
for font_name in config.SYSTEM_FONT_NAMES:
|
||||
try:
|
||||
font = ImageFont.truetype(font_candidate, 24)
|
||||
font = ImageFont.truetype(font_name, 24)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
# Fallback to absolute paths
|
||||
if font is None:
|
||||
for font_path in config.FALLBACK_FONT_PATHS:
|
||||
try:
|
||||
font = ImageFont.truetype(font_path, 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"]
|
||||
@@ -106,8 +119,8 @@ async def handle(args: Dict[str, Any]):
|
||||
draw.text((x + 5, y + thumb_size // 2), "Error", fill=(255, 0, 0), font=font)
|
||||
|
||||
buf = io.BytesIO()
|
||||
# Save as JPEG quality 95 to save data
|
||||
canvas.save(buf, format='JPEG', quality=95)
|
||||
# Save as JPEG to save data
|
||||
canvas.save(buf, format='JPEG', quality=config.IMAGE_QUALITY)
|
||||
img_data = base64.b64encode(buf.getvalue()).decode("utf-8")
|
||||
|
||||
total_pages = (total_files + PAGE_SIZE - 1) // PAGE_SIZE
|
||||
|
||||
+11
-6
@@ -6,24 +6,29 @@ import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Tuple
|
||||
from PIL import Image, ImageOps
|
||||
import config
|
||||
from tools.utils import format_relative_time, ToolError, get_file_info_list, sort_file_list, parse_indices
|
||||
|
||||
|
||||
logger = logging.getLogger("MattCP")
|
||||
|
||||
def calculate_patch_dimensions(orig_w: int, orig_h: int, target_tokens: int = 70):
|
||||
def calculate_patch_dimensions(orig_w: int, orig_h: int, target_tokens: int = None):
|
||||
"""
|
||||
Calculates optimal pixel dimensions to hit a token budget of at least target_tokens.
|
||||
Validates budget based on actual rounded pixel dimensions to avoid float precision errors.
|
||||
"""
|
||||
if target_tokens is None:
|
||||
target_tokens = config.PREVIEW_TOKEN_BUDGET
|
||||
|
||||
ar = orig_w / orig_h
|
||||
|
||||
# Case A: Width is the anchor (non-padded)
|
||||
w_anchor = 1
|
||||
while True:
|
||||
w_px = w_anchor * 48
|
||||
w_px = w_anchor * config.PATCH_SIZE
|
||||
h_px = round(w_px / ar)
|
||||
# Calculate actual tokens based on resulting pixel dimensions
|
||||
tokens = math.ceil(w_px / 48) * math.ceil(h_px / 48)
|
||||
tokens = math.ceil(w_px / config.PATCH_SIZE) * math.ceil(h_px / config.PATCH_SIZE)
|
||||
if tokens >= target_tokens:
|
||||
res_a = {"w": w_px, "h": h_px, "tokens": tokens}
|
||||
break
|
||||
@@ -32,10 +37,10 @@ def calculate_patch_dimensions(orig_w: int, orig_h: int, target_tokens: int = 70
|
||||
# Case B: Height is the anchor (non-padded)
|
||||
h_anchor = 1
|
||||
while True:
|
||||
h_px = h_anchor * 48
|
||||
h_px = h_anchor * config.PATCH_SIZE
|
||||
w_px = round(h_px * ar)
|
||||
# Calculate actual tokens based on resulting pixel dimensions
|
||||
tokens = math.ceil(w_px / 48) * math.ceil(h_px / 48)
|
||||
tokens = math.ceil(w_px / config.PATCH_SIZE) * math.ceil(h_px / config.PATCH_SIZE)
|
||||
if tokens >= target_tokens:
|
||||
res_b = {"w": w_px, "h": h_px, "tokens": tokens}
|
||||
break
|
||||
@@ -100,7 +105,7 @@ async def handle(args: Dict[str, Any]):
|
||||
thumb.thumbnail((target_w, target_h), Image.Resampling.LANCZOS)
|
||||
|
||||
buf = io.BytesIO()
|
||||
thumb.save(buf, format='JPEG', quality=95)
|
||||
thumb.save(buf, format='JPEG', quality=config.IMAGE_QUALITY)
|
||||
img_data = base64.b64encode(buf.getvalue()).decode("utf-8")
|
||||
|
||||
# Gather info
|
||||
|
||||
+1
-14
@@ -3,6 +3,7 @@ import datetime
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
|
||||
class ToolError(Exception):
|
||||
"""Custom exception for tool-related errors to be caught by the MCP server."""
|
||||
pass
|
||||
@@ -39,20 +40,6 @@ def format_relative_time(timestamp: float) -> str:
|
||||
dt = datetime.datetime.fromtimestamp(timestamp)
|
||||
return dt.strftime('%Y-%m-%d')
|
||||
|
||||
# Cross-platform font candidates
|
||||
# ImageFont.truetype can often find these by name on Windows,
|
||||
# or we can provide paths for Linux.
|
||||
COMMON_FONTS = [
|
||||
# Linux paths
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
||||
"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf",
|
||||
"/usr/share/fonts/truetype/freefont/FreeSansBold.ttf",
|
||||
# Windows filenames (Pillow often finds these in the system path)
|
||||
"arialbd.ttf",
|
||||
"calibrib.ttf",
|
||||
"verdanab.ttf",
|
||||
"tahomabd.ttf",
|
||||
]
|
||||
|
||||
def get_file_info_list(path: Path, extensions: Optional[tuple] = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user