Add video upload, extraction progress, and reusable analysis sources
This commit is contained in:
parent
690de21fa4
commit
686f897401
24 changed files with 927 additions and 137 deletions
|
|
@ -88,4 +88,4 @@
|
|||
(is (= [3 4] (vec (array-seq (wire/typed "int16" (wire/base64 view))))))))
|
||||
|
||||
(deftest an-unknown-element-type-is-refused
|
||||
(is (thrown-with-msg? ExceptionInfo #"int16" (wire/typed "float64" "AA=="))))
|
||||
(is (thrown-with-msg? ExceptionInfo #"int16" (wire/typed "float16" "AA=="))))
|
||||
|
|
|
|||
|
|
@ -119,9 +119,13 @@
|
|||
;; the thing it is derived FROM is the thing the biconditional above asserts.
|
||||
;; These two assertions are what keep the derivation honest in the only two ways
|
||||
;; it could rot.
|
||||
(testing "every role in the table is reachable from some knob"
|
||||
(is (= (set (keys address/block-knobs))
|
||||
(testing "every knob-dependent role is reachable from some knob"
|
||||
(is (= (into #{} (keep (fn [[role knobs]] (when (seq knobs) role)))
|
||||
address/block-knobs)
|
||||
(into #{} (mapcat val) address/knob-roles))))
|
||||
(testing "source roles have no knob dependencies"
|
||||
(doseq [role ["source/dense" "source/detected" "source/crops"]]
|
||||
(is (= [] (get address/block-knobs role)))))
|
||||
(testing "every knob a block declares is a knob the registry defines"
|
||||
;; The other direction of the same edge: a typo in `block-knobs` would name a
|
||||
;; setting no slider can move, and `block-descriptor` only checks that the
|
||||
|
|
|
|||
21
frontend/test/arthur/flow/source_test.cljs
Normal file
21
frontend/test/arthur/flow/source_test.cljs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
(ns arthur.flow.source-test
|
||||
(:require [cljs.test :refer [deftest is]]
|
||||
[arthur.flow.source :as source]))
|
||||
|
||||
(deftest source-blocks-round-trip-without-source-images
|
||||
(let [face (vec (repeat 478 {:x 0.25 :y 0.5 :z -0.125}))
|
||||
pixels (js/Uint8ClampedArray. #js [12 24 36 255])
|
||||
input {:dense [face face] :detected [true false]
|
||||
:crops [{:box {:x 3 :y 4 :w 1 :h 1} :data pixels} nil]}
|
||||
blocks (source/pack "sha256:analysis" input)
|
||||
out (source/unpack (source/wire-blocks blocks) [100 80])]
|
||||
(is (= (set source/roles) (set (keys blocks))))
|
||||
(is (= (:dense input) (:dense out)))
|
||||
(is (= (:detected input) (:detected out)))
|
||||
(is (= [3 4 1 1]
|
||||
((juxt :x :y :w :h) (:box (first (:crops out))))))
|
||||
(is (= [12 24 36 255]
|
||||
(vec (array-seq (:data (first (:crops out)))))))
|
||||
(is (nil? (second (:crops out))))
|
||||
(is (= (mapv :key (vals blocks))
|
||||
(mapv :key (vals (source/pack "sha256:analysis" out)))))))
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
// Writes a PNG per sampled frame into test/browser/out/ so that "it drew
|
||||
// something" can be checked by eye as well as by pixel count.
|
||||
|
||||
import { spawn } from 'node:child_process';
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { mkdtempSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
|
|
@ -356,9 +356,15 @@ async function main() {
|
|||
check(await page.eval(CLICK('save')), 'save is clickable');
|
||||
const saved = await statusMatching(/saved r\d+/);
|
||||
check(saved !== null, 'the document saves', saved ?? (await page.eval(STATUS)));
|
||||
// Eleven blocks the first time. The COUNT is not asserted — that is a fact
|
||||
// about the freeze, not about saving — but that some went up is.
|
||||
check(/· [1-9]\d* blocks?/.test(saved ?? ''), 'and its tier 2 went with it', saved ?? '');
|
||||
// Upload count may be zero when the content-addressed blocks already exist
|
||||
// on this server. The saved clip must still reference them.
|
||||
const savedBlocks = await page.eval(`(async () => {
|
||||
const listed = await fetch('/api/projects').then(r => r.json());
|
||||
const project = await fetch('/api/projects/' + listed.projects[0].id).then(r => r.json());
|
||||
return project.clips[0].blocks.length;
|
||||
})()`);
|
||||
check(savedBlocks > 0, 'the saved clip references its tier 2 blocks',
|
||||
`${savedBlocks} blocks`);
|
||||
|
||||
// Again, unchanged. Content addressing means the second save uploads nothing
|
||||
// and rewrites nothing: this is the assertion that the keys are stable across
|
||||
|
|
@ -387,6 +393,33 @@ async function main() {
|
|||
check(!back[0].selectedClip.includes('take'), 'the picture is the reopened document',
|
||||
JSON.stringify(back[0].selectedClip));
|
||||
|
||||
// Exercise the actual file input and FormData path, then wait for the
|
||||
// extraction job's footage to appear in the server list.
|
||||
const uploadDir = mkdtempSync(join(tmpdir(), 'arthur-upload-'));
|
||||
try {
|
||||
const video = join(uploadDir, 'browser-upload.mp4');
|
||||
const encoded = spawnSync('ffmpeg', [
|
||||
'-hide_banner', '-loglevel', 'error', '-y', '-f', 'lavfi',
|
||||
'-i', 'color=c=blue:s=64x48:r=4:d=1', '-c:v', 'mpeg4', video,
|
||||
]);
|
||||
if (encoded.status !== 0) throw new Error(`ffmpeg failed: ${encoded.stderr}`);
|
||||
const { root } = await page.send('DOM.getDocument');
|
||||
const { nodeId } = await page.send('DOM.querySelector', {
|
||||
nodeId: root.nodeId, selector: 'input[type=file]',
|
||||
});
|
||||
await page.send('DOM.setFileInputFiles', { files: [video], nodeId });
|
||||
const extracted = await statusMatching(/video extracted/, 160);
|
||||
check(extracted !== null, 'the page uploads and extracts a video',
|
||||
extracted ?? (await page.eval(STATUS)));
|
||||
const uploadedFootage = await page.eval(`(async () => {
|
||||
const listed = await fetch('/api/footage').then(r => r.json());
|
||||
return listed.footage.some(f => f.label === 'browser-upload.mp4' && f.frames === 4);
|
||||
})()`);
|
||||
check(uploadedFootage, 'the uploaded footage is selectable');
|
||||
} finally {
|
||||
rmSync(uploadDir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
check(page.logs.length === 0, 'no errors on the console',
|
||||
page.logs.slice(0, 3).join(' | '));
|
||||
} finally {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue