Support multiple regeneratable footage analyses

This commit is contained in:
Your Name 2026-10-03 01:24:45 -04:00
parent 5bcf22e458
commit 15deaea19e
20 changed files with 333 additions and 168 deletions

View file

@ -718,8 +718,6 @@ def _project_json(project: Project, user):
{
"cid": clip.cid,
"name": clip.name,
"footage": str(clip.footage_id) if clip.footage_id else None,
"analysis": clip.analysis_id,
"blocks": sorted(clip.blocks.values_list("key", flat=True)),
"leaves": {leaf.path: leaf.value for leaf in leaves if leaf.path.startswith(prefix)},
}
@ -909,7 +907,19 @@ def _save(project: Project, data, user):
)
keys = spec.get("blocks") or []
have = set(Block.objects.filter(key__in=keys).values_list("key", flat=True))
analyses = spec.get("analyses") or []
if (not isinstance(analyses, list)
or not all(isinstance(key, str) for key in analyses)
or len(analyses) != len(set(analyses))):
raise Bad("a clip's analyses must be a list of distinct analysis ids")
registered = set(Analysis.objects.filter(key__in=analyses)
.values_list("key", flat=True))
if unknown := [key for key in analyses if key not in registered]:
raise Bad("this clip names analyses the server does not know; register them first",
status=409, missing=unknown)
block_rows = list(Block.objects.filter(key__in=keys))
have = {block.key for block in block_rows}
if missing := [k for k in keys if k not in have]:
# Referential integrity across the tiers, enforced where it can be:
# a document that names blocks the server does not hold would load
@ -919,6 +929,9 @@ def _save(project: Project, data, user):
"before saving the document that points at them",
status=409, missing=missing,
)
if undeclared := sorted({block.analysis_id for block in block_rows} - registered):
raise Bad("every block in a clip must name one of that clip's analyses",
status=409, missing=undeclared)
existing = {leaf.path: leaf for leaf in project.leaves.filter(path__startswith=prefix)}
if base is not None:
@ -930,16 +943,12 @@ def _save(project: Project, data, user):
if conflicts:
continue
analysis = Analysis.objects.filter(key=spec.get("analysis")).first()
footage = None
if spec.get("footage"):
footage = Footage.objects.filter(id=spec["footage"]).first()
clip, _ = Clip.objects.update_or_create(
project=project,
cid=cid,
defaults={"name": spec.get("name") or "", "analysis": analysis, "footage": footage},
defaults={"name": spec.get("name") or ""},
)
blocks = Block.objects.filter(key__in=keys)
blocks = block_rows
if base is None:
clip.blocks.set(blocks)
gone = [path for path in existing if path not in leaves]