Add video upload, extraction progress, and reusable analysis sources

This commit is contained in:
Olive Vaughn 2026-09-28 09:38:49 -04:00
parent 690de21fa4
commit 686f897401
24 changed files with 927 additions and 137 deletions

View file

@ -16,11 +16,11 @@ modern conveniences belong in the workflow, not the output. See
## ClojureScript port
The active port has reached [step 9 of the port plan](docs/port-plan.md): it plays
the synthetic take, loads real footage with mouth, eyes, brows and pixel-derived
teeth, and now has a Django backend that persists the document. The step 8 data
model represents persistent feature IDs, eye pairs and feature-level observation
gaps; its controls are still pending.
The active port plays the synthetic take, accepts video uploads, extracts their
frames and audio, analyzes real footage for mouth, eyes, brows and pixel-derived
teeth, and saves the project with reusable analysis data. The step 8 data model
represents persistent feature IDs, eye pairs and feature-level observation gaps;
its controls are still pending. See the [port plan](docs/port-plan.md).
```sh
mise install # both halves
@ -29,9 +29,30 @@ mise exec -- python manage.py migrate
./do start # Django + frontend watcher
```
See [frontend/README.md](frontend/README.md) for the **load frames** and **save**
workflows. Anything under "## Run" and below describes the older JS prototype,
which still runs separately on port 8777.
In the app, upload a video, choose its footage, click **load frames**, then
**save**. Opening that project on another client reuses its saved landmarks and
mouth crops without detecting source frames again. The upload path derives its
footage response from database records; it does not create or consume a
`manifest.json` file. See [frontend/README.md](frontend/README.md) for details.
Anything under "## Run" and below describes the older JS prototype, which still
runs separately on port 8777.
### Deploy to Fly.io
The app uses a persistent Fly volume for its SQLite document database and
content-addressed footage blobs. Create the app once, set its Django secret, and
deploy from the repository root:
```sh
fly apps create arthur --org personal
fly volumes create data --region iad --size 1
fly secrets set DJANGO_SECRET_KEY="$(openssl rand -hex 32)" --app arthur
fly deploy --app arthur
```
The deployed app is at <https://arthur.fly.dev>. The container builds the
ClojureScript frontend, collects static assets, and runs database migrations on
startup.
### How it is stored
@ -41,8 +62,8 @@ Three tiers, cut by mutability and size — the full argument is in
| Tier | What | Where |
| --- | --- | --- |
| 1 **authored** | the scene: nodes, channels, features, time maps | the database, as independently addressed leaves. Kilobytes |
| 2 **derived** | the dense channel blocks | `var/blobs`, content-addressed by a hash over every input — including the detector version |
| 3 **source** | frames and audio | the same blob store, by the hash of their bytes |
| 2 **derived** | detected landmarks, raw mouth crops, and dense channel blocks | `var/blobs`, addressed by analysis and block inputs, including the detector version |
| 3 **source** | uploaded video, extracted frames, and audio | the same blob store, by the hash of their bytes |
Only tier 1 is the document. Tier 2 is a pure function of tiers 1 and 3, so a
saved project names its blocks rather than carrying them, and a knob change gives