chore: initialize screenjob project baseline

This commit is contained in:
Space-Banane
2026-05-27 17:31:49 +02:00
commit 84b0df520c
9 changed files with 1045 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
from __future__ import annotations
import base64
import io
import logging
import sys
from datetime import datetime, timezone
from pathlib import Path
from PIL import Image, ImageDraw
from .models import RunArtifacts
def utc_now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
def ensure_dir(path: Path) -> None:
path.mkdir(parents=True, exist_ok=True)
def clamp(value: int, minimum: int, maximum: int) -> int:
return max(minimum, min(maximum, value))
def image_to_data_url(image: Image.Image, fmt: str = "PNG") -> str:
buf = io.BytesIO()
image.save(buf, format=fmt)
encoded = base64.b64encode(buf.getvalue()).decode("ascii")
mime = "image/png" if fmt.upper() == "PNG" else "image/jpeg"
return f"data:{mime};base64,{encoded}"
def draw_global_grid(image: Image.Image, step: int = 100) -> Image.Image:
canvas = image.convert("RGB").copy()
draw = ImageDraw.Draw(canvas)
width, height = canvas.size
grid_color = (30, 200, 255)
minor_color = (180, 220, 240)
text_bg = (0, 0, 0)
text_fg = (255, 255, 255)
draw.rectangle([0, 0, width - 1, height - 1], outline=(255, 80, 80), width=2)
for x in range(0, width, step):
color = grid_color if x % (step * 5) == 0 else minor_color
draw.line([(x, 0), (x, height)], fill=color, width=1)
label = f"x={x}"
draw.rectangle([x + 2, 2, x + 58, 18], fill=text_bg)
draw.text((x + 4, 4), label, fill=text_fg)
for y in range(0, height, step):
color = grid_color if y % (step * 5) == 0 else minor_color
draw.line([(0, y), (width, y)], fill=color, width=1)
label = f"y={y}"
draw.rectangle([2, y + 2, 58, y + 18], fill=text_bg)
draw.text((4, y + 4), label, fill=text_fg)
draw.rectangle([5, 5, 520, 35], fill=text_bg)
draw.text(
(10, 12),
"Coordinate system: origin at top-left, values in pixels",
fill=text_fg,
)
return canvas
def setup_artifacts(base_dir: Path) -> RunArtifacts:
run_id = datetime.now().strftime("%Y%m%d_%H%M%S")
root = base_dir / f"run_{run_id}"
logs_dir = root / "logs"
shots_dir = root / "screens"
enhance_dir = root / "enhanced"
for path in (root, logs_dir, shots_dir, enhance_dir):
ensure_dir(path)
return RunArtifacts(
run_id=run_id,
root_dir=root,
logs_dir=logs_dir,
shots_dir=shots_dir,
enhance_dir=enhance_dir,
log_file=logs_dir / "screenjob.log",
)
def setup_logger(log_file: Path, verbose: bool = True) -> logging.Logger:
logger = logging.getLogger("screenjob")
logger.setLevel(logging.DEBUG)
logger.handlers.clear()
stream_level = logging.INFO if verbose else logging.WARNING
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(stream_level)
stream_handler.setFormatter(
logging.Formatter("%(asctime)s | %(levelname)-8s | %(message)s")
)
file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(
logging.Formatter(
"%(asctime)s | %(levelname)-8s | %(name)s | %(filename)s:%(lineno)d | %(message)s"
)
)
logger.addHandler(stream_handler)
logger.addHandler(file_handler)
return logger