117 lines
3.8 KiB
Python
117 lines
3.8 KiB
Python
import argparse
|
|
import math
|
|
import sys
|
|
import os
|
|
import shutil
|
|
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 sorted(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}")
|
|
|
|
for pdf_path in sorted(search_path.glob("*/*.pdf")):
|
|
student_name = pdf_path.stem # Filename without extension
|
|
save_path = OUTPUT_DIR / f"{student_name}.pdf"
|
|
|
|
shutil.copy(str(pdf_path), str(save_path))
|
|
|
|
|
|
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)
|