Media pool: this project and all assets
This project lists every symbol in the document and the video it uses or was given this session, each with a frame from its proxy as a thumbnail. All assets lists every upload on the server and every other saved project's symbols, from a new /api/symbols that reads symbol leaf paths. Every row is a drag source (symbol:, import:, footage:). Uploading no longer runs straight into detection; it lands in the project's media. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c81f91c442
commit
d5f044c6da
11 changed files with 270 additions and 105 deletions
|
|
@ -401,6 +401,18 @@ class DocumentTests(TestCase):
|
|||
"blocks": blocks if blocks is not None else [self.block]}],
|
||||
})
|
||||
|
||||
def test_every_saved_symbol_is_listed_across_projects(self):
|
||||
leaves = self.leaves()
|
||||
leaves["clip/c1/symbol/sym~face"] = ["^ ", "~:name", "face", "~:frames", 12]
|
||||
leaves["clip/c1/symbol/sym~face/node/mark"] = ["^ ", "~:id", "~:mark", "~:z", "a1"]
|
||||
self.assertEqual(200, self.save(leaves).status_code)
|
||||
rows = self.client.get("/api/symbols").json()["symbols"]
|
||||
self.assertEqual(
|
||||
[("face", "sym~face", 12), ("main", "main", 48)],
|
||||
[(r["name"], r["symbol"], r["frames"]) for r in rows])
|
||||
self.assertEqual({str(self.project.id)}, {r["project"] for r in rows})
|
||||
self.assertEqual({"c1"}, {r["cid"] for r in rows})
|
||||
|
||||
def test_a_document_comes_back_exactly(self):
|
||||
response = self.save()
|
||||
self.assertEqual(200, response.status_code, response.content)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ urlpatterns = [
|
|||
path("footage", views.footage_list),
|
||||
path("footage/<uuid:footage_id>", views.footage_detail),
|
||||
path("projects", views.projects),
|
||||
path("symbols", views.symbols),
|
||||
path("projects/<uuid:project_id>", views.project_detail),
|
||||
path("projects/<uuid:project_id>/leaves/<path:leaf_path>", views.leaf_detail),
|
||||
path("projects/<uuid:project_id>/revisions", views.revisions),
|
||||
|
|
|
|||
|
|
@ -273,6 +273,44 @@ def footage_list(request):
|
|||
)
|
||||
|
||||
|
||||
_SYMBOL_LEAF = re.compile(r"^clip/([^/]+)/symbol/([^/]+)$")
|
||||
|
||||
|
||||
def _transit_fields(value, *keys):
|
||||
"""Top-level fields of a transit map leaf, by keyword name. A leaf's own
|
||||
facts are a small flat map, so no key repeats and transit's cache never
|
||||
stands in for one; anything else reads as absent."""
|
||||
if not (isinstance(value, list) and value[:1] == ["^ "]):
|
||||
return {}
|
||||
pairs = dict(zip(value[1::2], value[2::2]))
|
||||
return {k: pairs.get(f"~:{k}") for k in keys}
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def symbols(request):
|
||||
"""Every symbol in every saved project, for the pool's all-assets folder.
|
||||
|
||||
Read off the leaf PATHS rather than by loading documents: a symbol's own leaf
|
||||
is `clip/<cid>/symbol/<sid>`, so listing them is one query and no decoding
|
||||
beyond the name and length its value carries."""
|
||||
rows = []
|
||||
for leaf in Leaf.objects.filter(path__contains="/symbol/").select_related("project"):
|
||||
m = _SYMBOL_LEAF.match(leaf.path)
|
||||
if not m:
|
||||
continue
|
||||
fields = _transit_fields(leaf.value, "name", "frames")
|
||||
rows.append({
|
||||
"project": str(leaf.project_id),
|
||||
"project_name": leaf.project.name,
|
||||
"cid": m.group(1),
|
||||
"symbol": m.group(2),
|
||||
"name": fields.get("name") or m.group(2).replace("~", "/"),
|
||||
"frames": fields.get("frames"),
|
||||
})
|
||||
rows.sort(key=lambda r: (r["project_name"], r["project"], r["name"]))
|
||||
return JsonResponse({"symbols": rows})
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def footage_detail(request, footage_id):
|
||||
try:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue