Make the media pool a list you can actually read
Every row is one line now — a thumbnail at the stage's own 16:10, the name, and the one number you need before dragging it somewhere — at the timeline's own row height. Two always-open folders cost eight headings before the first row in a pane 210px wide; they are two tabs, and a search counts its hits in the scope you are not looking at so nothing can hide behind the one you did not pick. Symbols draw their own first frame, through the resolver and rasteriser the stage uses. A PLACED symbol is drawn where it is placed, with the rest of its host isolated away: rooting at a symbol renders its DRAWING, and a rotoscoped face is head-local in units of one image height, so the source-to-stage scale that makes it pixels lives on the :face group of whatever places it. Rendered rooted at itself a face is correct and under a pixel across. Thumbnails are smoothly downscaled for the same kind of reason the preview is not: at a tenth of the stage's size nearest neighbour samples one pixel in a hundred, and the silhouette is the whole of what makes a thumbnail recognisable. Names are editable, and that is two operations behind one pencil. A symbol's name is a field of this document, so it is an undoable edit and blank gives it back its id. Footage and sounds live beside projects rather than inside one, so theirs is a server write shared by every project using the row — PATCH on the existing Footage.label, and a new Sound.label kept separate from the filename, which is a fact about the upload and not somebody's name for it. No TIMELINES section beside a SYMBOLS one: every symbol here IS a timeline, so that pair named one thing twice. What is true is that exactly one of them is where the work happens, and clip/opens-on already answers which — it leads the pane as PROJECT, drawn at a size you can read a pose off, with the library under it. Still not :main being special; rename it or place it inside something else and the pool follows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
815ce449ea
commit
7e34d0c704
11 changed files with 924 additions and 160 deletions
|
|
@ -657,6 +657,64 @@ class FootageTests(TestCase):
|
|||
with self.assertRaisesMessage(CommandError, "refusing an inaccurate footage"):
|
||||
call_command("ingest_bundle", str(root), stdout=StringIO())
|
||||
|
||||
def test_footage_can_be_renamed_and_falls_back_when_cleared(self):
|
||||
"""A LABEL IS THE ONE FIELD A CLIENT MAY WRITE ON FOOTAGE. The rest is a
|
||||
description of bytes that are content-addressed and immutable, so a
|
||||
rename that could reach `frames` or `digest` would let the pool's name
|
||||
for a clip contradict the clip."""
|
||||
footage = self.ingest(self.bundle())
|
||||
self.assertEqual("IMG_8608.MOV", self.client.get(
|
||||
f"/api/footage/{footage.id}").json()["label"])
|
||||
|
||||
renamed = self.client.patch(f"/api/footage/{footage.id}",
|
||||
json.dumps({"label": " the long take "}),
|
||||
content_type="application/json")
|
||||
self.assertEqual(200, renamed.status_code, renamed.content)
|
||||
self.assertEqual("the long take", renamed.json()["label"])
|
||||
# On the row, so every project listing this footage sees the new name.
|
||||
footage.refresh_from_db()
|
||||
self.assertEqual("the long take", footage.label)
|
||||
self.assertEqual("the long take",
|
||||
self.client.get("/api/footage").json()["footage"][0]["label"])
|
||||
|
||||
# Cleared gives back the name it was ingested under rather than nothing.
|
||||
cleared = self.client.patch(f"/api/footage/{footage.id}",
|
||||
json.dumps({"label": ""}),
|
||||
content_type="application/json")
|
||||
self.assertEqual("IMG_8608.MOV", cleared.json()["label"])
|
||||
self.assertEqual(3, Footage.objects.get().frames)
|
||||
|
||||
def test_a_rename_that_names_no_label_is_refused(self):
|
||||
footage = self.ingest(self.bundle())
|
||||
refused = self.client.patch(f"/api/footage/{footage.id}",
|
||||
json.dumps({"frames": 900}),
|
||||
content_type="application/json")
|
||||
self.assertEqual(400, refused.status_code)
|
||||
self.assertEqual("a rename needs a label", refused.json()["error"])
|
||||
self.assertEqual(3, Footage.objects.get().frames)
|
||||
|
||||
def test_a_sound_is_renamed_without_losing_the_name_it_arrived_as(self):
|
||||
"""`filename` is a fact about the upload and `label` is what a person
|
||||
called it, which is why renaming does not write over the first one."""
|
||||
digest, size = blobs.write_stream([b"RIFF....WAVEfmt "])
|
||||
blob = Blob.objects.create(digest=digest, size=size, media_type="audio/wav")
|
||||
sound = Sound.objects.create(blob=blob, filename="rec0012.wav", duration=2.5)
|
||||
|
||||
renamed = self.client.patch(f"/api/sounds/{sound.id}",
|
||||
json.dumps({"label": "arthur, line 4"}),
|
||||
content_type="application/json")
|
||||
self.assertEqual(200, renamed.status_code, renamed.content)
|
||||
self.assertEqual("arthur, line 4", renamed.json()["label"])
|
||||
self.assertEqual("rec0012.wav", renamed.json()["filename"])
|
||||
sound.refresh_from_db()
|
||||
self.assertEqual("rec0012.wav", sound.filename)
|
||||
self.assertEqual("arthur, line 4", sound.label)
|
||||
|
||||
self.client.patch(f"/api/sounds/{sound.id}", json.dumps({"label": " "}),
|
||||
content_type="application/json")
|
||||
self.assertEqual("rec0012.wav",
|
||||
self.client.get(f"/api/sounds/{sound.id}").json()["label"])
|
||||
|
||||
def test_the_footage_list_does_not_carry_every_url(self):
|
||||
# A list of takes should not be a list of six hundred URLs each.
|
||||
self.ingest(self.bundle())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue