spritetagger/labelstore.py
2026-05-23 21:32:55 -04:00

55 lines
1.7 KiB
Python

"""Persistence for sprite frame label assignments.
Assignments are stored in a JSON sidecar keyed by filename (not frame index),
so resuming a session survives frames being added, removed, or reordered.
"""
import json
from pathlib import Path
SIDECAR_NAME = '.sprite_tags.json'
SCHEMA_VERSION = 1
def sidecar_path(frames_dir: Path) -> Path:
return frames_dir / SIDECAR_NAME
def save_labels(path: Path, assignments_by_name: dict, labels: list) -> None:
data = {
'version': SCHEMA_VERSION,
'labels': list(labels),
'assignments': dict(assignments_by_name),
}
Path(path).write_text(json.dumps(data, indent=2))
def load_labels(path: Path):
"""Return (assignments_by_name, labels). Missing or invalid file → ({}, [])."""
path = Path(path)
if not path.exists():
return {}, []
try:
data = json.loads(path.read_text())
except (json.JSONDecodeError, OSError, ValueError):
return {}, []
assignments = data.get('assignments', {})
labels = data.get('labels', [])
if not isinstance(assignments, dict) or not isinstance(labels, list):
return {}, []
return assignments, labels
def to_name_keyed(assignments_by_index: dict, frames: list) -> dict:
"""{index: label} → {filename: label}."""
return {frames[i].name: label for i, label in assignments_by_index.items()}
def to_index_keyed(assignments_by_name: dict, frames: list) -> dict:
"""{filename: label} → {index: label}, dropping files not in `frames`."""
name_to_index = {p.name: i for i, p in enumerate(frames)}
return {
name_to_index[name]: label
for name, label in assignments_by_name.items()
if name in name_to_index
}