diff --git a/packages/adapters/src/media-context.ts b/packages/adapters/src/media-context.ts index 78f39cd..362a854 100644 --- a/packages/adapters/src/media-context.ts +++ b/packages/adapters/src/media-context.ts @@ -11,10 +11,30 @@ import type { MediaSourceProvider, MotionGraphicsRenderer, } from "@rakazo/adapter-kit"; -import type { ModelTier } from "@rakazo/contracts"; +import type { + AiAlteration, + ClientMediaItem, + CreateMarkInput, + Edl, + EdlError, + FfprobeReport, + GateReport, + IntroVariant, + MarketingCopy, + MarkTreatment, + MediaEditPlan, + MediaMark, + MediaPublication, + MediaRender, + MediaRenderOutput, + MediaRenderQuality, + ModelTier, + PlatformProfile, + Utm, +} from "@rakazo/contracts"; import type { MediaRepos, PrismaClient } from "@rakazo/db"; import { getLogger } from "@rakazo/logging"; -import type { ProfileSet } from "@rakazo/media"; +import type { CompileSource, ProfileSet } from "@rakazo/media"; import type { z } from "zod"; import { aiRecipient } from "./ai-consent.js"; @@ -184,3 +204,152 @@ export function createMediaModelClient(deps: { }, }; } + +// --------------------------------------------------------------------------- +// Service contracts between media slices. Each slice implements its part; the +// API, worker, and agent tools only depend on these interfaces. +// --------------------------------------------------------------------------- + +export type CreateMarkInputType = z.infer; + +export interface MediaActor { + spaceId: string; + userId: string; +} + +export interface MediaIngestService { + /** Client-pushed metadata (Apple Photos, iOS): upsert, apply deletions, enqueue work. */ + pushItems( + actor: MediaActor, + input: { + sourceId: string; + items: ClientMediaItem[]; + deletedExternalIds: string[]; + syncToken: string | null; + complete: boolean; + }, + ): Promise<{ created: number; updated: number; unchanged: number; deleted: number }>; + /** Enqueue an incremental sync of a server-side source. */ + requestSync(actor: MediaActor, sourceId: string): Promise; + /** An upload finished: move the file into place and enqueue proxy and analysis. */ + uploadCompleted( + actor: MediaActor, + input: { assetId: string; tempPath: string; sha256: string; fileName: string }, + ): Promise; + recluster(actor: MediaActor): Promise; + /** Remove a source with its assets and every derived file. */ + removeSource(actor: MediaActor, sourceId: string): Promise; +} + +export interface MediaPreparedSources { + sources: Map; + /** Alterations applied while preparing (inpaint, synthetic identity, voice). */ + aiAlterations: AiAlteration[]; + /** Redactions that could not be honored and fell back, for the plan rationale. */ + fallbacks: string[]; +} + +export interface MediaPrivacyService { + /** + * Resolve every clip's source file and apply all face, mark, and region + * redactions from the EDL plus person and mark defaults. Fails closed: a + * redaction that cannot be applied throws instead of returning an unredacted source. + */ + prepareSources(input: { + actor: MediaActor; + projectId: string; + edl: Edl; + quality: MediaRenderQuality; + workDir: string; + signal?: AbortSignal; + }): Promise; + /** Re-detect on the rendered output and evaluate the full gate. */ + gate(input: { + actor: MediaActor; + projectId: string; + edl: Edl; + profile: PlatformProfile; + outputPath: string; + ffprobe: FfprobeReport; + copy: { title: string | null; caption: string | null; tags: string[] }; + bundleHasDisclosure: boolean; + autoFixRound: number; + signal?: AbortSignal; + }): Promise; + /** Create a mark: segment from the prompt, propagate, score, and suggest a fallback. */ + createMark(actor: MediaActor, input: CreateMarkInputType): Promise; + updateMark( + actor: MediaActor, + input: { + markId: string; + treatment?: MarkTreatment; + label?: string | null; + personId?: string | null; + }, + ): Promise; + /** Delete a person, their biometric data, and their files. */ + removePerson(actor: MediaActor, personId: string): Promise; +} + +export interface MediaRenderService { + start( + actor: MediaActor, + input: { planId: string; quality: MediaRenderQuality }, + ): Promise; + cancel(actor: MediaActor, renderId: string): Promise; + /** Absolute path of a finished output for streaming, or null. */ + outputPath(actor: MediaActor, renderId: string, fileName: string): Promise; +} + +export interface MediaMarketingService { + autoPlan( + actor: MediaActor, + input: { projectId: string; profileIds?: string[] }, + ): Promise; + submitPlan( + actor: MediaActor, + input: { + projectId: string; + edl: unknown; + rationale: string | null; + createdBy: "agent" | "user"; + }, + ): Promise<{ ok: true; plan: MediaEditPlan } | { ok: false; errors: EdlError[] }>; + introVariants( + actor: MediaActor, + input: { brandKitId: string; count: number }, + ): Promise; + thumbnails( + actor: MediaActor, + input: { renderId: string; count: number }, + ): Promise; + copy(actor: MediaActor, input: { renderId: string; language: string }): Promise; + createPublication( + actor: MediaActor, + input: { + renderId: string; + destination: string; + targetUrl: string | null; + utm: Utm | null; + title: string | null; + caption: string | null; + tags: string[]; + thumbnailVariant: string | null; + scheduledAt: string | null; + }, + ): Promise; + bundle(actor: MediaActor, publicationId: string): Promise; + cancelPublication(actor: MediaActor, publicationId: string): Promise; + importMetricsCsv( + actor: MediaActor, + input: { mappingId: string; csv: string }, + ): Promise<{ imported: number; skipped: Array<{ row: number; reason: string }> }>; +} + +export interface MediaServices { + deps: MediaServiceDeps; + ingest: MediaIngestService; + privacy: MediaPrivacyService; + render: MediaRenderService; + marketing: MediaMarketingService; +}