"""Turns a checkout of free-exercise-db into ZenXercise's bundled Exercise Library. Writes `exercises.json` (slimmed, with each Exercise's Mode) and one WebP per image into the output folder, flattened to `images/_.webp` because Flutter assets are declared per folder. """ from __future__ import annotations import json from pathlib import Path from PIL import Image STRETCHING_CATEGORY = "stretching" MAX_EDGE = 480 WEBP_QUALITY = 72 def mode_of(category: str) -> str: """Category `stretching` belongs to Stretching Mode, everything else to Workout Mode.""" return "stretching" if category == STRETCHING_CATEGORY else "workout" def slim(exercise: dict) -> dict: """The fields the app needs, with images reduced to a count.""" return { "id": exercise["id"], "name": exercise["name"], "mode": mode_of(exercise["category"]), "category": exercise["category"], "level": exercise["level"], "equipment": exercise.get("equipment"), "force": exercise.get("force"), "mechanic": exercise.get("mechanic"), "primaryMuscles": exercise["primaryMuscles"], "secondaryMuscles": exercise["secondaryMuscles"], "instructions": [line.strip() for line in exercise["instructions"] if line.strip()], "imageCount": len(exercise["images"]), } def image_name(exercise_id: str, index: int) -> str: return f"{exercise_id}_{index}.webp" def convert_image(source: Path, target: Path) -> None: with Image.open(source) as image: image = image.convert("RGB") image.thumbnail((MAX_EDGE, MAX_EDGE)) image.save(target, "WEBP", quality=WEBP_QUALITY, method=6) def run(source_root: Path, output_root: Path) -> list[dict]: exercises = json.loads((source_root / "dist" / "exercises.json").read_text(encoding="utf-8")) images = output_root / "images" images.mkdir(parents=True, exist_ok=True) for stale in images.glob("*.webp"): stale.unlink() slimmed = sorted((slim(e) for e in exercises), key=lambda e: e["name"].lower()) for exercise in exercises: for index, relative in enumerate(exercise["images"]): convert_image(source_root / "exercises" / relative, images / image_name(exercise["id"], index)) (output_root / "exercises.json").write_text( json.dumps(slimmed, ensure_ascii=False, separators=(",", ":")), encoding="utf-8" ) return slimmed