Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138YqDj3ttfayRwuMKKrqVj
266 lines
8.0 KiB
TypeScript
266 lines
8.0 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import type { MediaSourceItem } from "@rakazo/adapter-kit";
|
|
import type { BrandKit, TeaserWeights } from "@rakazo/contracts";
|
|
import { TEASER_FEATURES } from "@rakazo/contracts";
|
|
import { bootstrapUserSpace } from "./bootstrap-user.js";
|
|
import type { PrismaClient } from "./client.js";
|
|
import { createDb } from "./client.js";
|
|
import type { MediaRepos } from "./media.js";
|
|
|
|
/*
|
|
* Synthetic fixtures for the Media Studio Postgres tests. Nothing here describes a
|
|
* real person, place, or file.
|
|
*/
|
|
|
|
const signupEnv = { signupsEnabled: "true", signupAllowlist: "" };
|
|
|
|
export interface MediaTestDb {
|
|
prisma: PrismaClient;
|
|
newUser(): Promise<{ userId: string; spaceId: string }>;
|
|
cleanup(): Promise<void>;
|
|
}
|
|
|
|
export function openMediaTestDb(databaseUrl: string): MediaTestDb {
|
|
const db = createDb(databaseUrl, { poolMax: 8 });
|
|
const userIds: string[] = [];
|
|
return {
|
|
prisma: db.prisma,
|
|
async newUser() {
|
|
// bootstrapUserSpace derives the organization slug from the first 12 characters.
|
|
const id = `md${randomUUID().replaceAll("-", "")}`;
|
|
userIds.push(id);
|
|
await db.prisma.user.create({ data: { id, name: "Test", email: `${id}@rakazo.test` } });
|
|
const { spaceId } = await bootstrapUserSpace(db.prisma, { id }, signupEnv, {
|
|
claimDeploymentOwner: false,
|
|
});
|
|
return { userId: id, spaceId };
|
|
},
|
|
async cleanup() {
|
|
const memberships = await db.prisma.member.findMany({
|
|
where: { userId: { in: userIds } },
|
|
select: { organizationId: true },
|
|
});
|
|
await db.prisma.organization.deleteMany({
|
|
where: { id: { in: memberships.map((member) => member.organizationId) } },
|
|
});
|
|
await db.prisma.user.deleteMany({ where: { id: { in: userIds } } });
|
|
await db.prisma.$disconnect();
|
|
await db.pool.end();
|
|
},
|
|
};
|
|
}
|
|
|
|
export const BASE_TIME = Date.UTC(2026, 0, 1, 8, 0, 0);
|
|
|
|
export function sourceItem(
|
|
index: number,
|
|
overrides: Partial<MediaSourceItem> = {},
|
|
): MediaSourceItem {
|
|
return {
|
|
externalId: `clip-${String(index).padStart(5, "0")}.mp4`,
|
|
kind: index % 3 === 0 ? "photo" : "video",
|
|
capturedAt: new Date(BASE_TIME + index * 60_000),
|
|
durationMs: index % 3 === 0 ? null : 10_000,
|
|
width: 1920,
|
|
height: 1080,
|
|
fps: 30,
|
|
gpsLat: null,
|
|
gpsLon: null,
|
|
deviceModel: "Test Camera",
|
|
albumNames: [],
|
|
isFavorite: false,
|
|
fileName: `clip-${index}.mp4`,
|
|
sizeBytes: 1000 + index,
|
|
revision: "r1",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function testEdl(assetId: string, profileId = "example-short-vertical") {
|
|
return {
|
|
edlVersion: 1,
|
|
output: {
|
|
profileId,
|
|
width: 1080,
|
|
height: 1920,
|
|
fps: 30,
|
|
durationTargetMs: 5000,
|
|
durationHardMinMs: 1000,
|
|
durationHardMaxMs: 60_000,
|
|
},
|
|
tracks: {
|
|
video: [{ id: "c1", assetId, inMs: 0, outMs: 5000, atMs: 0, reframe: "auto" }],
|
|
},
|
|
metadata: {},
|
|
};
|
|
}
|
|
|
|
export const testBrandKit: BrandKit = {
|
|
name: "Test Kit",
|
|
colors: {
|
|
primary: "#101010",
|
|
secondary: "#202020",
|
|
accent: "#303030",
|
|
text: "#f0f0f0",
|
|
background: "#000000",
|
|
},
|
|
fonts: [],
|
|
logoArtifactIds: [],
|
|
captionPresets: [],
|
|
musicBeds: [],
|
|
};
|
|
|
|
export const testTeaserWeights = Object.fromEntries(
|
|
TEASER_FEATURES.map((feature) => [feature, 1]),
|
|
) as TeaserWeights;
|
|
|
|
export function vectorBytes(values: number[]): Uint8Array {
|
|
return new Uint8Array(new Float32Array(values).buffer);
|
|
}
|
|
|
|
/** One of every media record in a space, linked the way the product links them. */
|
|
export async function seedMediaSpace(media: MediaRepos, spaceId: string, userId: string) {
|
|
const source = await media.createSource(spaceId, {
|
|
kind: "folder",
|
|
label: "Footage",
|
|
ownerUserId: userId,
|
|
config: { path: "/media/footage" },
|
|
});
|
|
const synced = await media.upsertSourceItems(spaceId, source.id, [
|
|
sourceItem(1, { albumNames: ["Holiday"] }),
|
|
sourceItem(2),
|
|
]);
|
|
const [assetId, secondAssetId] = synced.changedAssetIds as [string, string];
|
|
const analysis = await media.upsertAnalysis(spaceId, assetId, {
|
|
kind: "embeddings",
|
|
modelId: "test-embedder",
|
|
modelVersion: "1",
|
|
vectors: vectorBytes([1, 0, 0]),
|
|
});
|
|
await media.replaceSegments(spaceId, assetId, 1, [
|
|
{ kind: "shot", startMs: 0, endMs: 4000, labels: ["beach"], transcriptExcerpt: "hello" },
|
|
]);
|
|
const segments = await media.listSegments(spaceId, [assetId]);
|
|
const tracks = await media.replaceFaceTracks(spaceId, assetId, "faces-1", [
|
|
{
|
|
startMs: 0,
|
|
endMs: 3000,
|
|
confidenceMin: 0.9,
|
|
keyframesPath: `faces/${assetId}/t1.json`,
|
|
embedding: vectorBytes([0.1, 0.2, 0.3]),
|
|
},
|
|
]);
|
|
const [person] = await media.createPersonSuggestions(spaceId, [
|
|
{ centroid: vectorBytes([0.1, 0.2, 0.3]), trackIds: tracks.map((track) => track.id) },
|
|
]);
|
|
const mark = await media.createMark(spaceId, {
|
|
kind: "tattoo",
|
|
label: "arm",
|
|
personId: person?.id ?? null,
|
|
assetId,
|
|
prompt: { tMs: 1000, point: { x: 0.5, y: 0.5 } },
|
|
treatment: "blur",
|
|
});
|
|
await media.replaceClusters(spaceId, "session", [
|
|
{
|
|
key: "s1",
|
|
title: "Morning",
|
|
labelSource: "rule",
|
|
startAt: new Date(BASE_TIME),
|
|
endAt: new Date(BASE_TIME + 120_000),
|
|
centroid: null,
|
|
coverAssetId: assetId,
|
|
assetIds: [assetId, secondAssetId],
|
|
},
|
|
]);
|
|
const collection = (await media.listCollections(spaceId, {})).items[0];
|
|
const brandKit = await media.upsertBrandKit(spaceId, { kit: testBrandKit });
|
|
const project = await media.createProject(spaceId, userId, {
|
|
title: "Teaser",
|
|
goal: "teaser",
|
|
profileIds: ["example-short-vertical"],
|
|
collectionIds: collection ? [collection.id] : [],
|
|
brandKitId: brandKit.id,
|
|
});
|
|
const plan = await media.createPlanVersion(spaceId, {
|
|
projectId: project.id,
|
|
profileId: "example-short-vertical",
|
|
edl: testEdl(assetId),
|
|
createdBy: "agent",
|
|
rationale: null,
|
|
});
|
|
const render = await media.createRender(spaceId, {
|
|
planId: plan.id,
|
|
quality: "draft",
|
|
createdBy: userId,
|
|
});
|
|
await media.claimRender(spaceId, render.id);
|
|
await media.updateRender(spaceId, render.id, { status: "done", progress: 1 });
|
|
const queuedRender = await media.createRender(spaceId, {
|
|
planId: plan.id,
|
|
quality: "final",
|
|
createdBy: userId,
|
|
});
|
|
const publication = await media.createPublication(spaceId, userId, {
|
|
renderId: render.id,
|
|
destination: "bundle",
|
|
targetUrl: "https://example.com/offer",
|
|
trackingUrl: "https://example.com/offer?utm_content=a",
|
|
utm: { source: "shorts", medium: "video", campaign: "launch", content: "a" },
|
|
title: "Title",
|
|
caption: "Caption",
|
|
tags: ["test"],
|
|
thumbnailVariant: "t1",
|
|
scheduledAt: null,
|
|
});
|
|
const metric = await media.recordMetric(spaceId, {
|
|
publicationId: publication.id,
|
|
observedAt: new Date(BASE_TIME).toISOString(),
|
|
views: 100,
|
|
clicks: 10,
|
|
conversions: 1,
|
|
revenueCents: 999,
|
|
currency: "EUR",
|
|
source: "manual",
|
|
});
|
|
const mapping = await media.saveMetricMapping(spaceId, {
|
|
name: "Default",
|
|
delimiter: ",",
|
|
columns: { publication: "id", observedAt: "date", views: "views" },
|
|
publicationKey: "id",
|
|
revenueUnit: "major",
|
|
defaultCurrency: "EUR",
|
|
});
|
|
await media.putTeaserModel(spaceId, testTeaserWeights, 3);
|
|
const upload = await media.beginUpload(spaceId, {
|
|
assetId: secondAssetId,
|
|
userId,
|
|
sizeBytes: 10,
|
|
sha256: "a".repeat(64),
|
|
fileName: "clip.mp4",
|
|
chunkBytes: 4,
|
|
});
|
|
return {
|
|
sourceId: source.id,
|
|
assetId,
|
|
secondAssetId,
|
|
analysisId: analysis.id,
|
|
segmentId: segments[0]?.id as string,
|
|
trackId: tracks[0]?.id as string,
|
|
personId: person?.id as string,
|
|
markId: mark.id,
|
|
collectionId: collection?.id as string,
|
|
brandKitId: brandKit.id,
|
|
projectId: project.id,
|
|
planId: plan.id,
|
|
renderId: render.id,
|
|
queuedRenderId: queuedRender.id,
|
|
publicationId: publication.id,
|
|
metricId: metric.id,
|
|
mappingId: mapping.id as string,
|
|
uploadId: upload.id,
|
|
};
|
|
}
|
|
|
|
export type SeededMediaSpace = Awaited<ReturnType<typeof seedMediaSpace>>;
|