import argparse import math import sys import os from pathlib import Path import pandas as pd from PIL import Image, ImageDraw, ImageFont # Configuration constants ODS_PATH = "/home/sebastien/Rust/gestion_classe/Staging/simple_eval.ods" OUTPUT_DIR = Path("/home/sebastien/Rust/Server/copies") FONT_PATH = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" # Standard Linux font path def get_rounded_score(score): """Round score to one decimal place below (floor).""" try: val = float(score) return math.floor(val * 10) / 10 except (ValueError, TypeError): return None def process_images(base_dir): # 1. Load Data try: # header=None assumes the file starts directly with data. # If row 0 is a header, change to header=0 df = pd.read_excel(ODS_PATH, engine="odf", header=None) # Create a lookup dictionary: {Name: Score} score_db = dict(zip(df[0], df[1])) except Exception as e: print(f"CRITICAL ERROR: Could not read ODS file.\n{e}") sys.exit(1) # 2. Prepare Output Directory OUTPUT_DIR.mkdir(parents=True, exist_ok=True) # 3. Iterate Files # Structure: Dir/A Rendre/{name}/{name}.jpg search_path = base_dir / "A Rendre" if not search_path.exists(): print(f"Error: Directory '{search_path}' not found.") sys.exit(1) for img_path in search_path.glob("*/*.jpg"): student_name = img_path.stem # Filename without extension # 4. Find Score if student_name not in score_db: print(f"Error: Student '{student_name}' not found in ODS file.") continue raw_score = score_db[student_name] score = get_rounded_score(raw_score) if score is None: print(f"Error: Invalid score '{raw_score}' for '{student_name}'.") continue # 5. Process Image try: with Image.open(img_path) as img: img = img.convert("RGB") draw = ImageDraw.Draw(img) width, height = img.size # Dynamic font size (15% of image height) font_size = int(width * 0.08) try: font = ImageFont.truetype(FONT_PATH, font_size) except IOError: # Fallback if specific font not found font = ImageFont.load_default() print(f"Warning: System font not found, using default for {student_name}") text = str(score) # Calculate text size and position (Top Right) bbox = draw.textbbox((0, 0), text, font=font) text_w = bbox[2] - bbox[0] text_h = bbox[3] - bbox[1] # 30px padding x = width - text_w - 30 y = 30 # Draw Text (Red) draw.text((x, y), text, fill=(255, 0, 0), font=font) # Save save_path = OUTPUT_DIR / f"{student_name}.jpg" img.save(save_path) print(f"Processed: {student_name} -> {score}") except Exception as e: print(f"Error processing image for '{student_name}': {e}") if __name__ == "__main__": parser = argparse.ArgumentParser(description="Stamp scores on exam copies.") parser.add_argument("dir", type=Path, help="Root directory containing 'A Rendre' folder") args = parser.parse_args() OUTPUT_DIR = OUTPUT_DIR / args.dir OUTPUT_DIR.mkdir(parents=True, exist_ok=True) process_images(args.dir)