@agentmedia/schema 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/video.ts ADDED
@@ -0,0 +1,235 @@
1
+ // Copyright 2026 agent-media contributors. Apache-2.0 license.
2
+
3
+ /**
4
+ * Video-related enums, types, and validation schemas.
5
+ *
6
+ * This is the SINGLE SOURCE OF TRUTH for all video option values.
7
+ * Every consumer (API, CLI, dashboard, edge functions, docs, MCP, SDKs)
8
+ * must import from here — never hardcode these values.
9
+ *
10
+ * IMPORTANT: Values must exactly match what is currently in production.
11
+ * Any change to this file will propagate to all consumers on next build.
12
+ */
13
+
14
+ import { z } from 'zod';
15
+
16
+ // ── Enums (as const + derived types) ────────────────────────────────────────
17
+
18
+ export const DURATIONS = [5, 10, 15] as const;
19
+ export type Duration = (typeof DURATIONS)[number];
20
+
21
+ export const TONES = ['energetic', 'calm', 'confident', 'dramatic'] as const;
22
+ export type Tone = (typeof TONES)[number];
23
+
24
+ export const MUSIC_GENRES = ['chill', 'energetic', 'corporate', 'dramatic', 'upbeat'] as const;
25
+ export type MusicGenre = (typeof MUSIC_GENRES)[number];
26
+
27
+ export const SUBTITLE_STYLES = [
28
+ 'hormozi', 'minimal', 'bold', 'karaoke', 'clean',
29
+ 'tiktok', 'neon', 'fire', 'glow', 'pop', 'aesthetic',
30
+ 'impact', 'pastel', 'electric', 'boxed', 'gradient', 'spotlight',
31
+ ] as const;
32
+ export type SubtitleStyle = (typeof SUBTITLE_STYLES)[number];
33
+
34
+ export const ASPECT_RATIOS = ['9:16', '16:9', '1:1'] as const;
35
+ export type AspectRatio = (typeof ASPECT_RATIOS)[number];
36
+
37
+ /**
38
+ * Currently supported scene types. The API validator previously accepted
39
+ * 'narration', 'image', and 'transition' but the backend only processes
40
+ * these two. Additional types will be added here once backend handlers exist.
41
+ */
42
+ export const SCENE_TYPES = ['talking_head', 'broll'] as const;
43
+ export type SceneType = (typeof SCENE_TYPES)[number];
44
+
45
+ export const TEMPLATES = [
46
+ 'monologue', 'testimonial', 'product-review', 'problem-solution',
47
+ 'saas-review', 'before-after', 'listicle', 'product-demo',
48
+ ] as const;
49
+ export type Template = (typeof TEMPLATES)[number];
50
+
51
+ export const VOICES = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'] as const;
52
+ export type Voice = (typeof VOICES)[number];
53
+
54
+ export const TTS_PROVIDERS = ['openai', 'elevenlabs', 'hume'] as const;
55
+ export type TTSProvider = (typeof TTS_PROVIDERS)[number];
56
+
57
+ export const REVIEW_ANGLES = ['honest', 'enthusiastic', 'roast', 'tutorial', 'comparison'] as const;
58
+ export type ReviewAngle = (typeof REVIEW_ANGLES)[number];
59
+
60
+ export const PIP_POSITIONS = ['bottom-center', 'bottom-left', 'bottom-right'] as const;
61
+ export type PIPPosition = (typeof PIP_POSITIONS)[number];
62
+
63
+ export const PIP_SIZES = ['small', 'medium', 'large'] as const;
64
+ export type PIPSize = (typeof PIP_SIZES)[number];
65
+
66
+ export const PIP_ANIMATIONS = ['slide-up', 'slide-left', 'slide-right', 'fade', 'scale'] as const;
67
+ export type PIPAnimation = (typeof PIP_ANIMATIONS)[number];
68
+
69
+ export const PIP_FRAME_STYLES = ['none', 'rounded', 'shadow'] as const;
70
+ export type PIPFrameStyle = (typeof PIP_FRAME_STYLES)[number];
71
+
72
+ export const COMPOSITION_MODES = ['pip'] as const;
73
+ export type CompositionMode = (typeof COMPOSITION_MODES)[number];
74
+
75
+ /**
76
+ * B-roll model names. DEPRECATED — backend auto-selects the best model.
77
+ * Kept in schema for backwards compatibility (accept but ignore).
78
+ * Will be removed in schema v2.0.0.
79
+ * @deprecated Use the default — backend auto-selects the best model.
80
+ */
81
+ export const BROLL_MODELS = ['kling3', 'hailuo2', 'wan21'] as const;
82
+ export type BrollModel = (typeof BROLL_MODELS)[number];
83
+
84
+ // ── Validation constants ────────────────────────────────────────────────────
85
+
86
+ export const CREDITS_PER_SECOND = 30;
87
+ export const SCRIPT_GENERATION_CREDIT_SURCHARGE = 5;
88
+
89
+ // ── Zod schemas ─────────────────────────────────────────────────────────────
90
+
91
+ export const PipOptionsSchema = z.object({
92
+ position: z.enum(PIP_POSITIONS).optional(),
93
+ size: z.enum(PIP_SIZES).optional(),
94
+ animation: z.enum(PIP_ANIMATIONS).optional(),
95
+ frame_style: z.enum(PIP_FRAME_STYLES).optional(),
96
+ }).strict();
97
+
98
+ export type PipOptions = z.infer<typeof PipOptionsSchema>;
99
+
100
+ /**
101
+ * Grouped parameter objects — better DX for new integrations.
102
+ * These are optional alternatives to the flat params.
103
+ * The preprocess step flattens them so downstream code stays unchanged.
104
+ */
105
+ export const ActorConfigSchema = z.object({
106
+ slug: z.string().min(1).max(100).optional(),
107
+ persona_slug: z.string().min(1).max(100).optional(),
108
+ face_photo_url: z.string().url().optional(),
109
+ voice: z.string().max(100).optional(),
110
+ }).strict().optional();
111
+
112
+ export const OutputConfigSchema = z.object({
113
+ duration: z.number().refine(
114
+ (v) => (DURATIONS as readonly number[]).includes(v),
115
+ { message: 'duration must be 5, 10, or 15' },
116
+ ).optional(),
117
+ aspect_ratio: z.enum(ASPECT_RATIOS).optional(),
118
+ style: z.string().max(50).optional(),
119
+ music: z.enum(MUSIC_GENRES).optional(),
120
+ cta: z.string().max(100).optional(),
121
+ }).strict().optional();
122
+
123
+ export const BrollConfigSchema = z.object({
124
+ enabled: z.boolean().optional(),
125
+ images: z.array(z.string().url()).max(10).optional(),
126
+ }).strict().optional();
127
+
128
+ /**
129
+ * Preprocess: flatten grouped params into flat params.
130
+ * Accepts BOTH flat and grouped — flat params take precedence.
131
+ */
132
+ function flattenGroupedParams(input: unknown): unknown {
133
+ if (typeof input !== 'object' || input === null) return input;
134
+ const data = { ...(input as Record<string, unknown>) };
135
+
136
+ // Flatten actor config
137
+ const actor = data.actor as Record<string, unknown> | undefined;
138
+ if (actor) {
139
+ if (actor.slug && !data.actor_slug) data.actor_slug = actor.slug;
140
+ if (actor.persona_slug && !data.persona_slug) data.persona_slug = actor.persona_slug;
141
+ if (actor.face_photo_url && !data.face_photo_url) data.face_photo_url = actor.face_photo_url;
142
+ if (actor.voice && !data.voice) data.voice = actor.voice;
143
+ delete data.actor;
144
+ }
145
+
146
+ // Flatten output config
147
+ const output = data.output as Record<string, unknown> | undefined;
148
+ if (output) {
149
+ if (output.duration && !data.target_duration) data.target_duration = output.duration;
150
+ if (output.aspect_ratio && !data.aspect_ratio) data.aspect_ratio = output.aspect_ratio;
151
+ if (output.style && !data.style) data.style = output.style;
152
+ if (output.music && !data.music) data.music = output.music;
153
+ if (output.cta && !data.cta) data.cta = output.cta;
154
+ delete data.output;
155
+ }
156
+
157
+ // Flatten broll config
158
+ const broll = data.broll as Record<string, unknown> | undefined;
159
+ if (broll) {
160
+ if (broll.enabled !== undefined && data.allow_broll === undefined) data.allow_broll = broll.enabled;
161
+ if (broll.images && !data.broll_images) data.broll_images = broll.images;
162
+ delete data.broll;
163
+ }
164
+
165
+ return data;
166
+ }
167
+
168
+ /**
169
+ * Zod schema for video generation.
170
+ *
171
+ * Accepts both flat params and grouped objects:
172
+ * Flat: { actor_slug: "sofia", target_duration: 10, allow_broll: true }
173
+ * Grouped: { actor: { slug: "sofia" }, output: { duration: 10 }, broll: { enabled: true } }
174
+ * Mixed: { actor: { slug: "sofia" }, target_duration: 10, allow_broll: true }
175
+ *
176
+ * Flat params take precedence over grouped when both are provided.
177
+ */
178
+ export const CreateVideoSchema = z.preprocess(flattenGroupedParams, z.object({
179
+ script: z.string().min(50).max(3000).optional(),
180
+ prompt: z.string().min(1).max(1000).optional(),
181
+ product_url: z.string().url().optional(),
182
+ actor_slug: z.string().min(1).max(100).optional(),
183
+ persona_slug: z.string().min(1).max(100).optional(),
184
+ face_photo_url: z.string().url().optional(),
185
+ voice: z.string().max(100).optional(),
186
+ target_duration: z.number().refine(
187
+ (v) => (DURATIONS as readonly number[]).includes(v),
188
+ { message: 'target_duration must be 5, 10, or 15' },
189
+ ).optional(),
190
+ style: z.string().max(50).optional(),
191
+ tone: z.enum(TONES).optional(),
192
+ voice_speed: z.number().min(0.7).max(1.5).optional(),
193
+ music: z.enum(MUSIC_GENRES).optional(),
194
+ cta: z.string().max(100).optional(),
195
+ aspect_ratio: z.enum(ASPECT_RATIOS).optional(),
196
+ template: z.string().max(50).optional(),
197
+ composition_mode: z.literal('pip').optional(),
198
+ pip_options: PipOptionsSchema.optional(),
199
+ allow_broll: z.boolean().optional(),
200
+ broll_model: z.enum(BROLL_MODELS).optional(),
201
+ broll_images: z.array(z.string().url()).max(10).optional(),
202
+ product_image_url: z.string().url().optional(),
203
+ dub_language: z.string().max(10).optional(),
204
+ webhook_url: z.string().url().optional(),
205
+ scenes: z.array(
206
+ z.object({
207
+ type: z.enum(SCENE_TYPES).optional(),
208
+ }).passthrough(),
209
+ ).max(30).optional(),
210
+ }).refine(
211
+ (data) => data.script !== undefined || data.prompt !== undefined,
212
+ { message: 'Either script or prompt is required' },
213
+ ));
214
+
215
+ export type CreateVideoInput = z.infer<typeof CreateVideoSchema>;
216
+
217
+ export const SubtitleSchema = z.object({
218
+ video_url: z.string().url(),
219
+ style: z.enum(SUBTITLE_STYLES).optional(),
220
+ });
221
+
222
+ export type SubtitleInput = z.infer<typeof SubtitleSchema>;
223
+
224
+ export const ProductReviewSchema = z.object({
225
+ product_url: z.string().url(),
226
+ angle: z.enum(REVIEW_ANGLES).optional(),
227
+ tone: z.enum(TONES).optional(),
228
+ actor_slug: z.string().min(1).max(100).optional(),
229
+ target_duration: z.number().refine(
230
+ (v) => (DURATIONS as readonly number[]).includes(v),
231
+ { message: 'target_duration must be 5, 10, or 15' },
232
+ ).optional(),
233
+ });
234
+
235
+ export type ProductReviewInput = z.infer<typeof ProductReviewSchema>;
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "declaration": true
7
+ },
8
+ "include": ["src"]
9
+ }