69 lines
2.4 KiB
Python
69 lines
2.4 KiB
Python
"""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/<id>_<n>.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
|