feat(media): contracts and foundation for Media Studio
Freeze the shared contracts every Media Studio slice builds on: zod DTOs, EDL, platform profile, brand kit, gate and render plan schemas; the media RPC surface with a scaffold router; provider interfaces for sources, inference, rendering, motion graphics and publishing; media background jobs; the Prisma models and migration; and the @rakazo/media domain package signatures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138YqDj3ttfayRwuMKKrqVj
This commit is contained in:
parent
3c3961f242
commit
a663764ee8
@ -236,3 +236,10 @@ EVOO_COCKPIT_CORS_ORIGIN=
|
|||||||
# Expo: English or Simplified Chinese (en | zh-CN). Overridden by SecureStore key
|
# Expo: English or Simplified Chinese (en | zh-CN). Overridden by SecureStore key
|
||||||
# rakazo.uiLocale from Account → Language.
|
# rakazo.uiLocale from Account → Language.
|
||||||
# EXPO_PUBLIC_DEFAULT_UI_LOCALE=en
|
# EXPO_PUBLIC_DEFAULT_UI_LOCALE=en
|
||||||
|
|
||||||
|
# Optional Media Studio worker (local ffmpeg + ML sidecar, compose profile "media").
|
||||||
|
# Without it, Media Studio stays hidden and media jobs are skipped.
|
||||||
|
# MEDIA_WORKER_URL=http://127.0.0.1:8790
|
||||||
|
# MEDIA_WORKER_TOKEN=REPLACE_WITH_RANDOM_TOKEN
|
||||||
|
# Private platform profiles that override the shipped ones. Keep outside the repo.
|
||||||
|
# MEDIA_PROFILES_DIR=~/.evoo/media-profiles
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
"@rakazo/core": "workspace:*",
|
"@rakazo/core": "workspace:*",
|
||||||
"@rakazo/db": "workspace:*",
|
"@rakazo/db": "workspace:*",
|
||||||
"@rakazo/logging": "workspace:*",
|
"@rakazo/logging": "workspace:*",
|
||||||
|
"@rakazo/media": "workspace:*",
|
||||||
"@rakazo/memory": "workspace:*",
|
"@rakazo/memory": "workspace:*",
|
||||||
"hono": "4.13.7"
|
"hono": "4.13.7"
|
||||||
},
|
},
|
||||||
|
|||||||
113
apps/api/src/media.ts
Normal file
113
apps/api/src/media.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { implement, ORPCError } from "@orpc/server";
|
||||||
|
import type { Actor } from "@rakazo/contracts";
|
||||||
|
import { appContract } from "@rakazo/contracts";
|
||||||
|
import type { PrismaClient } from "@rakazo/db";
|
||||||
|
|
||||||
|
export interface MediaRouterDeps {
|
||||||
|
prisma: PrismaClient;
|
||||||
|
dataDir: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase A scaffold: every procedure is replaced by its real implementation.
|
||||||
|
function notImplemented(): never {
|
||||||
|
throw new ORPCError("NOT_IMPLEMENTED", { message: "Media Studio is not available yet" });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createMediaRouter(_deps: MediaRouterDeps) {
|
||||||
|
const os = implement(appContract.media).$context<{ actor: Actor | null; signal?: AbortSignal }>();
|
||||||
|
const authed = os.use(async ({ context, next }) => {
|
||||||
|
if (!context.actor) throw new ORPCError("UNAUTHORIZED");
|
||||||
|
return next({ context: { ...context, actor: context.actor } });
|
||||||
|
});
|
||||||
|
|
||||||
|
return os.router({
|
||||||
|
capabilities: authed.capabilities.handler(notImplemented),
|
||||||
|
sources: {
|
||||||
|
list: authed.sources.list.handler(notImplemented),
|
||||||
|
createFolder: authed.sources.createFolder.handler(notImplemented),
|
||||||
|
registerClient: authed.sources.registerClient.handler(notImplemented),
|
||||||
|
pushItems: authed.sources.pushItems.handler(notImplemented),
|
||||||
|
sync: authed.sources.sync.handler(notImplemented),
|
||||||
|
remove: authed.sources.remove.handler(notImplemented),
|
||||||
|
},
|
||||||
|
assets: {
|
||||||
|
search: authed.assets.search.handler(notImplemented),
|
||||||
|
get: authed.assets.get.handler(notImplemented),
|
||||||
|
setPaidOnly: authed.assets.setPaidOnly.handler(notImplemented),
|
||||||
|
requestUpload: authed.assets.requestUpload.handler(notImplemented),
|
||||||
|
},
|
||||||
|
uploads: {
|
||||||
|
begin: authed.uploads.begin.handler(notImplemented),
|
||||||
|
status: authed.uploads.status.handler(notImplemented),
|
||||||
|
complete: authed.uploads.complete.handler(notImplemented),
|
||||||
|
},
|
||||||
|
collections: {
|
||||||
|
list: authed.collections.list.handler(notImplemented),
|
||||||
|
get: authed.collections.get.handler(notImplemented),
|
||||||
|
rename: authed.collections.rename.handler(notImplemented),
|
||||||
|
merge: authed.collections.merge.handler(notImplemented),
|
||||||
|
split: authed.collections.split.handler(notImplemented),
|
||||||
|
createManual: authed.collections.createManual.handler(notImplemented),
|
||||||
|
recluster: authed.collections.recluster.handler(notImplemented),
|
||||||
|
},
|
||||||
|
people: {
|
||||||
|
list: authed.people.list.handler(notImplemented),
|
||||||
|
update: authed.people.update.handler(notImplemented),
|
||||||
|
merge: authed.people.merge.handler(notImplemented),
|
||||||
|
assignTrack: authed.people.assignTrack.handler(notImplemented),
|
||||||
|
recordConsent: authed.people.recordConsent.handler(notImplemented),
|
||||||
|
remove: authed.people.remove.handler(notImplemented),
|
||||||
|
},
|
||||||
|
marks: {
|
||||||
|
list: authed.marks.list.handler(notImplemented),
|
||||||
|
create: authed.marks.create.handler(notImplemented),
|
||||||
|
update: authed.marks.update.handler(notImplemented),
|
||||||
|
remove: authed.marks.remove.handler(notImplemented),
|
||||||
|
},
|
||||||
|
brandKits: {
|
||||||
|
list: authed.brandKits.list.handler(notImplemented),
|
||||||
|
upsert: authed.brandKits.upsert.handler(notImplemented),
|
||||||
|
remove: authed.brandKits.remove.handler(notImplemented),
|
||||||
|
templates: authed.brandKits.templates.handler(notImplemented),
|
||||||
|
introVariants: authed.brandKits.introVariants.handler(notImplemented),
|
||||||
|
},
|
||||||
|
profiles: {
|
||||||
|
list: authed.profiles.list.handler(notImplemented),
|
||||||
|
get: authed.profiles.get.handler(notImplemented),
|
||||||
|
},
|
||||||
|
projects: {
|
||||||
|
list: authed.projects.list.handler(notImplemented),
|
||||||
|
get: authed.projects.get.handler(notImplemented),
|
||||||
|
create: authed.projects.create.handler(notImplemented),
|
||||||
|
update: authed.projects.update.handler(notImplemented),
|
||||||
|
remove: authed.projects.remove.handler(notImplemented),
|
||||||
|
autoPlan: authed.projects.autoPlan.handler(notImplemented),
|
||||||
|
},
|
||||||
|
plans: {
|
||||||
|
submit: authed.plans.submit.handler(notImplemented),
|
||||||
|
},
|
||||||
|
renders: {
|
||||||
|
start: authed.renders.start.handler(notImplemented),
|
||||||
|
get: authed.renders.get.handler(notImplemented),
|
||||||
|
cancel: authed.renders.cancel.handler(notImplemented),
|
||||||
|
thumbnails: authed.renders.thumbnails.handler(notImplemented),
|
||||||
|
},
|
||||||
|
marketing: {
|
||||||
|
copy: authed.marketing.copy.handler(notImplemented),
|
||||||
|
},
|
||||||
|
publications: {
|
||||||
|
list: authed.publications.list.handler(notImplemented),
|
||||||
|
create: authed.publications.create.handler(notImplemented),
|
||||||
|
bundle: authed.publications.bundle.handler(notImplemented),
|
||||||
|
markPublished: authed.publications.markPublished.handler(notImplemented),
|
||||||
|
cancel: authed.publications.cancel.handler(notImplemented),
|
||||||
|
},
|
||||||
|
metrics: {
|
||||||
|
mappings: authed.metrics.mappings.handler(notImplemented),
|
||||||
|
saveMapping: authed.metrics.saveMapping.handler(notImplemented),
|
||||||
|
importCsv: authed.metrics.importCsv.handler(notImplemented),
|
||||||
|
record: authed.metrics.record.handler(notImplemented),
|
||||||
|
funnel: authed.metrics.funnel.handler(notImplemented),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -166,6 +166,7 @@ import {
|
|||||||
renameMainConversation,
|
renameMainConversation,
|
||||||
} from "./main-conversation.js";
|
} from "./main-conversation.js";
|
||||||
import { buildMcpUpdateMaterial } from "./mcp-material.js";
|
import { buildMcpUpdateMaterial } from "./mcp-material.js";
|
||||||
|
import { createMediaRouter } from "./media.js";
|
||||||
import {
|
import {
|
||||||
disconnectMemoryProvider,
|
disconnectMemoryProvider,
|
||||||
getMemoryProviderConfig,
|
getMemoryProviderConfig,
|
||||||
@ -526,6 +527,7 @@ export function createRouter(deps: RouterDeps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return os.router({
|
return os.router({
|
||||||
|
media: createMediaRouter({ prisma: deps.prisma, dataDir: deps.dataDir }),
|
||||||
aiConsent: {
|
aiConsent: {
|
||||||
status: authed.aiConsent.status.handler(({ context, input }) =>
|
status: authed.aiConsent.status.handler(({ context, input }) =>
|
||||||
aiConsentStatus(deps, context.actor, input),
|
aiConsentStatus(deps, context.actor, input),
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
"expo-image-picker": "57.0.20",
|
"expo-image-picker": "57.0.20",
|
||||||
"expo-linking": "57.0.11",
|
"expo-linking": "57.0.11",
|
||||||
"expo-localization": "57.0.2",
|
"expo-localization": "57.0.2",
|
||||||
|
"expo-media-library": "57.0.5",
|
||||||
"expo-modules-core": "57.0.20",
|
"expo-modules-core": "57.0.20",
|
||||||
"expo-notifications": "57.0.21",
|
"expo-notifications": "57.0.21",
|
||||||
"expo-router": "57.0.24",
|
"expo-router": "57.0.24",
|
||||||
@ -44,6 +45,7 @@
|
|||||||
"expo-status-bar": "57.0.1",
|
"expo-status-bar": "57.0.1",
|
||||||
"expo-symbols": "57.0.3",
|
"expo-symbols": "57.0.3",
|
||||||
"expo-updates": "57.0.24",
|
"expo-updates": "57.0.24",
|
||||||
|
"expo-video": "57.0.5",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "19.2.3",
|
||||||
"react-native": "0.86.3",
|
"react-native": "0.86.3",
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
"@rakazo/core": "workspace:*",
|
"@rakazo/core": "workspace:*",
|
||||||
"@rakazo/ui-tokens": "workspace:*",
|
"@rakazo/ui-tokens": "workspace:*",
|
||||||
"@rakazo/ui-web": "workspace:*",
|
"@rakazo/ui-web": "workspace:*",
|
||||||
|
"@tanstack/react-virtual": "3.14.13",
|
||||||
"better-auth": "1.7.4",
|
"better-auth": "1.7.4",
|
||||||
"lucide-react": "1.44.0",
|
"lucide-react": "1.44.0",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
|
|||||||
@ -22,6 +22,12 @@ function handlers(): BackgroundJobHandlers {
|
|||||||
"messaging.deliver": vi.fn(async () => undefined),
|
"messaging.deliver": vi.fn(async () => undefined),
|
||||||
"cloud_agent.poll": vi.fn(async () => undefined),
|
"cloud_agent.poll": vi.fn(async () => undefined),
|
||||||
"profile.consolidate": vi.fn(async () => undefined),
|
"profile.consolidate": vi.fn(async () => undefined),
|
||||||
|
"media.sync": vi.fn(async () => undefined),
|
||||||
|
"media.proxy": vi.fn(async () => undefined),
|
||||||
|
"media.analyze": vi.fn(async () => undefined),
|
||||||
|
"media.cluster": vi.fn(async () => undefined),
|
||||||
|
"media.render": vi.fn(async () => undefined),
|
||||||
|
"media.publish": vi.fn(async () => undefined),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import type { MediaAnalysisKind } from "@rakazo/contracts";
|
||||||
|
import { MediaAnalysisKindSchema } from "@rakazo/contracts";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import type {
|
import type {
|
||||||
BackgroundJob,
|
BackgroundJob,
|
||||||
@ -23,6 +25,15 @@ const payloadSchemas = {
|
|||||||
"messaging.deliver": z.object({ runId: z.string().min(1).optional() }),
|
"messaging.deliver": z.object({ runId: z.string().min(1).optional() }),
|
||||||
"cloud_agent.poll": z.object({ agentId: z.string().min(1) }),
|
"cloud_agent.poll": z.object({ agentId: z.string().min(1) }),
|
||||||
"profile.consolidate": z.object({ userId: z.string().min(1) }),
|
"profile.consolidate": z.object({ userId: z.string().min(1) }),
|
||||||
|
"media.sync": z.object({ sourceId: z.string().min(1) }),
|
||||||
|
"media.proxy": z.object({ assetId: z.string().min(1) }),
|
||||||
|
"media.analyze": z.object({
|
||||||
|
assetId: z.string().min(1),
|
||||||
|
kinds: z.array(MediaAnalysisKindSchema).optional(),
|
||||||
|
}),
|
||||||
|
"media.cluster": z.object({ spaceId: z.string().min(1) }),
|
||||||
|
"media.render": z.object({ renderId: z.string().min(1) }),
|
||||||
|
"media.publish": z.object({ publicationId: z.string().min(1) }),
|
||||||
} satisfies { [Name in BackgroundJobName]: z.ZodType<BackgroundJobPayloads[Name]> };
|
} satisfies { [Name in BackgroundJobName]: z.ZodType<BackgroundJobPayloads[Name]> };
|
||||||
|
|
||||||
export function parseBackgroundJob(name: string, payload: unknown): BackgroundJob {
|
export function parseBackgroundJob(name: string, payload: unknown): BackgroundJob {
|
||||||
@ -160,3 +171,41 @@ export function profileConsolidateJob(userId: string): BackgroundJob {
|
|||||||
replaceKey: profileConsolidateJobKey(userId),
|
replaceKey: profileConsolidateJobKey(userId),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mediaSyncJob(sourceId: string): BackgroundJob {
|
||||||
|
return { name: "media.sync", payload: { sourceId }, replaceKey: `media.sync:${sourceId}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mediaProxyJob(assetId: string): BackgroundJob {
|
||||||
|
return { name: "media.proxy", payload: { assetId }, replaceKey: `media.proxy:${assetId}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mediaAnalyzeJob(assetId: string, kinds?: MediaAnalysisKind[]): BackgroundJob {
|
||||||
|
return {
|
||||||
|
name: "media.analyze",
|
||||||
|
payload: kinds ? { assetId, kinds } : { assetId },
|
||||||
|
replaceKey: `media.analyze:${assetId}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mediaClusterJob(spaceId: string, availableAt?: Date): BackgroundJob {
|
||||||
|
return {
|
||||||
|
name: "media.cluster",
|
||||||
|
payload: { spaceId },
|
||||||
|
replaceKey: `media.cluster:${spaceId}`,
|
||||||
|
...(availableAt ? { availableAt } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mediaRenderJob(renderId: string): BackgroundJob {
|
||||||
|
return { name: "media.render", payload: { renderId }, replaceKey: `media.render:${renderId}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mediaPublishJob(publicationId: string, availableAt?: Date): BackgroundJob {
|
||||||
|
return {
|
||||||
|
name: "media.publish",
|
||||||
|
payload: { publicationId },
|
||||||
|
replaceKey: `media.publish:${publicationId}`,
|
||||||
|
...(availableAt ? { availableAt } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
export * from "./background-jobs.js";
|
export * from "./background-jobs.js";
|
||||||
export * from "./cloud-agents.js";
|
export * from "./cloud-agents.js";
|
||||||
export * from "./interfaces.js";
|
export * from "./interfaces.js";
|
||||||
|
export * from "./media.js";
|
||||||
export * from "./registry.js";
|
export * from "./registry.js";
|
||||||
export * from "./types.js";
|
export * from "./types.js";
|
||||||
|
|||||||
325
packages/adapter-kit/src/media.ts
Normal file
325
packages/adapter-kit/src/media.ts
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
import type {
|
||||||
|
AnonymizeMode,
|
||||||
|
BoxKeyframe,
|
||||||
|
BrandKit,
|
||||||
|
ContentCategory,
|
||||||
|
FfprobeReport,
|
||||||
|
MediaAssetKind,
|
||||||
|
MediaCapabilities,
|
||||||
|
MediaInferenceOperation,
|
||||||
|
MediaMarkPrompt,
|
||||||
|
MediaSourceKind,
|
||||||
|
MotionTemplateInfo,
|
||||||
|
MotionTemplateRef,
|
||||||
|
NormalizedBox,
|
||||||
|
NormalizedPoint,
|
||||||
|
PlatformProfile,
|
||||||
|
RenderPlan,
|
||||||
|
} from "@rakazo/contracts";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Media Studio provider boundaries. Every implementation (local sidecar,
|
||||||
|
* emulator, optional cloud adapters) satisfies the same contracts, which
|
||||||
|
* media-conformance tests exercise offline.
|
||||||
|
*
|
||||||
|
* Paths are absolute host paths inside the configured media root. Providers
|
||||||
|
* must reject any path outside that root.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Sources
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface MediaSourceItem {
|
||||||
|
externalId: string;
|
||||||
|
kind: MediaAssetKind;
|
||||||
|
capturedAt: Date | null;
|
||||||
|
durationMs: number | null;
|
||||||
|
width: number | null;
|
||||||
|
height: number | null;
|
||||||
|
fps: number | null;
|
||||||
|
gpsLat: number | null;
|
||||||
|
gpsLon: number | null;
|
||||||
|
deviceModel: string | null;
|
||||||
|
albumNames: string[];
|
||||||
|
isFavorite: boolean;
|
||||||
|
fileName: string | null;
|
||||||
|
sizeBytes: number | null;
|
||||||
|
/** Cheap change detector (mtime + size or provider revision); not a content hash. */
|
||||||
|
revision: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaSourceListRequest {
|
||||||
|
config: Record<string, unknown>;
|
||||||
|
cursor: string | null;
|
||||||
|
limit: number;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaSourceListResult {
|
||||||
|
items: MediaSourceItem[];
|
||||||
|
nextCursor: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Server-side sources the worker can enumerate and read directly. */
|
||||||
|
export interface MediaSourceProvider {
|
||||||
|
readonly kind: Extract<MediaSourceKind, "folder" | "emulator">;
|
||||||
|
list(request: MediaSourceListRequest): Promise<MediaSourceListResult>;
|
||||||
|
/** Absolute readable path of the original file. Throws when it escaped the source root. */
|
||||||
|
resolve(config: Record<string, unknown>, externalId: string): Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Inference
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export class UnsupportedMediaOperationError extends Error {
|
||||||
|
constructor(
|
||||||
|
readonly operation: MediaInferenceOperation,
|
||||||
|
readonly reason: string,
|
||||||
|
) {
|
||||||
|
super(`${operation} is not available: ${reason}`);
|
||||||
|
this.name = "UnsupportedMediaOperationError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaTimeSpan {
|
||||||
|
startMs: number;
|
||||||
|
endMs: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DetectScenesResult {
|
||||||
|
shots: Array<MediaTimeSpan & { score: number }>;
|
||||||
|
scenes: MediaTimeSpan[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TranscriptWord {
|
||||||
|
startMs: number;
|
||||||
|
endMs: number;
|
||||||
|
text: string;
|
||||||
|
confidence: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TranscribeResult {
|
||||||
|
language: string | null;
|
||||||
|
words: TranscriptWord[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FaceObservation {
|
||||||
|
box: NormalizedBox;
|
||||||
|
confidence: number;
|
||||||
|
/** Five-point landmarks: eyes, nose, mouth corners. */
|
||||||
|
landmarks?: NormalizedPoint[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DetectFacesResult {
|
||||||
|
frames: Array<{ tMs: number; faces: FaceObservation[] }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FaceTrackResult {
|
||||||
|
trackId: string;
|
||||||
|
keyframes: Array<BoxKeyframe & { confidence: number }>;
|
||||||
|
/** Identity embedding for local person clustering. Never leaves the host. */
|
||||||
|
embedding: number[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrackFacesResult {
|
||||||
|
sampleFps: number;
|
||||||
|
tracks: FaceTrackResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FaceLandmarksResult {
|
||||||
|
frames: Array<{
|
||||||
|
tMs: number;
|
||||||
|
faces: Array<{ box: NormalizedBox; landmarks: NormalizedPoint[] }>;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmbedFramesResult {
|
||||||
|
model: string;
|
||||||
|
dim: number;
|
||||||
|
vectors: number[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClassifyContentResult {
|
||||||
|
model: string;
|
||||||
|
frames: Array<{ tMs: number; scores: Partial<Record<ContentCategory, number>> }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SegmentPromptedResult {
|
||||||
|
/** Directory of per-frame PNG masks named by timestamp in ms. */
|
||||||
|
maskDir: string;
|
||||||
|
keyframes: Array<BoxKeyframe & { confidence: number }>;
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InpaintQuality {
|
||||||
|
/** 1 = no flicker inside the mask region between neighbouring frames. */
|
||||||
|
temporalStability: number;
|
||||||
|
/** 1 = no visible seams at the mask edge. */
|
||||||
|
edgeQuality: number;
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InpaintVideoResult {
|
||||||
|
outPath: string;
|
||||||
|
quality: InpaintQuality;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AudioFeaturesResult {
|
||||||
|
integratedLufs: number | null;
|
||||||
|
truePeakDb: number | null;
|
||||||
|
tempoBpm: number | null;
|
||||||
|
beatsMs: number[];
|
||||||
|
energy: Array<{ tMs: number; value: number }>;
|
||||||
|
silences: MediaTimeSpan[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SaliencyResult {
|
||||||
|
points: Array<{ tMs: number; cx: number; cy: number; strength: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RedactionInstruction {
|
||||||
|
keyframes: BoxKeyframe[];
|
||||||
|
mode: Exclude<AnonymizeMode, "none" | "reframe_out" | "synthetic_identity"> | "cover";
|
||||||
|
paddingPct: number;
|
||||||
|
featherPct: number;
|
||||||
|
/** Solid and cover fill. */
|
||||||
|
color?: string;
|
||||||
|
/** Landmarks per keyframe for partial modes. */
|
||||||
|
landmarks?: Array<{ tMs: number; points: NormalizedPoint[] }>;
|
||||||
|
/** Mask directory for region marks instead of boxes. */
|
||||||
|
maskDir?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MediaPathRequest {
|
||||||
|
path: string;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local-first ML operations. Callers check `capabilities()` and fall back
|
||||||
|
* (blur, cover, reframe) when an operation reports unavailable; providers
|
||||||
|
* throw UnsupportedMediaOperationError instead of guessing.
|
||||||
|
*/
|
||||||
|
export interface MediaInferenceProvider {
|
||||||
|
readonly id: string;
|
||||||
|
capabilities(signal?: AbortSignal): Promise<MediaCapabilities>;
|
||||||
|
detectScenes(request: MediaPathRequest): Promise<DetectScenesResult>;
|
||||||
|
transcribe(request: MediaPathRequest & { language?: string }): Promise<TranscribeResult>;
|
||||||
|
detectFaces(
|
||||||
|
request: MediaPathRequest & { sampleFps: number; timesMs?: number[] },
|
||||||
|
): Promise<DetectFacesResult>;
|
||||||
|
trackFaces(request: MediaPathRequest & { sampleFps: number }): Promise<TrackFacesResult>;
|
||||||
|
faceLandmarks(request: MediaPathRequest & { timesMs: number[] }): Promise<FaceLandmarksResult>;
|
||||||
|
embedFrames(request: MediaPathRequest & { timesMs: number[] }): Promise<EmbedFramesResult>;
|
||||||
|
classifyContent(
|
||||||
|
request: MediaPathRequest & { sampleFps: number; extraTimesMs?: number[] },
|
||||||
|
): Promise<ClassifyContentResult>;
|
||||||
|
segmentPrompted(
|
||||||
|
request: MediaPathRequest & { prompt: MediaMarkPrompt; propagate: boolean; outDir: string },
|
||||||
|
): Promise<SegmentPromptedResult>;
|
||||||
|
inpaintVideo(
|
||||||
|
request: MediaPathRequest & { maskDir: string; outPath: string },
|
||||||
|
): Promise<InpaintVideoResult>;
|
||||||
|
/**
|
||||||
|
* Apply face and region redactions frame-accurately and write a near-lossless
|
||||||
|
* intermediate. Fails closed: a frame it cannot process is an error, not a pass.
|
||||||
|
*/
|
||||||
|
redact(
|
||||||
|
request: MediaPathRequest & { redactions: RedactionInstruction[]; outPath: string },
|
||||||
|
): Promise<{ outPath: string; framesProcessed: number }>;
|
||||||
|
/**
|
||||||
|
* Replace a tracked face with a generated, non-existent identity derived from
|
||||||
|
* `seed`. Deliberately takes no target-face image.
|
||||||
|
*/
|
||||||
|
synthesizeIdentity(
|
||||||
|
request: MediaPathRequest & { track: BoxKeyframe[]; seed: number; outPath: string },
|
||||||
|
): Promise<{ outPath: string }>;
|
||||||
|
audioFeatures(request: MediaPathRequest): Promise<AudioFeaturesResult>;
|
||||||
|
saliency(request: MediaPathRequest & { timesMs: number[] }): Promise<SaliencyResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Rendering
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface MediaRenderProgress {
|
||||||
|
stepId: string;
|
||||||
|
/** Overall progress 0..1. */
|
||||||
|
progress: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaRenderer {
|
||||||
|
readonly id: string;
|
||||||
|
/** Hardware encoders usable on this host, e.g. h264_vaapi, h264_videotoolbox. */
|
||||||
|
hardwareEncoders(): Promise<string[]>;
|
||||||
|
/**
|
||||||
|
* Run every ffmpeg step of the plan. Motion, inpaint, and synthetic identity
|
||||||
|
* steps must already be satisfied (their outPath exists) or the renderer throws.
|
||||||
|
*/
|
||||||
|
render(
|
||||||
|
plan: RenderPlan,
|
||||||
|
options: { signal?: AbortSignal; onProgress?: (progress: MediaRenderProgress) => void },
|
||||||
|
): Promise<void>;
|
||||||
|
probe(
|
||||||
|
path: string,
|
||||||
|
options?: { measureLoudness?: boolean; signal?: AbortSignal },
|
||||||
|
): Promise<FfprobeReport>;
|
||||||
|
/** Extract a still frame as PNG (EXIF-free). */
|
||||||
|
extractFrame(input: {
|
||||||
|
path: string;
|
||||||
|
tMs: number;
|
||||||
|
outPath: string;
|
||||||
|
width?: number;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MotionRenderRequest {
|
||||||
|
template: MotionTemplateRef;
|
||||||
|
brandKit: BrandKit;
|
||||||
|
/** Resolved font and logo files from the brand kit artifacts. */
|
||||||
|
assets: { fonts: Array<{ family: string; path: string }>; logoPaths: string[] };
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
fps: number;
|
||||||
|
outPath: string;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MotionGraphicsRenderer {
|
||||||
|
readonly id: string;
|
||||||
|
templates(): MotionTemplateInfo[];
|
||||||
|
/** Render an overlay clip with alpha (MOV, qtrle or prores 4444). */
|
||||||
|
render(request: MotionRenderRequest): Promise<{ path: string; hasAlpha: boolean }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Publishing
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface MediaPublishRequest {
|
||||||
|
bundleDir: string;
|
||||||
|
profile: PlatformProfile;
|
||||||
|
publicationId: string;
|
||||||
|
title: string | null;
|
||||||
|
caption: string | null;
|
||||||
|
tags: string[];
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaPublishResult {
|
||||||
|
status: "published" | "bundled";
|
||||||
|
externalUrl: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaPublisher {
|
||||||
|
readonly id: string;
|
||||||
|
publish(request: MediaPublishRequest): Promise<MediaPublishResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LinkShortener {
|
||||||
|
readonly id: string;
|
||||||
|
shorten(url: string, signal?: AbortSignal): Promise<string>;
|
||||||
|
}
|
||||||
@ -36,4 +36,8 @@ export const slots = {
|
|||||||
web: "web",
|
web: "web",
|
||||||
browser: "browser",
|
browser: "browser",
|
||||||
cloudAgent: "cloud-agent",
|
cloudAgent: "cloud-agent",
|
||||||
|
mediaInference: "media-inference",
|
||||||
|
mediaRenderer: "media-renderer",
|
||||||
|
motionGraphics: "motion-graphics",
|
||||||
|
mediaPublisher: "media-publisher",
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import type { ConnectionCatalogItem, ModelTier, SandboxKind } from "@rakazo/contracts";
|
import type {
|
||||||
|
ConnectionCatalogItem,
|
||||||
|
MediaAnalysisKind,
|
||||||
|
ModelTier,
|
||||||
|
SandboxKind,
|
||||||
|
} from "@rakazo/contracts";
|
||||||
|
|
||||||
export interface AdapterContext {
|
export interface AdapterContext {
|
||||||
operationId: string;
|
operationId: string;
|
||||||
@ -524,9 +529,22 @@ export interface BackgroundJobPayloads {
|
|||||||
"cloud_agent.poll": { agentId: string };
|
"cloud_agent.poll": { agentId: string };
|
||||||
/** Learn durable user facts from conversations since the profile's last consolidation. */
|
/** Learn durable user facts from conversations since the profile's last consolidation. */
|
||||||
"profile.consolidate": { userId: string };
|
"profile.consolidate": { userId: string };
|
||||||
|
/** Enumerate a server-side media source incrementally (metadata only). */
|
||||||
|
"media.sync": { sourceId: string };
|
||||||
|
/** Hash the original and build proxy, thumbnails, and keyframes. */
|
||||||
|
"media.proxy": { assetId: string };
|
||||||
|
/** Run sidecar analyses; omitted kinds means every analysis not yet at the current version. */
|
||||||
|
"media.analyze": { assetId: string; kinds?: MediaAnalysisKind[] };
|
||||||
|
/** Rebuild sessions, themes, and person suggestions for a space. */
|
||||||
|
"media.cluster": { spaceId: string };
|
||||||
|
/** Render, probe, and gate one MediaRender. */
|
||||||
|
"media.render": { renderId: string };
|
||||||
|
/** Build the export bundle and publish a scheduled MediaPublication. */
|
||||||
|
"media.publish": { publicationId: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BackgroundJobName = keyof BackgroundJobPayloads;
|
export type BackgroundJobName = keyof BackgroundJobPayloads;
|
||||||
|
export type MediaJobName = Extract<BackgroundJobName, `media.${string}`>;
|
||||||
|
|
||||||
export type BackgroundJob = {
|
export type BackgroundJob = {
|
||||||
[Name in BackgroundJobName]: {
|
[Name in BackgroundJobName]: {
|
||||||
@ -541,6 +559,8 @@ export type BackgroundJobHandlers = {
|
|||||||
[Name in BackgroundJobName]: (payload: BackgroundJobPayloads[Name]) => Promise<void>;
|
[Name in BackgroundJobName]: (payload: BackgroundJobPayloads[Name]) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MediaJobHandlers = Pick<BackgroundJobHandlers, MediaJobName>;
|
||||||
|
|
||||||
export interface SecretRecord {
|
export interface SecretRecord {
|
||||||
id: string;
|
id: string;
|
||||||
ciphertext: string;
|
ciphertext: string;
|
||||||
|
|||||||
@ -29,6 +29,8 @@
|
|||||||
"@rakazo/core": "workspace:*",
|
"@rakazo/core": "workspace:*",
|
||||||
"@rakazo/db": "workspace:*",
|
"@rakazo/db": "workspace:*",
|
||||||
"@rakazo/logging": "workspace:*",
|
"@rakazo/logging": "workspace:*",
|
||||||
|
"@rakazo/media": "workspace:*",
|
||||||
|
"@resvg/resvg-js": "2.6.2",
|
||||||
"chat": "4.40.0",
|
"chat": "4.40.0",
|
||||||
"chat-adapter-lark": "0.3.1",
|
"chat-adapter-lark": "0.3.1",
|
||||||
"chat-adapter-sendblue": "0.2.0",
|
"chat-adapter-sendblue": "0.2.0",
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type {
|
|||||||
AgentRuntime,
|
AgentRuntime,
|
||||||
BackgroundJobHandlers,
|
BackgroundJobHandlers,
|
||||||
JobPublisher,
|
JobPublisher,
|
||||||
|
MediaJobHandlers,
|
||||||
MessagingSurface,
|
MessagingSurface,
|
||||||
SandboxProvider,
|
SandboxProvider,
|
||||||
} from "@rakazo/adapter-kit";
|
} from "@rakazo/adapter-kit";
|
||||||
@ -36,6 +37,8 @@ export function createBackgroundJobHandlers(deps: {
|
|||||||
deploymentModelKey?: string;
|
deploymentModelKey?: string;
|
||||||
messaging?: MessagingSurface;
|
messaging?: MessagingSurface;
|
||||||
cloudAgent?: CloudAgentConnection | null;
|
cloudAgent?: CloudAgentConnection | null;
|
||||||
|
/** Media Studio job handlers; absent when media is not configured for this worker. */
|
||||||
|
media?: MediaJobHandlers;
|
||||||
}): BackgroundJobHandlers {
|
}): BackgroundJobHandlers {
|
||||||
const deliverMessaging = async (runId?: string) => {
|
const deliverMessaging = async (runId?: string) => {
|
||||||
if (!deps.messaging) return;
|
if (!deps.messaging) return;
|
||||||
@ -53,6 +56,7 @@ export function createBackgroundJobHandlers(deps: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...(deps.media ?? mediaDisabledHandlers()),
|
||||||
"run.continue": async (payload) => {
|
"run.continue": async (payload) => {
|
||||||
await deps.executor.continueRun(payload.runId, deps.workerId);
|
await deps.executor.continueRun(payload.runId, deps.workerId);
|
||||||
// Automatic messaging mirror: once the run's bot messages are durable,
|
// Automatic messaging mirror: once the run's bot messages are durable,
|
||||||
@ -126,3 +130,17 @@ export function createBackgroundJobHandlers(deps: {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mediaDisabledHandlers(): MediaJobHandlers {
|
||||||
|
const skip = async () => {
|
||||||
|
getLogger().warn("media job skipped: Media Studio is not configured on this worker");
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"media.sync": skip,
|
||||||
|
"media.proxy": skip,
|
||||||
|
"media.analyze": skip,
|
||||||
|
"media.cluster": skip,
|
||||||
|
"media.render": skip,
|
||||||
|
"media.publish": skip,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -24,6 +24,12 @@ function handlers(): BackgroundJobHandlers {
|
|||||||
"messaging.deliver": vi.fn(async () => undefined),
|
"messaging.deliver": vi.fn(async () => undefined),
|
||||||
"cloud_agent.poll": vi.fn(async () => undefined),
|
"cloud_agent.poll": vi.fn(async () => undefined),
|
||||||
"profile.consolidate": vi.fn(async () => undefined),
|
"profile.consolidate": vi.fn(async () => undefined),
|
||||||
|
"media.sync": vi.fn(async () => undefined),
|
||||||
|
"media.proxy": vi.fn(async () => undefined),
|
||||||
|
"media.analyze": vi.fn(async () => undefined),
|
||||||
|
"media.cluster": vi.fn(async () => undefined),
|
||||||
|
"media.render": vi.fn(async () => undefined),
|
||||||
|
"media.publish": vi.fn(async () => undefined),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,12 @@ function handlers(overrides: Partial<BackgroundJobHandlers> = {}): BackgroundJob
|
|||||||
"messaging.deliver": vi.fn(async () => undefined),
|
"messaging.deliver": vi.fn(async () => undefined),
|
||||||
"cloud_agent.poll": vi.fn(async () => undefined),
|
"cloud_agent.poll": vi.fn(async () => undefined),
|
||||||
"profile.consolidate": vi.fn(async () => undefined),
|
"profile.consolidate": vi.fn(async () => undefined),
|
||||||
|
"media.sync": vi.fn(async () => undefined),
|
||||||
|
"media.proxy": vi.fn(async () => undefined),
|
||||||
|
"media.analyze": vi.fn(async () => undefined),
|
||||||
|
"media.cluster": vi.fn(async () => undefined),
|
||||||
|
"media.render": vi.fn(async () => undefined),
|
||||||
|
"media.publish": vi.fn(async () => undefined),
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,12 @@ function handlers(): BackgroundJobHandlers {
|
|||||||
"messaging.deliver": vi.fn(async () => undefined),
|
"messaging.deliver": vi.fn(async () => undefined),
|
||||||
"cloud_agent.poll": vi.fn(async () => undefined),
|
"cloud_agent.poll": vi.fn(async () => undefined),
|
||||||
"profile.consolidate": vi.fn(async () => undefined),
|
"profile.consolidate": vi.fn(async () => undefined),
|
||||||
|
"media.sync": vi.fn(async () => undefined),
|
||||||
|
"media.proxy": vi.fn(async () => undefined),
|
||||||
|
"media.analyze": vi.fn(async () => undefined),
|
||||||
|
"media.cluster": vi.fn(async () => undefined),
|
||||||
|
"media.render": vi.fn(async () => undefined),
|
||||||
|
"media.publish": vi.fn(async () => undefined),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ export * from "./inbox.js";
|
|||||||
export * from "./integration-settings.js";
|
export * from "./integration-settings.js";
|
||||||
export * from "./local-settings.js";
|
export * from "./local-settings.js";
|
||||||
export * from "./mcp.js";
|
export * from "./mcp.js";
|
||||||
|
export * from "./media.js";
|
||||||
export * from "./model-tiers.js";
|
export * from "./model-tiers.js";
|
||||||
export * from "./openai-compatible-ui.js";
|
export * from "./openai-compatible-ui.js";
|
||||||
export * from "./project-goals.js";
|
export * from "./project-goals.js";
|
||||||
|
|||||||
1518
packages/contracts/src/media.ts
Normal file
1518
packages/contracts/src/media.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -82,6 +82,7 @@ import {
|
|||||||
IntegrationProviderConfigSchema,
|
IntegrationProviderConfigSchema,
|
||||||
IntegrationSetupStateSchema,
|
IntegrationSetupStateSchema,
|
||||||
} from "./integration-settings.js";
|
} from "./integration-settings.js";
|
||||||
|
import { mediaRpcShapes } from "./media.js";
|
||||||
import { ModelTierMappingSchema, SetModelTierInput } from "./model-tiers.js";
|
import { ModelTierMappingSchema, SetModelTierInput } from "./model-tiers.js";
|
||||||
import {
|
import {
|
||||||
CreateProjectGoalInput,
|
CreateProjectGoalInput,
|
||||||
@ -157,6 +158,10 @@ const threadSendInput = threadTarget
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function rpc<I extends z.ZodType, O extends z.ZodType>(shape: { input: I; output: O }) {
|
||||||
|
return oc.input(shape.input).output(shape.output);
|
||||||
|
}
|
||||||
|
|
||||||
export const appContract = {
|
export const appContract = {
|
||||||
aiConsent: {
|
aiConsent: {
|
||||||
status: oc.input(AiConsentQuerySchema).output(AiConsentStatusSchema),
|
status: oc.input(AiConsentQuerySchema).output(AiConsentStatusSchema),
|
||||||
@ -861,6 +866,96 @@ export const appContract = {
|
|||||||
put: oc.input(AgentSecretInputSchema).output(AgentSecretSchema),
|
put: oc.input(AgentSecretInputSchema).output(AgentSecretSchema),
|
||||||
remove: oc.input(z.object({ id: Id })).output(z.object({ ok: z.literal(true) })),
|
remove: oc.input(z.object({ id: Id })).output(z.object({ ok: z.literal(true) })),
|
||||||
},
|
},
|
||||||
|
media: {
|
||||||
|
capabilities: rpc(mediaRpcShapes.capabilities),
|
||||||
|
sources: {
|
||||||
|
list: rpc(mediaRpcShapes.sources.list),
|
||||||
|
createFolder: rpc(mediaRpcShapes.sources.createFolder),
|
||||||
|
registerClient: rpc(mediaRpcShapes.sources.registerClient),
|
||||||
|
pushItems: rpc(mediaRpcShapes.sources.pushItems),
|
||||||
|
sync: rpc(mediaRpcShapes.sources.sync),
|
||||||
|
remove: rpc(mediaRpcShapes.sources.remove),
|
||||||
|
},
|
||||||
|
assets: {
|
||||||
|
search: rpc(mediaRpcShapes.assets.search),
|
||||||
|
get: rpc(mediaRpcShapes.assets.get),
|
||||||
|
setPaidOnly: rpc(mediaRpcShapes.assets.setPaidOnly),
|
||||||
|
requestUpload: rpc(mediaRpcShapes.assets.requestUpload),
|
||||||
|
},
|
||||||
|
uploads: {
|
||||||
|
begin: rpc(mediaRpcShapes.uploads.begin),
|
||||||
|
status: rpc(mediaRpcShapes.uploads.status),
|
||||||
|
complete: rpc(mediaRpcShapes.uploads.complete),
|
||||||
|
},
|
||||||
|
collections: {
|
||||||
|
list: rpc(mediaRpcShapes.collections.list),
|
||||||
|
get: rpc(mediaRpcShapes.collections.get),
|
||||||
|
rename: rpc(mediaRpcShapes.collections.rename),
|
||||||
|
merge: rpc(mediaRpcShapes.collections.merge),
|
||||||
|
split: rpc(mediaRpcShapes.collections.split),
|
||||||
|
createManual: rpc(mediaRpcShapes.collections.createManual),
|
||||||
|
recluster: rpc(mediaRpcShapes.collections.recluster),
|
||||||
|
},
|
||||||
|
people: {
|
||||||
|
list: rpc(mediaRpcShapes.people.list),
|
||||||
|
update: rpc(mediaRpcShapes.people.update),
|
||||||
|
merge: rpc(mediaRpcShapes.people.merge),
|
||||||
|
assignTrack: rpc(mediaRpcShapes.people.assignTrack),
|
||||||
|
recordConsent: rpc(mediaRpcShapes.people.recordConsent),
|
||||||
|
remove: rpc(mediaRpcShapes.people.remove),
|
||||||
|
},
|
||||||
|
marks: {
|
||||||
|
list: rpc(mediaRpcShapes.marks.list),
|
||||||
|
create: rpc(mediaRpcShapes.marks.create),
|
||||||
|
update: rpc(mediaRpcShapes.marks.update),
|
||||||
|
remove: rpc(mediaRpcShapes.marks.remove),
|
||||||
|
},
|
||||||
|
brandKits: {
|
||||||
|
list: rpc(mediaRpcShapes.brandKits.list),
|
||||||
|
upsert: rpc(mediaRpcShapes.brandKits.upsert),
|
||||||
|
remove: rpc(mediaRpcShapes.brandKits.remove),
|
||||||
|
templates: rpc(mediaRpcShapes.brandKits.templates),
|
||||||
|
introVariants: rpc(mediaRpcShapes.brandKits.introVariants),
|
||||||
|
},
|
||||||
|
profiles: {
|
||||||
|
list: rpc(mediaRpcShapes.profiles.list),
|
||||||
|
get: rpc(mediaRpcShapes.profiles.get),
|
||||||
|
},
|
||||||
|
projects: {
|
||||||
|
list: rpc(mediaRpcShapes.projects.list),
|
||||||
|
get: rpc(mediaRpcShapes.projects.get),
|
||||||
|
create: rpc(mediaRpcShapes.projects.create),
|
||||||
|
update: rpc(mediaRpcShapes.projects.update),
|
||||||
|
remove: rpc(mediaRpcShapes.projects.remove),
|
||||||
|
autoPlan: rpc(mediaRpcShapes.projects.autoPlan),
|
||||||
|
},
|
||||||
|
plans: {
|
||||||
|
submit: rpc(mediaRpcShapes.plans.submit),
|
||||||
|
},
|
||||||
|
renders: {
|
||||||
|
start: rpc(mediaRpcShapes.renders.start),
|
||||||
|
get: rpc(mediaRpcShapes.renders.get),
|
||||||
|
cancel: rpc(mediaRpcShapes.renders.cancel),
|
||||||
|
thumbnails: rpc(mediaRpcShapes.renders.thumbnails),
|
||||||
|
},
|
||||||
|
marketing: {
|
||||||
|
copy: rpc(mediaRpcShapes.marketing.copy),
|
||||||
|
},
|
||||||
|
publications: {
|
||||||
|
list: rpc(mediaRpcShapes.publications.list),
|
||||||
|
create: rpc(mediaRpcShapes.publications.create),
|
||||||
|
bundle: rpc(mediaRpcShapes.publications.bundle),
|
||||||
|
markPublished: rpc(mediaRpcShapes.publications.markPublished),
|
||||||
|
cancel: rpc(mediaRpcShapes.publications.cancel),
|
||||||
|
},
|
||||||
|
metrics: {
|
||||||
|
mappings: rpc(mediaRpcShapes.metrics.mappings),
|
||||||
|
saveMapping: rpc(mediaRpcShapes.metrics.saveMapping),
|
||||||
|
importCsv: rpc(mediaRpcShapes.metrics.importCsv),
|
||||||
|
record: rpc(mediaRpcShapes.metrics.record),
|
||||||
|
funnel: rpc(mediaRpcShapes.metrics.funnel),
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppContract = typeof appContract;
|
export type AppContract = typeof appContract;
|
||||||
|
|||||||
@ -0,0 +1,513 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_sources" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"ownerUserId" TEXT NOT NULL,
|
||||||
|
"kind" TEXT NOT NULL,
|
||||||
|
"label" TEXT NOT NULL,
|
||||||
|
"config" JSONB NOT NULL DEFAULT '{}',
|
||||||
|
"status" TEXT NOT NULL DEFAULT 'idle',
|
||||||
|
"error" TEXT,
|
||||||
|
"lastSyncCursor" TEXT,
|
||||||
|
"lastSyncedAt" TIMESTAMP(3),
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_sources_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_assets" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"sourceId" TEXT NOT NULL,
|
||||||
|
"externalId" TEXT NOT NULL,
|
||||||
|
"revision" TEXT,
|
||||||
|
"contentHash" TEXT,
|
||||||
|
"kind" TEXT NOT NULL,
|
||||||
|
"storage" TEXT NOT NULL,
|
||||||
|
"storagePath" TEXT,
|
||||||
|
"fileName" TEXT,
|
||||||
|
"sizeBytes" BIGINT,
|
||||||
|
"capturedAt" TIMESTAMP(3),
|
||||||
|
"durationMs" INTEGER,
|
||||||
|
"width" INTEGER,
|
||||||
|
"height" INTEGER,
|
||||||
|
"fps" DOUBLE PRECISION,
|
||||||
|
"codec" TEXT,
|
||||||
|
"orientation" INTEGER,
|
||||||
|
"gpsLat" DOUBLE PRECISION,
|
||||||
|
"gpsLon" DOUBLE PRECISION,
|
||||||
|
"deviceModel" TEXT,
|
||||||
|
"albumNames" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"isFavorite" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"proxyPath" TEXT,
|
||||||
|
"thumbPath" TEXT,
|
||||||
|
"spritePath" TEXT,
|
||||||
|
"qualityScore" DOUBLE PRECISION,
|
||||||
|
"analysisVersion" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"ingestState" TEXT NOT NULL DEFAULT 'indexed',
|
||||||
|
"ingestError" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_assets_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_uploads" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"assetId" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"sizeBytes" BIGINT NOT NULL,
|
||||||
|
"chunkBytes" INTEGER NOT NULL,
|
||||||
|
"sha256" TEXT NOT NULL,
|
||||||
|
"fileName" TEXT NOT NULL,
|
||||||
|
"receivedChunks" INTEGER[] DEFAULT ARRAY[]::INTEGER[],
|
||||||
|
"status" TEXT NOT NULL DEFAULT 'open',
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_uploads_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_segments" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"assetId" TEXT NOT NULL,
|
||||||
|
"kind" TEXT NOT NULL,
|
||||||
|
"startMs" INTEGER NOT NULL,
|
||||||
|
"endMs" INTEGER NOT NULL,
|
||||||
|
"scores" JSONB NOT NULL DEFAULT '{}',
|
||||||
|
"teaserScore" DOUBLE PRECISION,
|
||||||
|
"transcriptExcerpt" TEXT,
|
||||||
|
"labels" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"paidOnly" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"analysisVersion" INTEGER NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "media_segments_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_analyses" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"assetId" TEXT NOT NULL,
|
||||||
|
"kind" TEXT NOT NULL,
|
||||||
|
"modelId" TEXT NOT NULL,
|
||||||
|
"modelVersion" TEXT NOT NULL,
|
||||||
|
"data" JSONB,
|
||||||
|
"dataPath" TEXT,
|
||||||
|
"vectors" BYTEA,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "media_analyses_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_collections" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"kind" TEXT NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"labelSource" TEXT NOT NULL DEFAULT 'rule',
|
||||||
|
"startAt" TIMESTAMP(3),
|
||||||
|
"endAt" TIMESTAMP(3),
|
||||||
|
"centroidLat" DOUBLE PRECISION,
|
||||||
|
"centroidLon" DOUBLE PRECISION,
|
||||||
|
"coverAssetId" TEXT,
|
||||||
|
"clusterKey" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_collections_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_collection_items" (
|
||||||
|
"collectionId" TEXT NOT NULL,
|
||||||
|
"assetId" TEXT NOT NULL,
|
||||||
|
"position" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_collection_items_pkey" PRIMARY KEY ("collectionId","assetId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_people" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"displayName" TEXT NOT NULL,
|
||||||
|
"isSelf" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"confirmed" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"anonymizeByDefault" TEXT NOT NULL DEFAULT 'none',
|
||||||
|
"centroid" BYTEA,
|
||||||
|
"consentArtifactId" TEXT,
|
||||||
|
"consentRecordedAt" TIMESTAMP(3),
|
||||||
|
"consentRecordedBy" TEXT,
|
||||||
|
"ageVerifiedAt" TIMESTAMP(3),
|
||||||
|
"ageVerificationMethod" TEXT NOT NULL DEFAULT 'none',
|
||||||
|
"syntheticSeed" INTEGER,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_people_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_face_tracks" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"assetId" TEXT NOT NULL,
|
||||||
|
"personId" TEXT,
|
||||||
|
"startMs" INTEGER NOT NULL,
|
||||||
|
"endMs" INTEGER NOT NULL,
|
||||||
|
"confidenceMin" DOUBLE PRECISION NOT NULL,
|
||||||
|
"keyframesPath" TEXT NOT NULL,
|
||||||
|
"embedding" BYTEA,
|
||||||
|
"modelVersion" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "media_face_tracks_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_marks" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"kind" TEXT NOT NULL,
|
||||||
|
"label" TEXT,
|
||||||
|
"personId" TEXT,
|
||||||
|
"assetId" TEXT,
|
||||||
|
"prompt" JSONB,
|
||||||
|
"treatment" TEXT NOT NULL,
|
||||||
|
"maskDir" TEXT,
|
||||||
|
"qualityScore" DOUBLE PRECISION,
|
||||||
|
"suggestedFallback" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_marks_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_brand_kits" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"kit" JSONB NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_brand_kits_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_projects" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"createdBy" TEXT NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"goal" TEXT NOT NULL,
|
||||||
|
"status" TEXT NOT NULL DEFAULT 'draft',
|
||||||
|
"brandKitId" TEXT,
|
||||||
|
"collectionIds" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"assetIds" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"profileIds" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_projects_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_edit_plans" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"projectId" TEXT NOT NULL,
|
||||||
|
"profileId" TEXT NOT NULL,
|
||||||
|
"version" INTEGER NOT NULL,
|
||||||
|
"edl" JSONB NOT NULL,
|
||||||
|
"createdBy" TEXT NOT NULL,
|
||||||
|
"rationale" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "media_edit_plans_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_renders" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"projectId" TEXT NOT NULL,
|
||||||
|
"planId" TEXT NOT NULL,
|
||||||
|
"profileId" TEXT NOT NULL,
|
||||||
|
"quality" TEXT NOT NULL,
|
||||||
|
"status" TEXT NOT NULL DEFAULT 'queued',
|
||||||
|
"progress" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"outputs" JSONB NOT NULL DEFAULT '[]',
|
||||||
|
"gateReport" JSONB,
|
||||||
|
"ffprobe" JSONB,
|
||||||
|
"autoFixRound" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"error" TEXT,
|
||||||
|
"createdBy" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_renders_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_publications" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"renderId" TEXT NOT NULL,
|
||||||
|
"profileId" TEXT NOT NULL,
|
||||||
|
"destination" TEXT NOT NULL,
|
||||||
|
"status" TEXT NOT NULL DEFAULT 'draft',
|
||||||
|
"scheduledAt" TIMESTAMP(3),
|
||||||
|
"publishedAt" TIMESTAMP(3),
|
||||||
|
"externalUrl" TEXT,
|
||||||
|
"targetUrl" TEXT,
|
||||||
|
"trackingUrl" TEXT,
|
||||||
|
"utm" JSONB,
|
||||||
|
"title" TEXT,
|
||||||
|
"caption" TEXT,
|
||||||
|
"tags" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"thumbnailVariant" TEXT,
|
||||||
|
"bundleArtifactId" TEXT,
|
||||||
|
"createdBy" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_publications_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_metrics" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"publicationId" TEXT NOT NULL,
|
||||||
|
"observedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"views" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"clicks" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"conversions" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"revenueCents" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"currency" TEXT NOT NULL DEFAULT 'EUR',
|
||||||
|
"source" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "media_metrics_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_metric_mappings" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"mapping" JSONB NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_metric_mappings_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "media_teaser_models" (
|
||||||
|
"spaceId" TEXT NOT NULL,
|
||||||
|
"weights" JSONB NOT NULL,
|
||||||
|
"sampleCount" INTEGER NOT NULL,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "media_teaser_models_pkey" PRIMARY KEY ("spaceId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_sources_spaceId_createdAt_idx" ON "media_sources"("spaceId", "createdAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_assets_spaceId_capturedAt_idx" ON "media_assets"("spaceId", "capturedAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_assets_spaceId_kind_capturedAt_idx" ON "media_assets"("spaceId", "kind", "capturedAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_assets_spaceId_ingestState_idx" ON "media_assets"("spaceId", "ingestState");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_assets_contentHash_idx" ON "media_assets"("contentHash");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "media_assets_sourceId_externalId_key" ON "media_assets"("sourceId", "externalId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_uploads_assetId_idx" ON "media_uploads"("assetId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_uploads_spaceId_status_idx" ON "media_uploads"("spaceId", "status");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_segments_assetId_startMs_idx" ON "media_segments"("assetId", "startMs");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_segments_spaceId_teaserScore_idx" ON "media_segments"("spaceId", "teaserScore");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_analyses_spaceId_kind_idx" ON "media_analyses"("spaceId", "kind");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "media_analyses_assetId_kind_modelVersion_key" ON "media_analyses"("assetId", "kind", "modelVersion");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_collections_spaceId_kind_startAt_idx" ON "media_collections"("spaceId", "kind", "startAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "media_collections_spaceId_kind_clusterKey_key" ON "media_collections"("spaceId", "kind", "clusterKey");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_collection_items_assetId_idx" ON "media_collection_items"("assetId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_people_spaceId_idx" ON "media_people"("spaceId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_face_tracks_assetId_idx" ON "media_face_tracks"("assetId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_face_tracks_personId_idx" ON "media_face_tracks"("personId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_marks_spaceId_personId_idx" ON "media_marks"("spaceId", "personId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_marks_assetId_idx" ON "media_marks"("assetId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_brand_kits_spaceId_idx" ON "media_brand_kits"("spaceId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_projects_spaceId_updatedAt_idx" ON "media_projects"("spaceId", "updatedAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "media_edit_plans_projectId_profileId_version_key" ON "media_edit_plans"("projectId", "profileId", "version");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_renders_projectId_createdAt_idx" ON "media_renders"("projectId", "createdAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_renders_spaceId_status_idx" ON "media_renders"("spaceId", "status");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_publications_spaceId_createdAt_idx" ON "media_publications"("spaceId", "createdAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_publications_renderId_idx" ON "media_publications"("renderId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "media_metrics_spaceId_observedAt_idx" ON "media_metrics"("spaceId", "observedAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "media_metrics_publicationId_observedAt_source_key" ON "media_metrics"("publicationId", "observedAt", "source");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "media_metric_mappings_spaceId_name_key" ON "media_metric_mappings"("spaceId", "name");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_sources" ADD CONSTRAINT "media_sources_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_assets" ADD CONSTRAINT "media_assets_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_assets" ADD CONSTRAINT "media_assets_sourceId_fkey" FOREIGN KEY ("sourceId") REFERENCES "media_sources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_uploads" ADD CONSTRAINT "media_uploads_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_uploads" ADD CONSTRAINT "media_uploads_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "media_assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_segments" ADD CONSTRAINT "media_segments_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_segments" ADD CONSTRAINT "media_segments_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "media_assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_analyses" ADD CONSTRAINT "media_analyses_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_analyses" ADD CONSTRAINT "media_analyses_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "media_assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_collections" ADD CONSTRAINT "media_collections_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_collection_items" ADD CONSTRAINT "media_collection_items_collectionId_fkey" FOREIGN KEY ("collectionId") REFERENCES "media_collections"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_collection_items" ADD CONSTRAINT "media_collection_items_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "media_assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_people" ADD CONSTRAINT "media_people_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_face_tracks" ADD CONSTRAINT "media_face_tracks_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_face_tracks" ADD CONSTRAINT "media_face_tracks_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "media_assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_face_tracks" ADD CONSTRAINT "media_face_tracks_personId_fkey" FOREIGN KEY ("personId") REFERENCES "media_people"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_marks" ADD CONSTRAINT "media_marks_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_marks" ADD CONSTRAINT "media_marks_personId_fkey" FOREIGN KEY ("personId") REFERENCES "media_people"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_marks" ADD CONSTRAINT "media_marks_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "media_assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_brand_kits" ADD CONSTRAINT "media_brand_kits_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_projects" ADD CONSTRAINT "media_projects_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_projects" ADD CONSTRAINT "media_projects_brandKitId_fkey" FOREIGN KEY ("brandKitId") REFERENCES "media_brand_kits"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_edit_plans" ADD CONSTRAINT "media_edit_plans_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_edit_plans" ADD CONSTRAINT "media_edit_plans_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "media_projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_renders" ADD CONSTRAINT "media_renders_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_renders" ADD CONSTRAINT "media_renders_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "media_projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_renders" ADD CONSTRAINT "media_renders_planId_fkey" FOREIGN KEY ("planId") REFERENCES "media_edit_plans"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_publications" ADD CONSTRAINT "media_publications_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_publications" ADD CONSTRAINT "media_publications_renderId_fkey" FOREIGN KEY ("renderId") REFERENCES "media_renders"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_metrics" ADD CONSTRAINT "media_metrics_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_metrics" ADD CONSTRAINT "media_metrics_publicationId_fkey" FOREIGN KEY ("publicationId") REFERENCES "media_publications"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_metric_mappings" ADD CONSTRAINT "media_metric_mappings_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "media_teaser_models" ADD CONSTRAINT "media_teaser_models_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@ -216,6 +216,23 @@ model Space {
|
|||||||
userFactVisibility UserFactSpace[]
|
userFactVisibility UserFactSpace[]
|
||||||
delegatedTasks DelegatedTask[]
|
delegatedTasks DelegatedTask[]
|
||||||
semanticMemoryItems SemanticMemoryItem[]
|
semanticMemoryItems SemanticMemoryItem[]
|
||||||
|
mediaSources MediaSource[]
|
||||||
|
mediaAssets MediaAsset[]
|
||||||
|
mediaUploads MediaUpload[]
|
||||||
|
mediaSegments MediaSegment[]
|
||||||
|
mediaAnalyses MediaAnalysis[]
|
||||||
|
mediaCollections MediaCollection[]
|
||||||
|
mediaPeople MediaPerson[]
|
||||||
|
mediaFaceTracks MediaFaceTrack[]
|
||||||
|
mediaMarks MediaMark[]
|
||||||
|
mediaBrandKits MediaBrandKit[]
|
||||||
|
mediaProjects MediaProject[]
|
||||||
|
mediaEditPlans MediaEditPlan[]
|
||||||
|
mediaRenders MediaRender[]
|
||||||
|
mediaPublications MediaPublication[]
|
||||||
|
mediaMetrics MediaMetric[]
|
||||||
|
mediaMetricMappings MediaMetricMapping[]
|
||||||
|
mediaTeaserModel MediaTeaserModel?
|
||||||
|
|
||||||
@@unique([id, organizationId])
|
@@unique([id, organizationId])
|
||||||
@@index([organizationId, createdAt])
|
@@index([organizationId, createdAt])
|
||||||
@ -1497,3 +1514,411 @@ model DelegatedTask {
|
|||||||
// CHECK constraint delegated_tasks_status_chk is created in SQL.
|
// CHECK constraint delegated_tasks_status_chk is created in SQL.
|
||||||
@@map("delegated_tasks")
|
@@map("delegated_tasks")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Media Studio
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
model MediaSource {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
ownerUserId String
|
||||||
|
kind String
|
||||||
|
label String
|
||||||
|
/// Folder: { path }. Client-pushed: { deviceId }. Never secrets.
|
||||||
|
config Json @default("{}")
|
||||||
|
status String @default("idle")
|
||||||
|
error String?
|
||||||
|
lastSyncCursor String?
|
||||||
|
lastSyncedAt DateTime?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
assets MediaAsset[]
|
||||||
|
|
||||||
|
@@index([spaceId, createdAt])
|
||||||
|
@@map("media_sources")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaAsset {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
sourceId String
|
||||||
|
source MediaSource @relation(fields: [sourceId], references: [id], onDelete: Cascade)
|
||||||
|
externalId String
|
||||||
|
revision String?
|
||||||
|
contentHash String?
|
||||||
|
kind String
|
||||||
|
storage String
|
||||||
|
/// Uploaded originals: path relative to the media root. Referenced originals resolve through the source.
|
||||||
|
storagePath String?
|
||||||
|
fileName String?
|
||||||
|
sizeBytes BigInt?
|
||||||
|
capturedAt DateTime?
|
||||||
|
durationMs Int?
|
||||||
|
width Int?
|
||||||
|
height Int?
|
||||||
|
fps Float?
|
||||||
|
codec String?
|
||||||
|
orientation Int?
|
||||||
|
/// Rounded to two decimals (about 1 km).
|
||||||
|
gpsLat Float?
|
||||||
|
gpsLon Float?
|
||||||
|
deviceModel String?
|
||||||
|
albumNames String[] @default([])
|
||||||
|
isFavorite Boolean @default(false)
|
||||||
|
proxyPath String?
|
||||||
|
thumbPath String?
|
||||||
|
spritePath String?
|
||||||
|
qualityScore Float?
|
||||||
|
analysisVersion Int @default(0)
|
||||||
|
ingestState String @default("indexed")
|
||||||
|
ingestError String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
segments MediaSegment[]
|
||||||
|
analyses MediaAnalysis[]
|
||||||
|
collectionItems MediaCollectionItem[]
|
||||||
|
faceTracks MediaFaceTrack[]
|
||||||
|
marks MediaMark[]
|
||||||
|
uploads MediaUpload[]
|
||||||
|
|
||||||
|
@@unique([sourceId, externalId])
|
||||||
|
@@index([spaceId, capturedAt])
|
||||||
|
@@index([spaceId, kind, capturedAt])
|
||||||
|
@@index([spaceId, ingestState])
|
||||||
|
@@index([contentHash])
|
||||||
|
@@map("media_assets")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaUpload {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
assetId String
|
||||||
|
asset MediaAsset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||||||
|
userId String
|
||||||
|
sizeBytes BigInt
|
||||||
|
chunkBytes Int
|
||||||
|
sha256 String
|
||||||
|
fileName String
|
||||||
|
receivedChunks Int[] @default([])
|
||||||
|
status String @default("open")
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@index([assetId])
|
||||||
|
@@index([spaceId, status])
|
||||||
|
@@map("media_uploads")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaSegment {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
assetId String
|
||||||
|
asset MediaAsset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||||||
|
kind String
|
||||||
|
startMs Int
|
||||||
|
endMs Int
|
||||||
|
/// Partial<Record<TeaserFeature, number>>
|
||||||
|
scores Json @default("{}")
|
||||||
|
teaserScore Float?
|
||||||
|
transcriptExcerpt String?
|
||||||
|
labels String[] @default([])
|
||||||
|
paidOnly Boolean @default(false)
|
||||||
|
analysisVersion Int
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@index([assetId, startMs])
|
||||||
|
@@index([spaceId, teaserScore])
|
||||||
|
@@map("media_segments")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaAnalysis {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
assetId String
|
||||||
|
asset MediaAsset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||||||
|
kind String
|
||||||
|
modelId String
|
||||||
|
modelVersion String
|
||||||
|
/// Small results inline; large results as a path relative to the media root.
|
||||||
|
data Json?
|
||||||
|
dataPath String?
|
||||||
|
/// Float32 little-endian vectors for embeddings.
|
||||||
|
vectors Bytes?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@unique([assetId, kind, modelVersion])
|
||||||
|
@@index([spaceId, kind])
|
||||||
|
@@map("media_analyses")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaCollection {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
kind String
|
||||||
|
title String
|
||||||
|
labelSource String @default("rule")
|
||||||
|
startAt DateTime?
|
||||||
|
endAt DateTime?
|
||||||
|
centroidLat Float?
|
||||||
|
centroidLon Float?
|
||||||
|
coverAssetId String?
|
||||||
|
/// Stable key so re-clustering updates instead of recreating user-renamed collections.
|
||||||
|
clusterKey String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
items MediaCollectionItem[]
|
||||||
|
|
||||||
|
@@unique([spaceId, kind, clusterKey])
|
||||||
|
@@index([spaceId, kind, startAt])
|
||||||
|
@@map("media_collections")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaCollectionItem {
|
||||||
|
collectionId String
|
||||||
|
collection MediaCollection @relation(fields: [collectionId], references: [id], onDelete: Cascade)
|
||||||
|
assetId String
|
||||||
|
asset MediaAsset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||||||
|
position Int
|
||||||
|
|
||||||
|
@@id([collectionId, assetId])
|
||||||
|
@@index([assetId])
|
||||||
|
@@map("media_collection_items")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaPerson {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
displayName String
|
||||||
|
isSelf Boolean @default(false)
|
||||||
|
confirmed Boolean @default(false)
|
||||||
|
anonymizeByDefault String @default("none")
|
||||||
|
/// Mean identity embedding (Float32). Biometric: local only, deleted with the person.
|
||||||
|
centroid Bytes?
|
||||||
|
consentArtifactId String?
|
||||||
|
consentRecordedAt DateTime?
|
||||||
|
consentRecordedBy String?
|
||||||
|
ageVerifiedAt DateTime?
|
||||||
|
ageVerificationMethod String @default("none")
|
||||||
|
/// Seed for a stable synthetic identity.
|
||||||
|
syntheticSeed Int?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
faceTracks MediaFaceTrack[]
|
||||||
|
marks MediaMark[]
|
||||||
|
|
||||||
|
@@index([spaceId])
|
||||||
|
@@map("media_people")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaFaceTrack {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
assetId String
|
||||||
|
asset MediaAsset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||||||
|
personId String?
|
||||||
|
person MediaPerson? @relation(fields: [personId], references: [id], onDelete: SetNull)
|
||||||
|
startMs Int
|
||||||
|
endMs Int
|
||||||
|
confidenceMin Float
|
||||||
|
/// Box keyframes JSON relative to the media root.
|
||||||
|
keyframesPath String
|
||||||
|
/// Biometric (Float32). Local only.
|
||||||
|
embedding Bytes?
|
||||||
|
modelVersion String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@index([assetId])
|
||||||
|
@@index([personId])
|
||||||
|
@@map("media_face_tracks")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaMark {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
kind String
|
||||||
|
label String?
|
||||||
|
personId String?
|
||||||
|
person MediaPerson? @relation(fields: [personId], references: [id], onDelete: Cascade)
|
||||||
|
assetId String?
|
||||||
|
asset MediaAsset? @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||||||
|
/// MediaMarkPrompt
|
||||||
|
prompt Json?
|
||||||
|
treatment String
|
||||||
|
maskDir String?
|
||||||
|
qualityScore Float?
|
||||||
|
suggestedFallback String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@index([spaceId, personId])
|
||||||
|
@@index([assetId])
|
||||||
|
@@map("media_marks")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaBrandKit {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
/// BrandKit
|
||||||
|
kit Json
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
projects MediaProject[]
|
||||||
|
|
||||||
|
@@index([spaceId])
|
||||||
|
@@map("media_brand_kits")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaProject {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
createdBy String
|
||||||
|
title String
|
||||||
|
goal String
|
||||||
|
status String @default("draft")
|
||||||
|
brandKitId String?
|
||||||
|
brandKit MediaBrandKit? @relation(fields: [brandKitId], references: [id], onDelete: SetNull)
|
||||||
|
collectionIds String[] @default([])
|
||||||
|
assetIds String[] @default([])
|
||||||
|
profileIds String[] @default([])
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
plans MediaEditPlan[]
|
||||||
|
renders MediaRender[]
|
||||||
|
|
||||||
|
@@index([spaceId, updatedAt])
|
||||||
|
@@map("media_projects")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaEditPlan {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
projectId String
|
||||||
|
project MediaProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
profileId String
|
||||||
|
version Int
|
||||||
|
/// Edl, validated before insert.
|
||||||
|
edl Json
|
||||||
|
createdBy String
|
||||||
|
rationale String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
renders MediaRender[]
|
||||||
|
|
||||||
|
@@unique([projectId, profileId, version])
|
||||||
|
@@map("media_edit_plans")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaRender {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
projectId String
|
||||||
|
project MediaProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
planId String
|
||||||
|
plan MediaEditPlan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
||||||
|
profileId String
|
||||||
|
quality String
|
||||||
|
status String @default("queued")
|
||||||
|
progress Float @default(0)
|
||||||
|
/// MediaRenderOutput[] plus internal relative paths.
|
||||||
|
outputs Json @default("[]")
|
||||||
|
gateReport Json?
|
||||||
|
ffprobe Json?
|
||||||
|
autoFixRound Int @default(0)
|
||||||
|
error String?
|
||||||
|
createdBy String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
publications MediaPublication[]
|
||||||
|
|
||||||
|
@@index([projectId, createdAt])
|
||||||
|
@@index([spaceId, status])
|
||||||
|
@@map("media_renders")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaPublication {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
renderId String
|
||||||
|
render MediaRender @relation(fields: [renderId], references: [id], onDelete: Cascade)
|
||||||
|
profileId String
|
||||||
|
destination String
|
||||||
|
status String @default("draft")
|
||||||
|
scheduledAt DateTime?
|
||||||
|
publishedAt DateTime?
|
||||||
|
externalUrl String?
|
||||||
|
targetUrl String?
|
||||||
|
trackingUrl String?
|
||||||
|
utm Json?
|
||||||
|
title String?
|
||||||
|
caption String?
|
||||||
|
tags String[] @default([])
|
||||||
|
thumbnailVariant String?
|
||||||
|
bundleArtifactId String?
|
||||||
|
createdBy String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
metrics MediaMetric[]
|
||||||
|
|
||||||
|
@@index([spaceId, createdAt])
|
||||||
|
@@index([renderId])
|
||||||
|
@@map("media_publications")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaMetric {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
publicationId String
|
||||||
|
publication MediaPublication @relation(fields: [publicationId], references: [id], onDelete: Cascade)
|
||||||
|
observedAt DateTime
|
||||||
|
views Int @default(0)
|
||||||
|
clicks Int @default(0)
|
||||||
|
conversions Int @default(0)
|
||||||
|
revenueCents Int @default(0)
|
||||||
|
currency String @default("EUR")
|
||||||
|
source String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@unique([publicationId, observedAt, source])
|
||||||
|
@@index([spaceId, observedAt])
|
||||||
|
@@map("media_metrics")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaMetricMapping {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
spaceId String
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
name String
|
||||||
|
/// MetricCsvMapping without id
|
||||||
|
mapping Json
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@unique([spaceId, name])
|
||||||
|
@@map("media_metric_mappings")
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaTeaserModel {
|
||||||
|
spaceId String @id
|
||||||
|
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
|
||||||
|
/// TeaserWeights
|
||||||
|
weights Json
|
||||||
|
sampleCount Int
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@map("media_teaser_models")
|
||||||
|
}
|
||||||
|
|||||||
30
packages/media/package.json
Normal file
30
packages/media/package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@rakazo/media",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./src/index.ts",
|
||||||
|
"development": "./src/index.ts",
|
||||||
|
"default": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"./node": {
|
||||||
|
"types": "./src/node.ts",
|
||||||
|
"development": "./src/node.ts",
|
||||||
|
"default": "./src/node.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"check": "tsc --noEmit -p tsconfig.json",
|
||||||
|
"test": "vitest run --root ../.. packages/media/src"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@rakazo/contracts": "workspace:*",
|
||||||
|
"zod": "4.6.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "7.0.2",
|
||||||
|
"vitest": "4.1.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
87
packages/media/src/clustering.ts
Normal file
87
packages/media/src/clustering.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import type { MediaAssetKind } from "@rakazo/contracts";
|
||||||
|
import { notImplemented } from "./not-implemented.js";
|
||||||
|
|
||||||
|
export interface ClusterAsset {
|
||||||
|
id: string;
|
||||||
|
kind: MediaAssetKind;
|
||||||
|
capturedAt: Date | null;
|
||||||
|
gpsLat: number | null;
|
||||||
|
gpsLon: number | null;
|
||||||
|
deviceModel: string | null;
|
||||||
|
albumNames: string[];
|
||||||
|
/** Mean frame embedding; null when not analyzed yet. */
|
||||||
|
embedding: Float32Array | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SessionOptions {
|
||||||
|
/** New session after a gap longer than this. Default 45. */
|
||||||
|
gapMinutes: number;
|
||||||
|
/** New session after a location jump larger than this. Default 2. */
|
||||||
|
maxDistanceKm: number;
|
||||||
|
/** New session on calendar-day change when the gap exceeds this. Default 3. */
|
||||||
|
dayBreakHours: number;
|
||||||
|
/** Merge adjacent sessions whose mean embeddings reach this cosine. Default 0.85. */
|
||||||
|
mergeSimilarity: number;
|
||||||
|
/** ...and whose gap is below this. Default 2. */
|
||||||
|
mergeMaxGapHours: number;
|
||||||
|
/** IANA zone for calendar-day boundaries. Default "UTC". */
|
||||||
|
timeZone: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_SESSION_OPTIONS: SessionOptions = {
|
||||||
|
gapMinutes: 45,
|
||||||
|
maxDistanceKm: 2,
|
||||||
|
dayBreakHours: 3,
|
||||||
|
mergeSimilarity: 0.85,
|
||||||
|
mergeMaxGapHours: 2,
|
||||||
|
timeZone: "UTC",
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SessionCluster {
|
||||||
|
/** Stable across re-clustering: derived from the earliest asset id of the session. */
|
||||||
|
key: string;
|
||||||
|
assetIds: string[];
|
||||||
|
startAt: Date | null;
|
||||||
|
endAt: Date | null;
|
||||||
|
centroid: { lat: number; lon: number } | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deterministic session grouping (spec 4.4). Assets without capturedAt form
|
||||||
|
* one trailing "undated" session; exact duplicates (same id) are ignored.
|
||||||
|
*/
|
||||||
|
export function buildSessions(
|
||||||
|
_assets: ClusterAsset[],
|
||||||
|
_options?: Partial<SessionOptions>,
|
||||||
|
): SessionCluster[] {
|
||||||
|
return notImplemented("buildSessions");
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VectorItem {
|
||||||
|
id: string;
|
||||||
|
vector: Float32Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Agglomerative average-linkage clustering; returns groups with stable keys. */
|
||||||
|
export function clusterThemes(
|
||||||
|
_items: VectorItem[],
|
||||||
|
_options?: { similarity?: number; minSize?: number },
|
||||||
|
): Array<{ key: string; ids: string[] }> {
|
||||||
|
return notImplemented("clusterThemes");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Group face tracks into person suggestions by identity embedding. Local only. */
|
||||||
|
export function clusterFaces(
|
||||||
|
_tracks: VectorItem[],
|
||||||
|
_options?: { similarity?: number },
|
||||||
|
): Array<{ key: string; ids: string[]; centroid: Float32Array }> {
|
||||||
|
return notImplemented("clusterFaces");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rule-based title from date range, place centroid, and dominant album. No model involved. */
|
||||||
|
export function ruleLabel(
|
||||||
|
_cluster: { startAt: Date | null; endAt: Date | null; albumNames: string[] },
|
||||||
|
_locale?: string,
|
||||||
|
): string {
|
||||||
|
return notImplemented("ruleLabel");
|
||||||
|
}
|
||||||
117
packages/media/src/compile.ts
Normal file
117
packages/media/src/compile.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import type {
|
||||||
|
BrandKit,
|
||||||
|
Edl,
|
||||||
|
MediaRenderQuality,
|
||||||
|
NormalizedBox,
|
||||||
|
PlatformProfile,
|
||||||
|
ReframeKeyframe,
|
||||||
|
RenderPlan,
|
||||||
|
} from "@rakazo/contracts";
|
||||||
|
import { notImplemented } from "./not-implemented.js";
|
||||||
|
|
||||||
|
export interface CompileSource {
|
||||||
|
/** Redacted intermediate when redactions apply, otherwise original or proxy. */
|
||||||
|
path: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
durationMs: number;
|
||||||
|
fps: number;
|
||||||
|
hasAudio: boolean;
|
||||||
|
rotation: 0 | 90 | 180 | 270;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CaptionWord {
|
||||||
|
startMs: number;
|
||||||
|
endMs: number;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompileInput {
|
||||||
|
edl: Edl;
|
||||||
|
profile: PlatformProfile;
|
||||||
|
brandKit: BrandKit | null;
|
||||||
|
quality: MediaRenderQuality;
|
||||||
|
/** Absolute work directory for intermediates and outputs. */
|
||||||
|
workDir: string;
|
||||||
|
sources: Map<string, CompileSource>;
|
||||||
|
/** Source-time words per asset for captions. */
|
||||||
|
transcripts: Map<string, CaptionWord[]>;
|
||||||
|
/** Saliency or subject points per asset for "auto" reframe. */
|
||||||
|
subjects: Map<string, Array<{ tMs: number; cx: number; cy: number; strength: number }>>;
|
||||||
|
/** Pre-rendered motion clips keyed by overlay id, "intro", "outro". */
|
||||||
|
motionClips: Map<string, string>;
|
||||||
|
files: {
|
||||||
|
lutPath: string | null;
|
||||||
|
watermarkPath: string | null;
|
||||||
|
fonts: Map<string, string>;
|
||||||
|
/** artifactId → absolute path for audio clips. */
|
||||||
|
audio: Map<string, string>;
|
||||||
|
};
|
||||||
|
/** ffmpeg video encoder name, e.g. libx264, h264_vaapi, h264_videotoolbox. */
|
||||||
|
videoEncoder: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pure compiler from a validated EDL to ffmpeg argv steps plus caption files.
|
||||||
|
* No string ever reaches a shell; text is escaped for filtergraph and ASS,
|
||||||
|
* every label comes from SafeId values.
|
||||||
|
*/
|
||||||
|
export function compileRenderPlan(_input: CompileInput): RenderPlan {
|
||||||
|
return notImplemented("compileRenderPlan");
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CaptionCue {
|
||||||
|
startMs: number;
|
||||||
|
endMs: number;
|
||||||
|
text: string;
|
||||||
|
/** Word timings inside the cue for karaoke highlight. */
|
||||||
|
words: CaptionWord[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildCaptionCues(
|
||||||
|
_words: CaptionWord[],
|
||||||
|
_options: { maxCharsPerLine: number; maxLines: number },
|
||||||
|
): CaptionCue[] {
|
||||||
|
return notImplemented("buildCaptionCues");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toSrt(_cues: CaptionCue[]): string {
|
||||||
|
return notImplemented("toSrt");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toVtt(_cues: CaptionCue[]): string {
|
||||||
|
return notImplemented("toVtt");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Smoothed crop path that keeps the subject in frame for the output aspect. */
|
||||||
|
export function autoReframe(
|
||||||
|
_subjects: Array<{ tMs: number; cx: number; cy: number; strength: number }>,
|
||||||
|
_input: {
|
||||||
|
sourceWidth: number;
|
||||||
|
sourceHeight: number;
|
||||||
|
outWidth: number;
|
||||||
|
outHeight: number;
|
||||||
|
durationMs: number;
|
||||||
|
},
|
||||||
|
): ReframeKeyframe[] {
|
||||||
|
return notImplemented("autoReframe");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Crop path that keeps every given face box outside the output frame, or null if impossible. */
|
||||||
|
export function reframeExcluding(
|
||||||
|
_faces: Array<{ tMs: number; box: NormalizedBox }>,
|
||||||
|
_input: {
|
||||||
|
sourceWidth: number;
|
||||||
|
sourceHeight: number;
|
||||||
|
outWidth: number;
|
||||||
|
outHeight: number;
|
||||||
|
durationMs: number;
|
||||||
|
},
|
||||||
|
): ReframeKeyframe[] | null {
|
||||||
|
return notImplemented("reframeExcluding");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Escape text for ffmpeg drawtext/filtergraph values. */
|
||||||
|
export function escapeFilterText(_text: string): string {
|
||||||
|
return notImplemented("escapeFilterText");
|
||||||
|
}
|
||||||
45
packages/media/src/edl.ts
Normal file
45
packages/media/src/edl.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import type { BrandKit, Edl, EdlError, MediaAssetKind, PlatformProfile } from "@rakazo/contracts";
|
||||||
|
import { notImplemented } from "./not-implemented.js";
|
||||||
|
|
||||||
|
export interface EdlContext {
|
||||||
|
profile: PlatformProfile;
|
||||||
|
brandKit: BrandKit | null;
|
||||||
|
assets: Map<
|
||||||
|
string,
|
||||||
|
{ kind: MediaAssetKind; durationMs: number | null; width: number | null; height: number | null }
|
||||||
|
>;
|
||||||
|
/** Artifact ids the plan may reference (music, voice-over, fonts, logos). */
|
||||||
|
artifactIds: Set<string>;
|
||||||
|
/** Music artifacts and whether their license allows commercial use. */
|
||||||
|
music: Map<string, { commercialUse: boolean }>;
|
||||||
|
markIds: Set<string>;
|
||||||
|
personIds: Set<string>;
|
||||||
|
faceTrackIds: Set<string>;
|
||||||
|
templateIds: Set<string>;
|
||||||
|
/** Segments the user reserved for the paid release. */
|
||||||
|
paidOnly: Array<{ assetId: string; startMs: number; endMs: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EdlValidation = { ok: true; edl: Edl } | { ok: false; errors: EdlError[] };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse with EdlSchema, then check references, ranges, overlaps, durations,
|
||||||
|
* safe zones, paid-only use (teaser-style profiles), and music licensing.
|
||||||
|
* Every failure is reported; nothing is silently corrected.
|
||||||
|
*/
|
||||||
|
export function validateEdl(_input: unknown, _context: EdlContext): EdlValidation {
|
||||||
|
return notImplemented("validateEdl");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Output length in ms including intro, outro, speed, and transition overlap. */
|
||||||
|
export function edlDurationMs(_edl: Edl): number {
|
||||||
|
return notImplemented("edlDurationMs");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Map an output timestamp back to the clip and source time it shows. */
|
||||||
|
export function locateOutputTime(
|
||||||
|
_edl: Edl,
|
||||||
|
_outputMs: number,
|
||||||
|
): { clipId: string; assetId: string; sourceMs: number } | null {
|
||||||
|
return notImplemented("locateOutputTime");
|
||||||
|
}
|
||||||
87
packages/media/src/gate.ts
Normal file
87
packages/media/src/gate.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import type {
|
||||||
|
AiAlteration,
|
||||||
|
ContentCategory,
|
||||||
|
Edl,
|
||||||
|
FfprobeReport,
|
||||||
|
GateIssue,
|
||||||
|
GateReport,
|
||||||
|
NormalizedBox,
|
||||||
|
PlatformProfile,
|
||||||
|
} from "@rakazo/contracts";
|
||||||
|
import { notImplemented } from "./not-implemented.js";
|
||||||
|
|
||||||
|
/** Tags a clean export may carry. Everything else is a metadata leak. */
|
||||||
|
export const METADATA_ALLOWED_TAGS: readonly string[] = [];
|
||||||
|
|
||||||
|
export interface GateInput {
|
||||||
|
profile: PlatformProfile;
|
||||||
|
edl: Edl;
|
||||||
|
ffprobe: FfprobeReport;
|
||||||
|
/** Classifier scores on the rendered output. */
|
||||||
|
content: Array<{ tMs: number; scores: Partial<Record<ContentCategory, number>> }>;
|
||||||
|
/**
|
||||||
|
* Faces re-detected on the rendered output. personId null means the face
|
||||||
|
* could not be matched to a known person.
|
||||||
|
*/
|
||||||
|
faces: Array<{ tMs: number; box: NormalizedBox; personId: string | null; coverage: number }>;
|
||||||
|
/** Frames where tracking lost a face that must stay anonymized for longer than allowed. */
|
||||||
|
trackingGaps: Array<{ startMs: number; endMs: number; personId: string | null }>;
|
||||||
|
/** Residual visibility 0..1 of treated marks in the output. */
|
||||||
|
marks: Array<{ markId: string; tMs: number; residualVisibility: number }>;
|
||||||
|
/** People visible in the output with their protection and consent state. */
|
||||||
|
people: Array<{
|
||||||
|
id: string;
|
||||||
|
anonymizeByDefault: string;
|
||||||
|
consentRecordedAt: string | null;
|
||||||
|
ageVerifiedAt: string | null;
|
||||||
|
}>;
|
||||||
|
copy: { title: string | null; caption: string | null; tags: string[] };
|
||||||
|
aiAlterations: AiAlteration[];
|
||||||
|
bundleHasDisclosure: boolean;
|
||||||
|
watermarkBox: NormalizedBox | null;
|
||||||
|
sampledFrames: number;
|
||||||
|
autoFixRound: number;
|
||||||
|
now: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deterministic gate (spec 6). Any blocking issue fails the report. */
|
||||||
|
export function evaluateGate(_input: GateInput): GateReport {
|
||||||
|
return notImplemented("evaluateGate");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkTechnical(
|
||||||
|
_profile: PlatformProfile,
|
||||||
|
_edl: Edl,
|
||||||
|
_probe: FfprobeReport,
|
||||||
|
): GateIssue[] {
|
||||||
|
return notImplemented("checkTechnical");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findMetadataLeaks(_tags: Record<string, string>): string[] {
|
||||||
|
return notImplemented("findMetadataLeaks");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkCopy(
|
||||||
|
_profile: PlatformProfile,
|
||||||
|
_copy: { title: string | null; caption: string | null; tags: string[] },
|
||||||
|
): GateIssue[] {
|
||||||
|
return notImplemented("checkCopy");
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AutoFixResult {
|
||||||
|
edl: Edl;
|
||||||
|
changes: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compliance-only repairs: trim or drop offending clips, change crop, widen
|
||||||
|
* redaction padding, swap to another segment. Never adds obfuscation aimed at
|
||||||
|
* classifiers. Returns null when the round limit is reached or nothing safe applies.
|
||||||
|
*/
|
||||||
|
export function planAutoFix(
|
||||||
|
_edl: Edl,
|
||||||
|
_report: GateReport,
|
||||||
|
_alternatives: Array<{ segmentId: string; assetId: string; startMs: number; endMs: number }>,
|
||||||
|
): AutoFixResult | null {
|
||||||
|
return notImplemented("planAutoFix");
|
||||||
|
}
|
||||||
28
packages/media/src/geo.ts
Normal file
28
packages/media/src/geo.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { notImplemented } from "./not-implemented.js";
|
||||||
|
|
||||||
|
/** Great-circle distance in kilometres. */
|
||||||
|
export function haversineKm(
|
||||||
|
_a: { lat: number; lon: number },
|
||||||
|
_b: { lat: number; lon: number },
|
||||||
|
): number {
|
||||||
|
return notImplemented("haversineKm");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Round a coordinate to two decimals (about 1 km) before storage or display. */
|
||||||
|
export function roundCoordinate(_value: number): number {
|
||||||
|
return notImplemented("roundCoordinate");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cosine similarity of two equal-length vectors. */
|
||||||
|
export function cosineSimilarity(_a: ArrayLike<number>, _b: ArrayLike<number>): number {
|
||||||
|
return notImplemented("cosineSimilarity");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Encode Float32 vectors as little-endian bytes for MediaAnalysis.vectors and centroids. */
|
||||||
|
export function encodeVectors(_vectors: number[][]): Uint8Array {
|
||||||
|
return notImplemented("encodeVectors");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeVectors(_bytes: Uint8Array, _dim: number): Float32Array[] {
|
||||||
|
return notImplemented("decodeVectors");
|
||||||
|
}
|
||||||
8
packages/media/src/index.ts
Normal file
8
packages/media/src/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export * from "./clustering.js";
|
||||||
|
export * from "./compile.js";
|
||||||
|
export * from "./edl.js";
|
||||||
|
export * from "./gate.js";
|
||||||
|
export * from "./geo.js";
|
||||||
|
export * from "./marketing.js";
|
||||||
|
export * from "./profiles.js";
|
||||||
|
export * from "./teaser.js";
|
||||||
66
packages/media/src/marketing.ts
Normal file
66
packages/media/src/marketing.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import type {
|
||||||
|
BrandKit,
|
||||||
|
MetricCsvMapping,
|
||||||
|
MotionTemplateInfo,
|
||||||
|
MotionTemplateRef,
|
||||||
|
PlatformProfile,
|
||||||
|
Utm,
|
||||||
|
} from "@rakazo/contracts";
|
||||||
|
import { notImplemented } from "./not-implemented.js";
|
||||||
|
|
||||||
|
/** Append UTM parameters to an http(s) target. Rejects other schemes. */
|
||||||
|
export function buildTrackingUrl(_targetUrl: string, _utm: Utm): string {
|
||||||
|
return notImplemented("buildTrackingUrl");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeHashtags(_tags: string[], _limit: number): string[] {
|
||||||
|
return notImplemented("normalizeHashtags");
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParsedMetricRow {
|
||||||
|
row: number;
|
||||||
|
publicationKey: string;
|
||||||
|
observedAt: Date;
|
||||||
|
views: number;
|
||||||
|
clicks: number;
|
||||||
|
conversions: number;
|
||||||
|
revenueCents: number;
|
||||||
|
currency: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** RFC 4180 CSV parsing with the saved column mapping. */
|
||||||
|
export function parseMetricsCsv(
|
||||||
|
_csv: string,
|
||||||
|
_mapping: MetricCsvMapping,
|
||||||
|
): { rows: ParsedMetricRow[]; skipped: Array<{ row: number; reason: string }> } {
|
||||||
|
return notImplemented("parseMetricsCsv");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the brand intro fits before the hook window; shorts put the hook first. */
|
||||||
|
export function introFits(_profile: PlatformProfile, _introMs: number): boolean {
|
||||||
|
return notImplemented("introFits");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deterministic intro proposals from the brand kit, tone, and available templates. */
|
||||||
|
export function proposeIntroVariants(
|
||||||
|
_kit: BrandKit,
|
||||||
|
_templates: MotionTemplateInfo[],
|
||||||
|
_count: number,
|
||||||
|
): Array<{ template: MotionTemplateRef; rationale: string }> {
|
||||||
|
return notImplemented("proposeIntroVariants");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Structural brand-kit checks beyond the schema (contrast, duplicate presets). */
|
||||||
|
export function validateBrandKit(_kit: BrandKit): string[] {
|
||||||
|
return notImplemented("validateBrandKit");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Upload checklist markdown for an export bundle. */
|
||||||
|
export function uploadChecklist(_input: {
|
||||||
|
profile: PlatformProfile;
|
||||||
|
aiAltered: boolean;
|
||||||
|
hasCaptions: boolean;
|
||||||
|
trackingUrl: string | null;
|
||||||
|
}): string {
|
||||||
|
return notImplemented("uploadChecklist");
|
||||||
|
}
|
||||||
18
packages/media/src/node.ts
Normal file
18
packages/media/src/node.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { PlatformProfile } from "@rakazo/contracts";
|
||||||
|
import { notImplemented } from "./not-implemented.js";
|
||||||
|
import type { ProfileLoadError } from "./profiles.js";
|
||||||
|
|
||||||
|
/** Directory of the profile JSON files shipped with this package. */
|
||||||
|
export const BUILTIN_PROFILES_DIR = new URL("../profiles/", import.meta.url);
|
||||||
|
|
||||||
|
/** Read every *.json in a directory; missing directory yields no profiles. */
|
||||||
|
export async function loadProfilesFromDir(
|
||||||
|
_dir: string | URL,
|
||||||
|
): Promise<{ profiles: PlatformProfile[]; errors: ProfileLoadError[] }> {
|
||||||
|
return notImplemented("loadProfilesFromDir");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resolve a path inside root; rejects traversal and symlinks that escape it. */
|
||||||
|
export async function resolveInside(_root: string, _relative: string): Promise<string> {
|
||||||
|
return notImplemented("resolveInside");
|
||||||
|
}
|
||||||
4
packages/media/src/not-implemented.ts
Normal file
4
packages/media/src/not-implemented.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/** Phase A scaffold marker. No call site may remain once Media Studio ships. */
|
||||||
|
export function notImplemented(name: string): never {
|
||||||
|
throw new Error(`not implemented: ${name}`);
|
||||||
|
}
|
||||||
38
packages/media/src/profiles.ts
Normal file
38
packages/media/src/profiles.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import type { PlatformProfile, PlatformProfileSummary } from "@rakazo/contracts";
|
||||||
|
import { notImplemented } from "./not-implemented.js";
|
||||||
|
|
||||||
|
export const PROFILE_STALE_DAYS = 90;
|
||||||
|
|
||||||
|
export interface ProfileLoadError {
|
||||||
|
file: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfileSet {
|
||||||
|
profiles: Map<string, { profile: PlatformProfile; origin: "builtin" | "private" }>;
|
||||||
|
errors: ProfileLoadError[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse raw JSON documents; invalid ones become errors and are skipped whole. */
|
||||||
|
export function parseProfiles(_documents: Array<{ file: string; json: unknown }>): {
|
||||||
|
profiles: PlatformProfile[];
|
||||||
|
errors: ProfileLoadError[];
|
||||||
|
} {
|
||||||
|
return notImplemented("parseProfiles");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Private profiles override builtin ones with the same id. */
|
||||||
|
export function mergeProfiles(
|
||||||
|
_builtin: PlatformProfile[],
|
||||||
|
_private: PlatformProfile[],
|
||||||
|
): ProfileSet {
|
||||||
|
return notImplemented("mergeProfiles");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function summarizeProfile(
|
||||||
|
_profile: PlatformProfile,
|
||||||
|
_origin: "builtin" | "private",
|
||||||
|
_now: Date,
|
||||||
|
): PlatformProfileSummary {
|
||||||
|
return notImplemented("summarizeProfile");
|
||||||
|
}
|
||||||
98
packages/media/src/teaser.ts
Normal file
98
packages/media/src/teaser.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import type {
|
||||||
|
BrandKit,
|
||||||
|
Edl,
|
||||||
|
MediaProjectGoal,
|
||||||
|
PlatformProfile,
|
||||||
|
TeaserFeature,
|
||||||
|
TeaserWeights,
|
||||||
|
} from "@rakazo/contracts";
|
||||||
|
import { notImplemented } from "./not-implemented.js";
|
||||||
|
|
||||||
|
export type SegmentFeatures = Partial<Record<TeaserFeature, number>>;
|
||||||
|
|
||||||
|
export function defaultTeaserWeights(_goal: MediaProjectGoal): TeaserWeights {
|
||||||
|
return notImplemented("defaultTeaserWeights");
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScoredSegmentInput {
|
||||||
|
id: string;
|
||||||
|
assetId: string;
|
||||||
|
startMs: number;
|
||||||
|
endMs: number;
|
||||||
|
capturedAt: Date | null;
|
||||||
|
features: SegmentFeatures;
|
||||||
|
paidOnly: boolean;
|
||||||
|
transcript: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scoreSegment(_features: SegmentFeatures, _weights: TeaserWeights): number {
|
||||||
|
return notImplemented("scoreSegment");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 0..1 speech-hook strength from a transcript excerpt (questions, exclamations, numbers, announcements). */
|
||||||
|
export function speechHookScore(_text: string, _language?: string): number {
|
||||||
|
return notImplemented("speechHookScore");
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TeaserSelection {
|
||||||
|
clips: Array<{ segmentId: string; assetId: string; inMs: number; outMs: number }>;
|
||||||
|
hookSegmentId: string | null;
|
||||||
|
/** Why each clip was chosen, for plan rationale. */
|
||||||
|
notes: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick clips for a goal and profile: a hook inside hookWindowMs, rising
|
||||||
|
* tension, cut before the payoff (open loop), never paid-only segments,
|
||||||
|
* total within the profile's duration bounds.
|
||||||
|
*/
|
||||||
|
export function selectTeaserSegments(_input: {
|
||||||
|
segments: ScoredSegmentInput[];
|
||||||
|
profile: PlatformProfile;
|
||||||
|
goal: MediaProjectGoal;
|
||||||
|
targetMs: number;
|
||||||
|
weights: TeaserWeights;
|
||||||
|
endCardMs: number;
|
||||||
|
}): TeaserSelection {
|
||||||
|
return notImplemented("selectTeaserSegments");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Regularized logistic regression of conversion on features; null below TEASER_LEARNING_MIN_SAMPLES. */
|
||||||
|
export function fitTeaserWeights(
|
||||||
|
_samples: Array<{ features: SegmentFeatures; views: number; conversions: number }>,
|
||||||
|
_prior: TeaserWeights,
|
||||||
|
): TeaserWeights | null {
|
||||||
|
return notImplemented("fitTeaserWeights");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deterministic first-cut EDL for a project and profile; the agent refines it. */
|
||||||
|
export function autoPlanEdl(_input: {
|
||||||
|
goal: MediaProjectGoal;
|
||||||
|
profile: PlatformProfile;
|
||||||
|
brandKit: BrandKit | null;
|
||||||
|
segments: ScoredSegmentInput[];
|
||||||
|
weights: TeaserWeights;
|
||||||
|
assets: Map<
|
||||||
|
string,
|
||||||
|
{ durationMs: number | null; width: number | null; height: number | null; hasSpeech: boolean }
|
||||||
|
>;
|
||||||
|
/** Person anonymization defaults to carry into redactions. */
|
||||||
|
people: Array<{ id: string; anonymizeByDefault: string }>;
|
||||||
|
marks: Array<{ id: string; treatment: string; assetId: string | null; personId: string | null }>;
|
||||||
|
ctaText: string | null;
|
||||||
|
}): Edl {
|
||||||
|
return notImplemented("autoPlanEdl");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rankThumbnailFrames(
|
||||||
|
_candidates: Array<{
|
||||||
|
tMs: number;
|
||||||
|
sharpness: number;
|
||||||
|
face: number;
|
||||||
|
thirds: number;
|
||||||
|
contrast: number;
|
||||||
|
}>,
|
||||||
|
_count: number,
|
||||||
|
): number[] {
|
||||||
|
return notImplemented("rankThumbnailFrames");
|
||||||
|
}
|
||||||
8
packages/media/tsconfig.json
Normal file
8
packages/media/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
202
pnpm-lock.yaml
generated
202
pnpm-lock.yaml
generated
@ -78,6 +78,9 @@ importers:
|
|||||||
'@rakazo/logging':
|
'@rakazo/logging':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/logging
|
version: link:../../packages/logging
|
||||||
|
'@rakazo/media':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/media
|
||||||
'@rakazo/memory':
|
'@rakazo/memory':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/memory
|
version: link:../../packages/memory
|
||||||
@ -176,6 +179,9 @@ importers:
|
|||||||
expo-localization:
|
expo-localization:
|
||||||
specifier: 57.0.2
|
specifier: 57.0.2
|
||||||
version: 57.0.2(expo@57.0.26)(react@19.2.3)
|
version: 57.0.2(expo@57.0.26)(react@19.2.3)
|
||||||
|
expo-media-library:
|
||||||
|
specifier: 57.0.5
|
||||||
|
version: 57.0.5(expo@57.0.26)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))
|
||||||
expo-modules-core:
|
expo-modules-core:
|
||||||
specifier: 57.0.20
|
specifier: 57.0.20
|
||||||
version: 57.0.20(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)
|
version: 57.0.20(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)
|
||||||
@ -203,6 +209,9 @@ importers:
|
|||||||
expo-updates:
|
expo-updates:
|
||||||
specifier: 57.0.24
|
specifier: 57.0.24
|
||||||
version: 57.0.24(expo@57.0.26)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)
|
version: 57.0.24(expo@57.0.26)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)
|
||||||
|
expo-video:
|
||||||
|
specifier: 57.0.5
|
||||||
|
version: 57.0.5(expo@57.0.26)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)
|
||||||
react:
|
react:
|
||||||
specifier: 19.2.3
|
specifier: 19.2.3
|
||||||
version: 19.2.3
|
version: 19.2.3
|
||||||
@ -288,6 +297,9 @@ importers:
|
|||||||
'@rakazo/ui-web':
|
'@rakazo/ui-web':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/ui-web
|
version: link:../../packages/ui-web
|
||||||
|
'@tanstack/react-virtual':
|
||||||
|
specifier: 3.14.13
|
||||||
|
version: 3.14.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
better-auth:
|
better-auth:
|
||||||
specifier: 1.7.4
|
specifier: 1.7.4
|
||||||
version: 1.7.4(@opentelemetry/api@1.9.1)(@prisma/client@7.10.0(prisma@7.10.0(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(magicast@0.5.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@7.0.2))(typescript@7.0.2))(mysql2@3.15.3)(pg@8.23.0)(prisma@7.10.0(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(magicast@0.5.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@7.0.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.5.0)(jsdom@30.0.1(@noble/hashes@2.3.0))(vite@8.3.0(@types/node@26.5.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.0)))
|
version: 1.7.4(@opentelemetry/api@1.9.1)(@prisma/client@7.10.0(prisma@7.10.0(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(magicast@0.5.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@7.0.2))(typescript@7.0.2))(mysql2@3.15.3)(pg@8.23.0)(prisma@7.10.0(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(magicast@0.5.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@7.0.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.5.0)(jsdom@30.0.1(@noble/hashes@2.3.0))(vite@8.3.0(@types/node@26.5.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.0)))
|
||||||
@ -579,6 +591,12 @@ importers:
|
|||||||
'@rakazo/logging':
|
'@rakazo/logging':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../logging
|
version: link:../logging
|
||||||
|
'@rakazo/media':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../media
|
||||||
|
'@resvg/resvg-js':
|
||||||
|
specifier: 2.6.2
|
||||||
|
version: 2.6.2
|
||||||
chat:
|
chat:
|
||||||
specifier: 4.40.0
|
specifier: 4.40.0
|
||||||
version: 4.40.0(zod@4.6.2)
|
version: 4.40.0(zod@4.6.2)
|
||||||
@ -800,6 +818,22 @@ importers:
|
|||||||
specifier: 4.1.11
|
specifier: 4.1.11
|
||||||
version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.5.0)(jsdom@30.0.1(@noble/hashes@2.3.0))(vite@8.3.0(@types/node@26.5.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.0))
|
version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.5.0)(jsdom@30.0.1(@noble/hashes@2.3.0))(vite@8.3.0(@types/node@26.5.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.0))
|
||||||
|
|
||||||
|
packages/media:
|
||||||
|
dependencies:
|
||||||
|
'@rakazo/contracts':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../contracts
|
||||||
|
zod:
|
||||||
|
specifier: 4.6.2
|
||||||
|
version: 4.6.2
|
||||||
|
devDependencies:
|
||||||
|
typescript:
|
||||||
|
specifier: 7.0.2
|
||||||
|
version: 7.0.2
|
||||||
|
vitest:
|
||||||
|
specifier: 4.1.11
|
||||||
|
version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.5.0)(jsdom@30.0.1(@noble/hashes@2.3.0))(vite@8.3.0(@types/node@26.5.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.0))
|
||||||
|
|
||||||
packages/memory:
|
packages/memory:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rakazo/adapter-kit':
|
'@rakazo/adapter-kit':
|
||||||
@ -3800,6 +3834,82 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js-android-arm-eabi@2.6.2':
|
||||||
|
resolution: {integrity: sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@resvg/resvg-js-android-arm64@2.6.2':
|
||||||
|
resolution: {integrity: sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@resvg/resvg-js-darwin-arm64@2.6.2':
|
||||||
|
resolution: {integrity: sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@resvg/resvg-js-darwin-x64@2.6.2':
|
||||||
|
resolution: {integrity: sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@resvg/resvg-js-linux-arm-gnueabihf@2.6.2':
|
||||||
|
resolution: {integrity: sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@resvg/resvg-js-linux-arm64-gnu@2.6.2':
|
||||||
|
resolution: {integrity: sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@resvg/resvg-js-linux-arm64-musl@2.6.2':
|
||||||
|
resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@resvg/resvg-js-linux-x64-gnu@2.6.2':
|
||||||
|
resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@resvg/resvg-js-linux-x64-musl@2.6.2':
|
||||||
|
resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@resvg/resvg-js-win32-arm64-msvc@2.6.2':
|
||||||
|
resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@resvg/resvg-js-win32-ia32-msvc@2.6.2':
|
||||||
|
resolution: {integrity: sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@resvg/resvg-js-win32-x64-msvc@2.6.2':
|
||||||
|
resolution: {integrity: sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@resvg/resvg-js@2.6.2':
|
||||||
|
resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
'@rolldown/binding-android-arm-eabi@1.2.8':
|
'@rolldown/binding-android-arm-eabi@1.2.8':
|
||||||
resolution: {integrity: sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==}
|
resolution: {integrity: sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@ -4116,6 +4226,15 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^5.2.0 || ^6 || ^7 || ^8
|
vite: ^5.2.0 || ^6 || ^7 || ^8
|
||||||
|
|
||||||
|
'@tanstack/react-virtual@3.14.13':
|
||||||
|
resolution: {integrity: sha512-JbDTAwtzZ99aOeCrAfW5EsE5KSq5RWh6Af2dtFwyLIIk48Ja7vm6n4axu/43T3vfjFnEGJalxJ1wyYjSQD6bSg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: 19.2.3
|
||||||
|
react-dom: 19.2.3
|
||||||
|
|
||||||
|
'@tanstack/virtual-core@3.17.11':
|
||||||
|
resolution: {integrity: sha512-+ILjvtHup6Y2hzQ6YzwMgX1Q+oQpxEGOXCEsCNaPoIP0VxMbizIBTmYTDtkerkIQS8/CbP1BRuyt8V/8BCsy1g==}
|
||||||
|
|
||||||
'@testcontainers/postgresql@12.1.0':
|
'@testcontainers/postgresql@12.1.0':
|
||||||
resolution: {integrity: sha512-Pjf2VSVNirEPfz36nidyrVAnZvc2YhajOznY4VgyEsvfTd5qiMNOuPq96drREvxAUtXl5SFLX7vXj7sSq4aTcA==}
|
resolution: {integrity: sha512-Pjf2VSVNirEPfz36nidyrVAnZvc2YhajOznY4VgyEsvfTd5qiMNOuPq96drREvxAUtXl5SFLX7vXj7sSq4aTcA==}
|
||||||
|
|
||||||
@ -6165,6 +6284,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
expo: '*'
|
expo: '*'
|
||||||
|
|
||||||
|
expo-media-library@57.0.5:
|
||||||
|
resolution: {integrity: sha512-amSLrLsS7Gl4nCqbc2PlVbHj5jK9kQLsSOQgJ2QasHCSHzYXw6Ui7aolxX5APg9P4TgYPE0yWn46ndifh52bRw==}
|
||||||
|
peerDependencies:
|
||||||
|
expo: '*'
|
||||||
|
react-native: '*'
|
||||||
|
|
||||||
expo-modules-autolinking@57.0.13:
|
expo-modules-autolinking@57.0.13:
|
||||||
resolution: {integrity: sha512-Hj5NjZRlfccazhKab+wK19leRVUr1Y/PUERaRvegIbUSkq4GVxKq6lU9QHlKQyC16MdD/VS4iwsiLivB4o7lvQ==}
|
resolution: {integrity: sha512-Hj5NjZRlfccazhKab+wK19leRVUr1Y/PUERaRvegIbUSkq4GVxKq6lU9QHlKQyC16MdD/VS4iwsiLivB4o7lvQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -6279,6 +6404,13 @@ packages:
|
|||||||
expo-dev-client:
|
expo-dev-client:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
expo-video@57.0.5:
|
||||||
|
resolution: {integrity: sha512-VK2o+d6rb+tVeL2DI6d0F58pRV8d8bctLpOrMJEHRYtMIK5iwvjjrRx2mciYN8/d04SgWC8TkI+wuZ8Sbp/yuA==}
|
||||||
|
peerDependencies:
|
||||||
|
expo: '*'
|
||||||
|
react: 19.2.3
|
||||||
|
react-native: '*'
|
||||||
|
|
||||||
expo@57.0.26:
|
expo@57.0.26:
|
||||||
resolution: {integrity: sha512-U1rsFwcuZNnU61Z/YLnnVC/ZqP0wpxh/rCKUMff3ot/rhLpxFY1zxEqu4vyaexyvBAB1mwpl9lA/ltcHBoQGyg==}
|
resolution: {integrity: sha512-U1rsFwcuZNnU61Z/YLnnVC/ZqP0wpxh/rCKUMff3ot/rhLpxFY1zxEqu4vyaexyvBAB1mwpl9lA/ltcHBoQGyg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -13559,6 +13691,57 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.18
|
'@types/react': 19.2.18
|
||||||
|
|
||||||
|
'@resvg/resvg-js-android-arm-eabi@2.6.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js-android-arm64@2.6.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js-darwin-arm64@2.6.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js-darwin-x64@2.6.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js-linux-arm-gnueabihf@2.6.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js-linux-arm64-gnu@2.6.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js-linux-arm64-musl@2.6.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js-linux-x64-gnu@2.6.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js-linux-x64-musl@2.6.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js-win32-arm64-msvc@2.6.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js-win32-ia32-msvc@2.6.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js-win32-x64-msvc@2.6.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@resvg/resvg-js@2.6.2':
|
||||||
|
optionalDependencies:
|
||||||
|
'@resvg/resvg-js-android-arm-eabi': 2.6.2
|
||||||
|
'@resvg/resvg-js-android-arm64': 2.6.2
|
||||||
|
'@resvg/resvg-js-darwin-arm64': 2.6.2
|
||||||
|
'@resvg/resvg-js-darwin-x64': 2.6.2
|
||||||
|
'@resvg/resvg-js-linux-arm-gnueabihf': 2.6.2
|
||||||
|
'@resvg/resvg-js-linux-arm64-gnu': 2.6.2
|
||||||
|
'@resvg/resvg-js-linux-arm64-musl': 2.6.2
|
||||||
|
'@resvg/resvg-js-linux-x64-gnu': 2.6.2
|
||||||
|
'@resvg/resvg-js-linux-x64-musl': 2.6.2
|
||||||
|
'@resvg/resvg-js-win32-arm64-msvc': 2.6.2
|
||||||
|
'@resvg/resvg-js-win32-ia32-msvc': 2.6.2
|
||||||
|
'@resvg/resvg-js-win32-x64-msvc': 2.6.2
|
||||||
|
|
||||||
'@rolldown/binding-android-arm-eabi@1.2.8':
|
'@rolldown/binding-android-arm-eabi@1.2.8':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -13847,6 +14030,14 @@ snapshots:
|
|||||||
tailwindcss: 4.3.3
|
tailwindcss: 4.3.3
|
||||||
vite: 8.3.0(@types/node@26.5.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.0)
|
vite: 8.3.0(@types/node@26.5.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.0)
|
||||||
|
|
||||||
|
'@tanstack/react-virtual@3.14.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/virtual-core': 3.17.11
|
||||||
|
react: 19.2.3
|
||||||
|
react-dom: 19.2.3(react@19.2.3)
|
||||||
|
|
||||||
|
'@tanstack/virtual-core@3.17.11': {}
|
||||||
|
|
||||||
'@testcontainers/postgresql@12.1.0':
|
'@testcontainers/postgresql@12.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
testcontainers: 12.1.0
|
testcontainers: 12.1.0
|
||||||
@ -16233,6 +16424,11 @@ snapshots:
|
|||||||
expo: 57.0.26(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.16)(expo-router@57.0.24)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3)
|
expo: 57.0.26(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.16)(expo-router@57.0.24)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3)
|
||||||
expo-json-utils: 57.0.2
|
expo-json-utils: 57.0.2
|
||||||
|
|
||||||
|
expo-media-library@57.0.5(expo@57.0.26)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)):
|
||||||
|
dependencies:
|
||||||
|
expo: 57.0.26(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.16)(expo-router@57.0.24)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3)
|
||||||
|
react-native: 0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)
|
||||||
|
|
||||||
expo-modules-autolinking@57.0.13(typescript@6.0.3):
|
expo-modules-autolinking@57.0.13(typescript@6.0.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@expo/require-utils': 57.0.5(typescript@6.0.3)
|
'@expo/require-utils': 57.0.5(typescript@6.0.3)
|
||||||
@ -16391,6 +16587,12 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
expo-video@57.0.5(expo@57.0.26)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3):
|
||||||
|
dependencies:
|
||||||
|
expo: 57.0.26(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.16)(expo-router@57.0.24)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3)
|
||||||
|
react: 19.2.3
|
||||||
|
react-native: 0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)
|
||||||
|
|
||||||
expo@57.0.26(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.16)(expo-router@57.0.24)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3):
|
expo@57.0.26(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.16)(expo-router@57.0.24)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.7
|
'@babel/runtime': 7.29.7
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user