Files
bitvid/tests/video-probe.test.mjs
T
thePR0M3TH3ANandClaude Opus 4.8 115521928c feat(video): capture & persist dimensions/duration at upload (enables shorts)
Auto-captures a video's width/height/duration at file-select and persists them on
the 30078 — the durable foundation for a future "shorts" vertical feed (filter on
height > width) and for the NIP-71 mirror's short-kind selection.

- js/utils/videoProbe.js: probeVideoMetadata(src) loads metadata into a hidden
  <video> (element injectable for tests); resolves {width,height,duration} or null.
- UploadModal: probes the selected file (objectURL) in the background and threads
  width/height/duration into the publish payload.
- videoPayloadBuilder: persists width/height/duration in content (publish) and
  preserves them across edits; normalizers exported.
- convertEventToVideo: exposes video.width/height/duration → the mirror builder
  already uses them (portrait ⇒ 34236 short + imeta dim).

tests: probe resolution (incl portrait + error/timeout → null); content persist +
parse; portrait⇒34236 / landscape⇒34235 mirror selection; edit preserves dims.
Mutation-verified earlier on the mirror short-selection.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 11:44:02 -04:00

60 lines
2.0 KiB
JavaScript

// probeVideoMetadata resolves a video's dimensions/duration from a hidden element,
// or null on error/timeout. Uses an injected fake element so it's deterministic.
import assert from "node:assert/strict";
import test from "node:test";
import { probeVideoMetadata } from "../js/utils/videoProbe.js";
// Minimal fake <video>: fires the named event on the next tick when src is set.
function fakeVideo({ fire = "loadedmetadata", videoWidth = 0, videoHeight = 0, duration = 0 } = {}) {
const handlers = {};
return {
videoWidth,
videoHeight,
duration,
addEventListener(name, h) {
handlers[name] = h;
},
removeAttribute() {},
load() {},
set src(_v) {
setTimeout(() => handlers[fire]?.(), 0);
},
};
}
test("resolves width/height/duration from loadedmetadata", async () => {
const result = await probeVideoMetadata("blob:abc", {
createVideoEl: () => fakeVideo({ videoWidth: 1080, videoHeight: 1920, duration: 12.3 }),
});
assert.deepEqual(result, { width: 1080, height: 1920, duration: 12.3 });
});
test("a portrait probe yields height > width (drives short selection)", async () => {
const r = await probeVideoMetadata("blob:abc", {
createVideoEl: () => fakeVideo({ videoWidth: 720, videoHeight: 1280 }),
});
assert.ok(r.height > r.width);
assert.equal(r.duration, 0, "missing duration normalizes to 0");
});
test("resolves null on a media error", async () => {
const r = await probeVideoMetadata("blob:bad", {
createVideoEl: () => fakeVideo({ fire: "error" }),
});
assert.equal(r, null);
});
test("resolves null for an empty source and times out gracefully", async () => {
assert.equal(await probeVideoMetadata("", { createVideoEl: () => fakeVideo() }), null);
// never fires any event -> timeout path
const neverFires = {
addEventListener() {},
removeAttribute() {},
load() {},
set src(_v) {},
};
const r = await probeVideoMetadata("blob:x", { createVideoEl: () => neverFires, timeoutMs: 20 });
assert.equal(r, null);
});