import * as z from "zod"; import { Id } from "./ids.js"; /** What a media job does. Generation runs on the configured image/video backend, edits run locally. */ export const MediaOperationSchema = z.enum([ "generate_image", "generate_video", "edit_image", "edit_video", "export_video", "transcribe", ]); export type MediaOperation = z.infer; /** `waiting` means the job is queued behind another GPU job or for free GPU memory. */ export const MediaJobStatusSchema = z.enum([ "queued", "waiting", "running", "done", "failed", "cancelled", ]); export type MediaJobStatus = z.infer; export const MEDIA_JOB_ACTIVE_STATUSES = [ "queued", "waiting", "running", ] as const satisfies readonly MediaJobStatus[]; export const MediaOutputKindSchema = z.enum(["image", "video", "audio", "file"]); export type MediaOutputKind = z.infer; /** * Output formats for video cuts and exports. Width and height are the encoded frame; every * preset encodes H.264 + AAC in MP4 so the result plays everywhere it is posted. */ export const MEDIA_PRESETS = { vertical: { aspect: "9:16", width: 1080, height: 1920 }, landscape: { aspect: "16:9", width: 1920, height: 1080 }, square: { aspect: "1:1", width: 1080, height: 1080 }, portrait: { aspect: "4:5", width: 1080, height: 1350 }, classic: { aspect: "4:3", width: 1440, height: 1080 }, cinema: { aspect: "21:9", width: 2560, height: 1080 }, } as const; export const MediaPresetIdSchema = z.enum([ "vertical", "landscape", "square", "portrait", "classic", "cinema", ]); export type MediaPresetId = z.infer; export type MediaPreset = (typeof MEDIA_PRESETS)[MediaPresetId]; /** How a frame of another aspect ratio fills a preset. */ export const MediaFitSchema = z.enum(["crop", "pad", "blur"]); export type MediaFit = z.infer; export const MediaOutputSchema = z.object({ artifactId: Id, kind: MediaOutputKindSchema, mimeType: z.string(), name: z.string(), size: z.number().int().nonnegative(), width: z.number().int().positive().optional(), height: z.number().int().positive().optional(), durationMs: z.number().int().nonnegative().optional(), posterArtifactId: Id.optional(), /** The artifact this output was made from, for version history. */ parentArtifactId: Id.optional(), preset: MediaPresetIdSchema.optional(), }); export type MediaOutput = z.infer; /** The live media card a job keeps up to date in the conversation. */ export const MediaJobBlockSchema = z.object({ kind: z.literal("media"), jobId: Id, operation: MediaOperationSchema, status: MediaJobStatusSchema, /** Short label: the prompt, or what the edit does. */ title: z.string(), progress: z.number().min(0).max(1).optional(), error: z.string().optional(), outputs: z.array(MediaOutputSchema), }); export type MediaJobBlock = z.infer; export const VideoBlockSchema = z.object({ kind: z.literal("video"), artifactId: Id, mimeType: z.string(), name: z.string(), size: z.number().int().nonnegative(), durationMs: z.number().int().nonnegative().optional(), width: z.number().int().positive().optional(), height: z.number().int().positive().optional(), posterArtifactId: Id.optional(), }); export type VideoBlock = z.infer; export const AudioBlockSchema = z.object({ kind: z.literal("audio"), artifactId: Id, mimeType: z.string(), name: z.string(), size: z.number().int().nonnegative(), durationMs: z.number().int().nonnegative().optional(), }); export type AudioBlock = z.infer;