Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives: publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style column wall with hostile-content-safe rendering, live control room with Auto Director, bridged chat governance with polls and quorum, distributed sandboxed transcode workers, encrypted content-addressed archive, monthly analytics. MIT licensed. See README.md and docs/.
This commit is contained in:
commit
aa5e262f3a
880 changed files with 111042 additions and 0 deletions
46
apps/api/src/video/video.roundtrip.test.ts
Normal file
46
apps/api/src/video/video.roundtrip.test.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { readFile } from "node:fs/promises";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { keyForHash, sha256, seal, open, type StorageService } from "../storage/storage.js";
|
||||
import { probeVideoFile } from "./ffprobe.js";
|
||||
import { serviceWithDatabase, workspaceId } from "./test-fixtures.js";
|
||||
|
||||
// Opt in with a generated/real MP4; the normal suite needs no ffmpeg installation.
|
||||
const fixture = process.env.VIDEO_ROUNDTRIP_FIXTURE;
|
||||
|
||||
describe.skipIf(fixture === undefined)("real ffprobe roundtrip (encrypted in-memory storage)", () => {
|
||||
it("uploads actual MP4 bytes, decrypts the stored source, and persists real probe metadata", async () => {
|
||||
if (fixture === undefined) throw new Error("VIDEO_ROUNDTRIP_FIXTURE is required");
|
||||
const bytes = await readFile(fixture);
|
||||
const objects = new Map<string, Buffer>();
|
||||
const kek = Buffer.alloc(32, 7);
|
||||
const storage: StorageService = {
|
||||
putContent: async (source, contentType) => {
|
||||
const hash = sha256(source);
|
||||
const key = keyForHash(hash, contentType);
|
||||
const deduplicated = objects.has(key);
|
||||
if (!deduplicated) objects.set(key, seal(source, kek));
|
||||
return { key, hash, sizeBytes: source.length, contentType, deduplicated };
|
||||
},
|
||||
getObject: async (key) => {
|
||||
const encrypted = objects.get(key);
|
||||
if (encrypted === undefined) throw new Error("missing object");
|
||||
const source = open(encrypted, kek);
|
||||
expect(key).toBe(keyForHash(sha256(source), "video/mp4"));
|
||||
return source;
|
||||
},
|
||||
hasContent: async (hash, contentType) => objects.has(keyForHash(hash, contentType)),
|
||||
presign: async () => { throw new Error("must decrypt, not presign"); },
|
||||
};
|
||||
const { service } = serviceWithDatabase({}, probeVideoFile, storage);
|
||||
const uploaded = await service.uploadVideoBytes(workspaceId, bytes, "video/mp4");
|
||||
const status = await service.getVideoStatus(uploaded.videoAsset.id, workspaceId);
|
||||
expect(status.processingJob.state).toBe("complete");
|
||||
expect(status.videoAsset).toMatchObject({ durationMs: 1000, width: 320, height: 180, frameRate: 25 });
|
||||
expect(status.videoAsset.codec).toBeDefined();
|
||||
expect(objects.get(status.videoAsset.storageKey)?.equals(bytes)).toBe(false);
|
||||
expect(await storage.getObject(status.videoAsset.storageKey)).toEqual(bytes);
|
||||
const again = await storage.putContent(bytes, "video/mp4");
|
||||
expect(again).toMatchObject({ key: status.videoAsset.storageKey, deduplicated: true });
|
||||
console.info("Real ffprobe status:", JSON.stringify(status));
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue