@contractspec/lib.video-gen 1.45.0 → 1.46.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.
Files changed (61) hide show
  1. package/README.md +37 -0
  2. package/dist/browser/docs/rendering.docblock.js +4 -4
  3. package/dist/browser/docs/video-gen.docblock.js +1 -1
  4. package/dist/browser/generators/index.js +601 -192
  5. package/dist/browser/generators/scene-planner.js +527 -23
  6. package/dist/browser/generators/script-generator.js +528 -18
  7. package/dist/browser/generators/video-generator.js +601 -191
  8. package/dist/browser/i18n/catalogs/en.js +160 -0
  9. package/dist/browser/i18n/catalogs/es.js +160 -0
  10. package/dist/browser/i18n/catalogs/fr.js +160 -0
  11. package/dist/browser/i18n/catalogs/index.js +462 -0
  12. package/dist/browser/i18n/index.js +534 -0
  13. package/dist/browser/i18n/keys.js +54 -0
  14. package/dist/browser/i18n/locale.js +21 -0
  15. package/dist/browser/i18n/messages.js +474 -0
  16. package/dist/browser/index.js +601 -192
  17. package/dist/docs/rendering.docblock.js +4 -4
  18. package/dist/docs/video-gen.docblock.js +1 -1
  19. package/dist/generators/index.d.ts +0 -2
  20. package/dist/generators/index.js +601 -192
  21. package/dist/generators/scene-planner.d.ts +2 -0
  22. package/dist/generators/scene-planner.js +527 -23
  23. package/dist/generators/script-generator.d.ts +2 -0
  24. package/dist/generators/script-generator.js +528 -18
  25. package/dist/generators/video-generator.d.ts +6 -5
  26. package/dist/generators/video-generator.js +601 -191
  27. package/dist/i18n/catalogs/en.d.ts +8 -0
  28. package/dist/i18n/catalogs/en.js +155 -0
  29. package/dist/i18n/catalogs/es.d.ts +6 -0
  30. package/dist/i18n/catalogs/es.js +155 -0
  31. package/dist/i18n/catalogs/fr.d.ts +6 -0
  32. package/dist/i18n/catalogs/fr.js +155 -0
  33. package/dist/i18n/catalogs/index.d.ts +8 -0
  34. package/dist/i18n/catalogs/index.js +457 -0
  35. package/dist/i18n/i18n.test.d.ts +1 -0
  36. package/dist/i18n/index.d.ts +22 -0
  37. package/dist/i18n/index.js +529 -0
  38. package/dist/i18n/keys.d.ts +116 -0
  39. package/dist/i18n/keys.js +49 -0
  40. package/dist/i18n/locale.d.ts +8 -0
  41. package/dist/i18n/locale.js +16 -0
  42. package/dist/i18n/messages.d.ts +14 -0
  43. package/dist/i18n/messages.js +469 -0
  44. package/dist/index.js +601 -192
  45. package/dist/node/docs/rendering.docblock.js +4 -4
  46. package/dist/node/docs/video-gen.docblock.js +1 -1
  47. package/dist/node/generators/index.js +601 -192
  48. package/dist/node/generators/scene-planner.js +527 -23
  49. package/dist/node/generators/script-generator.js +528 -18
  50. package/dist/node/generators/video-generator.js +601 -191
  51. package/dist/node/i18n/catalogs/en.js +155 -0
  52. package/dist/node/i18n/catalogs/es.js +155 -0
  53. package/dist/node/i18n/catalogs/fr.js +155 -0
  54. package/dist/node/i18n/catalogs/index.js +457 -0
  55. package/dist/node/i18n/index.js +529 -0
  56. package/dist/node/i18n/keys.js +49 -0
  57. package/dist/node/i18n/locale.js +16 -0
  58. package/dist/node/i18n/messages.js +469 -0
  59. package/dist/node/index.js +601 -192
  60. package/dist/types.d.ts +14 -3
  61. package/package.json +149 -6
@@ -41,17 +41,528 @@ function getAllFormatVariants() {
41
41
  ];
42
42
  }
43
43
 
44
+ // src/i18n/catalogs/en.ts
45
+ import { defineTranslation } from "@contractspec/lib.contracts-spec/translations";
46
+ var enMessages = defineTranslation({
47
+ meta: {
48
+ key: "video-gen.messages",
49
+ version: "1.0.0",
50
+ domain: "video-gen",
51
+ description: "All user-facing, LLM-facing, and developer-facing strings for the video-gen package",
52
+ owners: ["platform"],
53
+ stability: "experimental"
54
+ },
55
+ locale: "en",
56
+ fallback: "en",
57
+ messages: {
58
+ "prompt.script.system": {
59
+ value: `You are a video narration script writer.
60
+ Write a narration script for a short video (30-60 seconds).
61
+ {styleGuide}
62
+
63
+ Return JSON with shape:
64
+ {
65
+ "segments": [{ "sceneId": string, "text": string }],
66
+ "fullText": string
67
+ }
68
+
69
+ Scene IDs should be: "intro", "problems", "solutions", "metrics", "cta".
70
+ Only include segments that are relevant to the brief content.`,
71
+ description: "Script generator LLM system prompt",
72
+ placeholders: [{ name: "styleGuide", type: "string" }]
73
+ },
74
+ "prompt.scenePlanner.system": {
75
+ value: `You are a video scene planner for ContractSpec marketing/documentation videos.
76
+ Given a content brief, break it into video scenes.
77
+
78
+ Each scene must have:
79
+ - compositionId: one of "ApiOverview", "SocialClip", "TerminalDemo"
80
+ - props: the input props for that composition (see type definitions)
81
+ - durationInFrames: duration at {fps}fps
82
+ - narrationText: what the narrator says during this scene
83
+
84
+ Return a JSON object with shape:
85
+ {
86
+ "scenes": [{ "compositionId": string, "props": object, "durationInFrames": number, "narrationText": string }],
87
+ "narrationScript": string
88
+ }
89
+
90
+ Keep the total duration around {targetSeconds} seconds.
91
+ Prioritize clarity and pacing. Each scene should communicate one idea.`,
92
+ description: "Scene planner LLM system prompt",
93
+ placeholders: [
94
+ { name: "fps", type: "number" },
95
+ { name: "targetSeconds", type: "number" }
96
+ ]
97
+ },
98
+ "prompt.style.professional": {
99
+ value: "Use a clear, authoritative, professional tone. Be concise and direct.",
100
+ description: "Style guide for professional narration"
101
+ },
102
+ "prompt.style.casual": {
103
+ value: "Use a friendly, conversational tone. Be approachable and relatable.",
104
+ description: "Style guide for casual narration"
105
+ },
106
+ "prompt.style.technical": {
107
+ value: "Use precise technical language. Be detailed and accurate.",
108
+ description: "Style guide for technical narration"
109
+ },
110
+ "script.segment.challenge": {
111
+ value: "The challenge: {content}",
112
+ description: "Narration segment prefix for problems",
113
+ placeholders: [{ name: "content", type: "string" }]
114
+ },
115
+ "script.segment.solution": {
116
+ value: "The solution: {content}",
117
+ description: "Narration segment prefix for solutions",
118
+ placeholders: [{ name: "content", type: "string" }]
119
+ },
120
+ "script.segment.results": {
121
+ value: "The results: {content}",
122
+ description: "Narration segment prefix for metrics",
123
+ placeholders: [{ name: "content", type: "string" }]
124
+ },
125
+ "scene.cta.default": {
126
+ value: "Learn more",
127
+ description: "Default call-to-action text for scenes"
128
+ },
129
+ "scene.hook.problem": {
130
+ value: "The Problem",
131
+ description: "Scene hook label for problem statement"
132
+ },
133
+ "scene.narration.problem": {
134
+ value: "The problem: {content}",
135
+ description: "Scene narration for problem statement",
136
+ placeholders: [{ name: "content", type: "string" }]
137
+ },
138
+ "scene.hook.solution": {
139
+ value: "The Solution",
140
+ description: "Scene hook label for solution"
141
+ },
142
+ "scene.narration.solution": {
143
+ value: "The solution: {content}",
144
+ description: "Scene narration for solution",
145
+ placeholders: [{ name: "content", type: "string" }]
146
+ },
147
+ "scene.hook.results": {
148
+ value: "Results",
149
+ description: "Scene hook label for results/metrics"
150
+ },
151
+ "composition.apiOverview.generates": {
152
+ value: "Generates:",
153
+ description: "ApiOverview heading for generated outputs"
154
+ },
155
+ "composition.apiOverview.tagline": {
156
+ value: "One spec. Every surface.",
157
+ description: "ApiOverview default tagline"
158
+ },
159
+ "composition.apiOverview.output.rest": {
160
+ value: "REST Endpoint",
161
+ description: "Generated output label: REST"
162
+ },
163
+ "composition.apiOverview.output.graphql": {
164
+ value: "GraphQL Mutation",
165
+ description: "Generated output label: GraphQL"
166
+ },
167
+ "composition.apiOverview.output.prisma": {
168
+ value: "Prisma Model",
169
+ description: "Generated output label: Prisma"
170
+ },
171
+ "composition.apiOverview.output.typescript": {
172
+ value: "TypeScript SDK",
173
+ description: "Generated output label: TypeScript SDK"
174
+ },
175
+ "composition.apiOverview.output.mcp": {
176
+ value: "MCP Tool",
177
+ description: "Generated output label: MCP Tool"
178
+ },
179
+ "composition.apiOverview.output.openapi": {
180
+ value: "OpenAPI Spec",
181
+ description: "Generated output label: OpenAPI"
182
+ },
183
+ "composition.socialClip.cta": {
184
+ value: "Learn more",
185
+ description: "SocialClip default CTA"
186
+ },
187
+ "composition.terminal.title": {
188
+ value: "Terminal",
189
+ description: "TerminalDemo default window title"
190
+ }
191
+ }
192
+ });
193
+
194
+ // src/i18n/catalogs/fr.ts
195
+ import { defineTranslation as defineTranslation2 } from "@contractspec/lib.contracts-spec/translations";
196
+ var frMessages = defineTranslation2({
197
+ meta: {
198
+ key: "video-gen.messages",
199
+ version: "1.0.0",
200
+ domain: "video-gen",
201
+ description: "French translations for the video-gen package",
202
+ owners: ["platform"],
203
+ stability: "experimental"
204
+ },
205
+ locale: "fr",
206
+ fallback: "en",
207
+ messages: {
208
+ "prompt.script.system": {
209
+ value: `Vous êtes un rédacteur de scripts de narration vidéo.
210
+ Écrivez un script de narration pour une courte vidéo (30-60 secondes).
211
+ {styleGuide}
212
+
213
+ Retournez du JSON avec la forme :
214
+ {
215
+ "segments": [{ "sceneId": string, "text": string }],
216
+ "fullText": string
217
+ }
218
+
219
+ Les identifiants de scène doivent être : "intro", "problems", "solutions", "metrics", "cta".
220
+ N'incluez que les segments pertinents au brief.`,
221
+ description: "Script generator LLM system prompt",
222
+ placeholders: [{ name: "styleGuide", type: "string" }]
223
+ },
224
+ "prompt.scenePlanner.system": {
225
+ value: `Vous êtes un planificateur de scènes vidéo pour les vidéos marketing/documentation de ContractSpec.
226
+ À partir d'un brief, décomposez-le en scènes vidéo.
227
+
228
+ Chaque scène doit avoir :
229
+ - compositionId : un parmi "ApiOverview", "SocialClip", "TerminalDemo"
230
+ - props : les propriétés d'entrée de cette composition
231
+ - durationInFrames : durée à {fps} fps
232
+ - narrationText : ce que le narrateur dit pendant cette scène
233
+
234
+ Retournez un objet JSON avec la forme :
235
+ {
236
+ "scenes": [{ "compositionId": string, "props": object, "durationInFrames": number, "narrationText": string }],
237
+ "narrationScript": string
238
+ }
239
+
240
+ Gardez la durée totale autour de {targetSeconds} secondes.
241
+ Privilégiez la clarté et le rythme. Chaque scène doit communiquer une idée.`,
242
+ description: "Scene planner LLM system prompt",
243
+ placeholders: [
244
+ { name: "fps", type: "number" },
245
+ { name: "targetSeconds", type: "number" }
246
+ ]
247
+ },
248
+ "prompt.style.professional": {
249
+ value: "Utilisez un ton clair, autoritaire et professionnel. Soyez concis et direct.",
250
+ description: "Style guide for professional narration"
251
+ },
252
+ "prompt.style.casual": {
253
+ value: "Utilisez un ton amical et conversationnel. Soyez accessible et proche.",
254
+ description: "Style guide for casual narration"
255
+ },
256
+ "prompt.style.technical": {
257
+ value: "Utilisez un langage technique précis. Soyez détaillé et exact.",
258
+ description: "Style guide for technical narration"
259
+ },
260
+ "script.segment.challenge": {
261
+ value: "Le défi : {content}",
262
+ description: "Narration segment prefix for problems",
263
+ placeholders: [{ name: "content", type: "string" }]
264
+ },
265
+ "script.segment.solution": {
266
+ value: "La solution : {content}",
267
+ description: "Narration segment prefix for solutions",
268
+ placeholders: [{ name: "content", type: "string" }]
269
+ },
270
+ "script.segment.results": {
271
+ value: "Les résultats : {content}",
272
+ description: "Narration segment prefix for metrics",
273
+ placeholders: [{ name: "content", type: "string" }]
274
+ },
275
+ "scene.cta.default": {
276
+ value: "En savoir plus",
277
+ description: "Default call-to-action text for scenes"
278
+ },
279
+ "scene.hook.problem": {
280
+ value: "Le problème",
281
+ description: "Scene hook label for problem statement"
282
+ },
283
+ "scene.narration.problem": {
284
+ value: "Le problème : {content}",
285
+ description: "Scene narration for problem statement",
286
+ placeholders: [{ name: "content", type: "string" }]
287
+ },
288
+ "scene.hook.solution": {
289
+ value: "La solution",
290
+ description: "Scene hook label for solution"
291
+ },
292
+ "scene.narration.solution": {
293
+ value: "La solution : {content}",
294
+ description: "Scene narration for solution",
295
+ placeholders: [{ name: "content", type: "string" }]
296
+ },
297
+ "scene.hook.results": {
298
+ value: "Résultats",
299
+ description: "Scene hook label for results/metrics"
300
+ },
301
+ "composition.apiOverview.generates": {
302
+ value: "Génère :",
303
+ description: "ApiOverview heading for generated outputs"
304
+ },
305
+ "composition.apiOverview.tagline": {
306
+ value: "Un spec. Toutes les surfaces.",
307
+ description: "ApiOverview default tagline"
308
+ },
309
+ "composition.apiOverview.output.rest": {
310
+ value: "Endpoint REST",
311
+ description: "Generated output label: REST"
312
+ },
313
+ "composition.apiOverview.output.graphql": {
314
+ value: "Mutation GraphQL",
315
+ description: "Generated output label: GraphQL"
316
+ },
317
+ "composition.apiOverview.output.prisma": {
318
+ value: "Modèle Prisma",
319
+ description: "Generated output label: Prisma"
320
+ },
321
+ "composition.apiOverview.output.typescript": {
322
+ value: "SDK TypeScript",
323
+ description: "Generated output label: TypeScript SDK"
324
+ },
325
+ "composition.apiOverview.output.mcp": {
326
+ value: "Outil MCP",
327
+ description: "Generated output label: MCP Tool"
328
+ },
329
+ "composition.apiOverview.output.openapi": {
330
+ value: "Spec OpenAPI",
331
+ description: "Generated output label: OpenAPI"
332
+ },
333
+ "composition.socialClip.cta": {
334
+ value: "En savoir plus",
335
+ description: "SocialClip default CTA"
336
+ },
337
+ "composition.terminal.title": {
338
+ value: "Terminal",
339
+ description: "TerminalDemo default window title"
340
+ }
341
+ }
342
+ });
343
+
344
+ // src/i18n/catalogs/es.ts
345
+ import { defineTranslation as defineTranslation3 } from "@contractspec/lib.contracts-spec/translations";
346
+ var esMessages = defineTranslation3({
347
+ meta: {
348
+ key: "video-gen.messages",
349
+ version: "1.0.0",
350
+ domain: "video-gen",
351
+ description: "Spanish translations for the video-gen package",
352
+ owners: ["platform"],
353
+ stability: "experimental"
354
+ },
355
+ locale: "es",
356
+ fallback: "en",
357
+ messages: {
358
+ "prompt.script.system": {
359
+ value: `Eres un redactor de guiones de narración para vídeo.
360
+ Escribe un guión de narración para un vídeo corto (30-60 segundos).
361
+ {styleGuide}
362
+
363
+ Devuelve JSON con la forma:
364
+ {
365
+ "segments": [{ "sceneId": string, "text": string }],
366
+ "fullText": string
367
+ }
368
+
369
+ Los identificadores de escena deben ser: "intro", "problems", "solutions", "metrics", "cta".
370
+ Incluye solo los segmentos relevantes para el brief.`,
371
+ description: "Script generator LLM system prompt",
372
+ placeholders: [{ name: "styleGuide", type: "string" }]
373
+ },
374
+ "prompt.scenePlanner.system": {
375
+ value: `Eres un planificador de escenas de vídeo para vídeos de marketing/documentación de ContractSpec.
376
+ Dado un brief de contenido, divídelo en escenas de vídeo.
377
+
378
+ Cada escena debe tener:
379
+ - compositionId: uno de "ApiOverview", "SocialClip", "TerminalDemo"
380
+ - props: las propiedades de entrada de esa composición
381
+ - durationInFrames: duración a {fps} fps
382
+ - narrationText: lo que dice el narrador durante esta escena
383
+
384
+ Devuelve un objeto JSON con la forma:
385
+ {
386
+ "scenes": [{ "compositionId": string, "props": object, "durationInFrames": number, "narrationText": string }],
387
+ "narrationScript": string
388
+ }
389
+
390
+ Mantén la duración total alrededor de {targetSeconds} segundos.
391
+ Prioriza la claridad y el ritmo. Cada escena debe comunicar una idea.`,
392
+ description: "Scene planner LLM system prompt",
393
+ placeholders: [
394
+ { name: "fps", type: "number" },
395
+ { name: "targetSeconds", type: "number" }
396
+ ]
397
+ },
398
+ "prompt.style.professional": {
399
+ value: "Usa un tono claro, autoritario y profesional. Sé conciso y directo.",
400
+ description: "Style guide for professional narration"
401
+ },
402
+ "prompt.style.casual": {
403
+ value: "Usa un tono amigable y conversacional. Sé accesible y cercano.",
404
+ description: "Style guide for casual narration"
405
+ },
406
+ "prompt.style.technical": {
407
+ value: "Usa un lenguaje técnico preciso. Sé detallado y exacto.",
408
+ description: "Style guide for technical narration"
409
+ },
410
+ "script.segment.challenge": {
411
+ value: "El desafío: {content}",
412
+ description: "Narration segment prefix for problems",
413
+ placeholders: [{ name: "content", type: "string" }]
414
+ },
415
+ "script.segment.solution": {
416
+ value: "La solución: {content}",
417
+ description: "Narration segment prefix for solutions",
418
+ placeholders: [{ name: "content", type: "string" }]
419
+ },
420
+ "script.segment.results": {
421
+ value: "Los resultados: {content}",
422
+ description: "Narration segment prefix for metrics",
423
+ placeholders: [{ name: "content", type: "string" }]
424
+ },
425
+ "scene.cta.default": {
426
+ value: "Más información",
427
+ description: "Default call-to-action text for scenes"
428
+ },
429
+ "scene.hook.problem": {
430
+ value: "El problema",
431
+ description: "Scene hook label for problem statement"
432
+ },
433
+ "scene.narration.problem": {
434
+ value: "El problema: {content}",
435
+ description: "Scene narration for problem statement",
436
+ placeholders: [{ name: "content", type: "string" }]
437
+ },
438
+ "scene.hook.solution": {
439
+ value: "La solución",
440
+ description: "Scene hook label for solution"
441
+ },
442
+ "scene.narration.solution": {
443
+ value: "La solución: {content}",
444
+ description: "Scene narration for solution",
445
+ placeholders: [{ name: "content", type: "string" }]
446
+ },
447
+ "scene.hook.results": {
448
+ value: "Resultados",
449
+ description: "Scene hook label for results/metrics"
450
+ },
451
+ "composition.apiOverview.generates": {
452
+ value: "Genera:",
453
+ description: "ApiOverview heading for generated outputs"
454
+ },
455
+ "composition.apiOverview.tagline": {
456
+ value: "Una spec. Todas las superficies.",
457
+ description: "ApiOverview default tagline"
458
+ },
459
+ "composition.apiOverview.output.rest": {
460
+ value: "Endpoint REST",
461
+ description: "Generated output label: REST"
462
+ },
463
+ "composition.apiOverview.output.graphql": {
464
+ value: "Mutación GraphQL",
465
+ description: "Generated output label: GraphQL"
466
+ },
467
+ "composition.apiOverview.output.prisma": {
468
+ value: "Modelo Prisma",
469
+ description: "Generated output label: Prisma"
470
+ },
471
+ "composition.apiOverview.output.typescript": {
472
+ value: "SDK TypeScript",
473
+ description: "Generated output label: TypeScript SDK"
474
+ },
475
+ "composition.apiOverview.output.mcp": {
476
+ value: "Herramienta MCP",
477
+ description: "Generated output label: MCP Tool"
478
+ },
479
+ "composition.apiOverview.output.openapi": {
480
+ value: "Spec OpenAPI",
481
+ description: "Generated output label: OpenAPI"
482
+ },
483
+ "composition.socialClip.cta": {
484
+ value: "Más información",
485
+ description: "SocialClip default CTA"
486
+ },
487
+ "composition.terminal.title": {
488
+ value: "Terminal",
489
+ description: "TerminalDemo default window title"
490
+ }
491
+ }
492
+ });
493
+
494
+ // src/i18n/messages.ts
495
+ import {
496
+ createI18nFactory
497
+ } from "@contractspec/lib.contracts-spec/translations";
498
+ var factory = createI18nFactory({
499
+ specKey: "video-gen.messages",
500
+ catalogs: [enMessages, frMessages, esMessages]
501
+ });
502
+ var createVideoGenI18n = factory.create;
503
+ var getDefaultI18n = factory.getDefault;
504
+ var resetI18nRegistry = factory.resetRegistry;
505
+
506
+ // src/i18n/locale.ts
507
+ import {
508
+ DEFAULT_LOCALE,
509
+ SUPPORTED_LOCALES,
510
+ resolveLocale,
511
+ isSupportedLocale
512
+ } from "@contractspec/lib.contracts-spec/translations";
513
+
514
+ // src/i18n/keys.ts
515
+ var PROMPT_KEYS = {
516
+ "prompt.script.system": "prompt.script.system",
517
+ "prompt.scenePlanner.system": "prompt.scenePlanner.system",
518
+ "prompt.style.professional": "prompt.style.professional",
519
+ "prompt.style.casual": "prompt.style.casual",
520
+ "prompt.style.technical": "prompt.style.technical"
521
+ };
522
+ var SCRIPT_KEYS = {
523
+ "script.segment.challenge": "script.segment.challenge",
524
+ "script.segment.solution": "script.segment.solution",
525
+ "script.segment.results": "script.segment.results"
526
+ };
527
+ var SCENE_KEYS = {
528
+ "scene.cta.default": "scene.cta.default",
529
+ "scene.hook.problem": "scene.hook.problem",
530
+ "scene.narration.problem": "scene.narration.problem",
531
+ "scene.hook.solution": "scene.hook.solution",
532
+ "scene.narration.solution": "scene.narration.solution",
533
+ "scene.hook.results": "scene.hook.results"
534
+ };
535
+ var COMPOSITION_KEYS = {
536
+ "composition.apiOverview.generates": "composition.apiOverview.generates",
537
+ "composition.apiOverview.tagline": "composition.apiOverview.tagline",
538
+ "composition.apiOverview.output.rest": "composition.apiOverview.output.rest",
539
+ "composition.apiOverview.output.graphql": "composition.apiOverview.output.graphql",
540
+ "composition.apiOverview.output.prisma": "composition.apiOverview.output.prisma",
541
+ "composition.apiOverview.output.typescript": "composition.apiOverview.output.typescript",
542
+ "composition.apiOverview.output.mcp": "composition.apiOverview.output.mcp",
543
+ "composition.apiOverview.output.openapi": "composition.apiOverview.output.openapi",
544
+ "composition.socialClip.cta": "composition.socialClip.cta",
545
+ "composition.terminal.title": "composition.terminal.title"
546
+ };
547
+ var I18N_KEYS = {
548
+ ...PROMPT_KEYS,
549
+ ...SCRIPT_KEYS,
550
+ ...SCENE_KEYS,
551
+ ...COMPOSITION_KEYS
552
+ };
44
553
  // src/generators/scene-planner.ts
45
554
  class ScenePlanner {
46
555
  llm;
47
556
  model;
48
557
  temperature;
49
558
  fps;
559
+ i18n;
50
560
  constructor(options) {
51
561
  this.llm = options?.llm;
52
562
  this.model = options?.model;
53
563
  this.temperature = options?.temperature ?? 0.3;
54
564
  this.fps = options?.fps ?? DEFAULT_FPS;
565
+ this.i18n = createVideoGenI18n(options?.locale);
55
566
  }
56
567
  async plan(brief) {
57
568
  if (this.llm) {
@@ -61,6 +572,7 @@ class ScenePlanner {
61
572
  }
62
573
  planDeterministic(brief) {
63
574
  const { content } = brief;
575
+ const { t } = this.i18n;
64
576
  const scenes = [];
65
577
  const fps = this.fps;
66
578
  scenes.push({
@@ -69,7 +581,7 @@ class ScenePlanner {
69
581
  hook: content.title,
70
582
  message: content.summary,
71
583
  points: content.solutions.slice(0, 3),
72
- cta: content.callToAction ?? "Learn more"
584
+ cta: content.callToAction ?? t("scene.cta.default")
73
585
  },
74
586
  durationInFrames: 3 * fps,
75
587
  narrationText: `${content.title}. ${content.summary}`
@@ -78,31 +590,35 @@ class ScenePlanner {
78
590
  scenes.push({
79
591
  compositionId: "SocialClip",
80
592
  props: {
81
- hook: "The Problem",
593
+ hook: t("scene.hook.problem"),
82
594
  message: content.problems[0] ?? "",
83
595
  points: content.problems.slice(1, 4)
84
596
  },
85
597
  durationInFrames: 4 * fps,
86
- narrationText: `The problem: ${content.problems.join(". ")}`
598
+ narrationText: t("scene.narration.problem", {
599
+ content: content.problems.join(". ")
600
+ })
87
601
  });
88
602
  }
89
603
  if (content.solutions.length > 0) {
90
604
  scenes.push({
91
605
  compositionId: "SocialClip",
92
606
  props: {
93
- hook: "The Solution",
607
+ hook: t("scene.hook.solution"),
94
608
  message: content.solutions[0] ?? "",
95
609
  points: content.solutions.slice(1, 4)
96
610
  },
97
611
  durationInFrames: 5 * fps,
98
- narrationText: `The solution: ${content.solutions.join(". ")}`
612
+ narrationText: t("scene.narration.solution", {
613
+ content: content.solutions.join(". ")
614
+ })
99
615
  });
100
616
  }
101
617
  if (content.metrics && content.metrics.length > 0) {
102
618
  scenes.push({
103
619
  compositionId: "SocialClip",
104
620
  props: {
105
- hook: "Results",
621
+ hook: t("scene.hook.results"),
106
622
  message: content.metrics[0] ?? "",
107
623
  points: content.metrics.slice(1, 3)
108
624
  },
@@ -139,29 +655,17 @@ class ScenePlanner {
139
655
  };
140
656
  }
141
657
  async planWithLlm(brief) {
658
+ const { t } = this.i18n;
142
659
  const messages = [
143
660
  {
144
661
  role: "system",
145
662
  content: [
146
663
  {
147
664
  type: "text",
148
- text: `You are a video scene planner for ContractSpec marketing/documentation videos.
149
- Given a content brief, break it into video scenes.
150
-
151
- Each scene must have:
152
- - compositionId: one of "ApiOverview", "SocialClip", "TerminalDemo"
153
- - props: the input props for that composition (see type definitions)
154
- - durationInFrames: duration at ${this.fps}fps
155
- - narrationText: what the narrator says during this scene
156
-
157
- Return a JSON object with shape:
158
- {
159
- "scenes": [{ "compositionId": string, "props": object, "durationInFrames": number, "narrationText": string }],
160
- "narrationScript": string
161
- }
162
-
163
- Keep the total duration around ${brief.targetDurationSeconds ?? 30} seconds.
164
- Prioritize clarity and pacing. Each scene should communicate one idea.`
665
+ text: t("prompt.scenePlanner.system", {
666
+ fps: this.fps,
667
+ targetSeconds: brief.targetDurationSeconds ?? 30
668
+ })
165
669
  }
166
670
  ]
167
671
  },
@@ -201,190 +705,29 @@ Prioritize clarity and pacing. Each scene should communicate one idea.`
201
705
  }
202
706
  }
203
707
 
204
- // src/generators/script-generator.ts
205
- class ScriptGenerator {
206
- llm;
207
- model;
208
- temperature;
209
- constructor(options) {
210
- this.llm = options?.llm;
211
- this.model = options?.model;
212
- this.temperature = options?.temperature ?? 0.5;
213
- }
214
- async generate(brief, config) {
215
- const style = config?.style ?? "professional";
216
- if (this.llm) {
217
- return this.generateWithLlm(brief, style);
218
- }
219
- return this.generateDeterministic(brief, style);
220
- }
221
- generateDeterministic(brief, style) {
222
- const segments = [];
223
- const intro = this.formatForStyle(`${brief.title}. ${brief.summary}`, style);
224
- segments.push({
225
- sceneId: "intro",
226
- text: intro,
227
- estimatedDurationSeconds: this.estimateDuration(intro)
228
- });
229
- if (brief.problems.length > 0) {
230
- const problemText = this.formatForStyle(`The challenge: ${brief.problems.join(". ")}`, style);
231
- segments.push({
232
- sceneId: "problems",
233
- text: problemText,
234
- estimatedDurationSeconds: this.estimateDuration(problemText)
235
- });
236
- }
237
- if (brief.solutions.length > 0) {
238
- const solutionText = this.formatForStyle(`The solution: ${brief.solutions.join(". ")}`, style);
239
- segments.push({
240
- sceneId: "solutions",
241
- text: solutionText,
242
- estimatedDurationSeconds: this.estimateDuration(solutionText)
243
- });
244
- }
245
- if (brief.metrics && brief.metrics.length > 0) {
246
- const metricsText = this.formatForStyle(`The results: ${brief.metrics.join(". ")}`, style);
247
- segments.push({
248
- sceneId: "metrics",
249
- text: metricsText,
250
- estimatedDurationSeconds: this.estimateDuration(metricsText)
251
- });
252
- }
253
- if (brief.callToAction) {
254
- const ctaText = this.formatForStyle(brief.callToAction, style);
255
- segments.push({
256
- sceneId: "cta",
257
- text: ctaText,
258
- estimatedDurationSeconds: this.estimateDuration(ctaText)
259
- });
260
- }
261
- const fullText = segments.map((s) => s.text).join(" ");
262
- const totalDuration = segments.reduce((sum, s) => sum + s.estimatedDurationSeconds, 0);
263
- return {
264
- fullText,
265
- segments,
266
- estimatedDurationSeconds: totalDuration,
267
- style
268
- };
269
- }
270
- async generateWithLlm(brief, style) {
271
- const styleGuide = {
272
- professional: "Use a clear, authoritative, professional tone. Be concise and direct.",
273
- casual: "Use a friendly, conversational tone. Be approachable and relatable.",
274
- technical: "Use precise technical language. Be detailed and accurate."
275
- };
276
- const styleKey = style ?? "professional";
277
- const messages = [
278
- {
279
- role: "system",
280
- content: [
281
- {
282
- type: "text",
283
- text: `You are a video narration script writer.
284
- Write a narration script for a short video (30-60 seconds).
285
- ${styleGuide[styleKey]}
286
-
287
- Return JSON with shape:
288
- {
289
- "segments": [{ "sceneId": string, "text": string }],
290
- "fullText": string
291
- }
292
-
293
- Scene IDs should be: "intro", "problems", "solutions", "metrics", "cta".
294
- Only include segments that are relevant to the brief content.`
295
- }
296
- ]
297
- },
298
- {
299
- role: "user",
300
- content: [{ type: "text", text: JSON.stringify(brief) }]
301
- }
302
- ];
303
- if (!this.llm) {
304
- return this.generateDeterministic(brief, style);
305
- }
306
- try {
307
- const response = await this.llm.chat(messages, {
308
- model: this.model,
309
- temperature: this.temperature,
310
- responseFormat: "json"
311
- });
312
- const text = response.message.content.find((p) => p.type === "text");
313
- if (!text || text.type !== "text") {
314
- return this.generateDeterministic(brief, style);
315
- }
316
- const parsed = JSON.parse(text.text);
317
- const segments = parsed.segments.map((s) => ({
318
- sceneId: s.sceneId,
319
- text: s.text,
320
- estimatedDurationSeconds: this.estimateDuration(s.text)
321
- }));
322
- return {
323
- fullText: parsed.fullText,
324
- segments,
325
- estimatedDurationSeconds: segments.reduce((sum, s) => sum + s.estimatedDurationSeconds, 0),
326
- style
327
- };
328
- } catch {
329
- return this.generateDeterministic(brief, style);
330
- }
331
- }
332
- formatForStyle(text, _style) {
333
- return text;
334
- }
335
- estimateDuration(text) {
336
- const wordCount = text.split(/\s+/).length;
337
- return Math.ceil(wordCount / 150 * 60);
338
- }
339
- }
340
-
341
708
  // src/generators/video-generator.ts
342
709
  import { VIDEO_FORMATS as VIDEO_FORMATS2 } from "@contractspec/lib.contracts-integrations/integrations/providers/video";
343
710
  class VideoGenerator {
344
711
  scenePlanner;
345
- scriptGenerator;
346
712
  voice;
347
- defaultVoiceId;
713
+ transcriber;
714
+ image;
348
715
  fps;
349
716
  constructor(options) {
350
717
  this.fps = options?.fps ?? DEFAULT_FPS;
351
718
  this.voice = options?.voice;
352
- this.defaultVoiceId = options?.defaultVoiceId;
719
+ this.transcriber = options?.transcriber;
720
+ this.image = options?.image;
353
721
  this.scenePlanner = new ScenePlanner({
354
722
  llm: options?.llm,
355
723
  model: options?.model,
356
724
  temperature: options?.temperature,
357
- fps: this.fps
358
- });
359
- this.scriptGenerator = new ScriptGenerator({
360
- llm: options?.llm,
361
- model: options?.model,
362
- temperature: options?.temperature
725
+ fps: this.fps,
726
+ locale: options?.locale
363
727
  });
364
728
  }
365
729
  async generate(brief) {
366
730
  const scenePlan = await this.scenePlanner.plan(brief);
367
- let narrationAudio;
368
- if (brief.narration?.enabled && this.voice) {
369
- const script = await this.scriptGenerator.generate(brief.content, brief.narration);
370
- const voiceId = brief.narration.voiceId ?? this.defaultVoiceId;
371
- if (voiceId && script.fullText) {
372
- try {
373
- const result = await this.voice.synthesize({
374
- text: script.fullText,
375
- voiceId,
376
- format: "mp3"
377
- });
378
- narrationAudio = {
379
- data: result.audio,
380
- format: "mp3",
381
- durationSeconds: result.durationSeconds ?? script.estimatedDurationSeconds,
382
- volume: 1
383
- };
384
- } catch {}
385
- }
386
- }
387
- const format = brief.format ?? VIDEO_FORMATS2.landscape;
388
731
  const scenes = scenePlan.scenes.map((planned, i) => ({
389
732
  id: `scene-${i}`,
390
733
  compositionId: planned.compositionId,
@@ -392,6 +735,70 @@ class VideoGenerator {
392
735
  durationInFrames: planned.durationInFrames,
393
736
  narrationText: planned.narrationText
394
737
  }));
738
+ let ttsProject;
739
+ let narrationAudio;
740
+ if (brief.narration?.enabled && this.voice) {
741
+ try {
742
+ ttsProject = await this.voice.synthesizeForVideo({
743
+ content: brief.content,
744
+ scenePlan: {
745
+ scenes: scenes.map((s) => ({
746
+ id: s.id,
747
+ compositionId: s.compositionId,
748
+ durationInFrames: s.durationInFrames,
749
+ narrationText: s.narrationText
750
+ })),
751
+ estimatedDurationSeconds: scenePlan.estimatedDurationSeconds
752
+ },
753
+ voice: { voiceId: brief.narration.voiceId ?? "" },
754
+ pacing: { strategy: "scene-matched" },
755
+ fps: this.fps
756
+ });
757
+ if (ttsProject.timingMap) {
758
+ for (const seg of ttsProject.timingMap.segments) {
759
+ const scene = scenes.find((s) => s.id === seg.sceneId);
760
+ if (scene) {
761
+ scene.durationInFrames = seg.recommendedSceneDurationInFrames;
762
+ }
763
+ }
764
+ }
765
+ if (ttsProject.assembledAudio) {
766
+ narrationAudio = {
767
+ data: ttsProject.assembledAudio.data,
768
+ format: ttsProject.assembledAudio.format === "wav" ? "wav" : "mp3",
769
+ durationSeconds: (ttsProject.assembledAudio.durationMs ?? 0) / 1000,
770
+ volume: 1
771
+ };
772
+ }
773
+ } catch {}
774
+ }
775
+ let subtitles;
776
+ if (this.transcriber && ttsProject?.assembledAudio) {
777
+ try {
778
+ const transcription = await this.transcriber.transcribe({
779
+ audio: ttsProject.assembledAudio,
780
+ subtitleFormat: "vtt"
781
+ });
782
+ subtitles = transcription.subtitles;
783
+ } catch {}
784
+ }
785
+ let thumbnail;
786
+ if (this.image) {
787
+ try {
788
+ const thumbProject = await this.image.generate({
789
+ content: brief.content,
790
+ purpose: "video-thumbnail",
791
+ format: "png",
792
+ style: "photorealistic"
793
+ });
794
+ const imageUrl = thumbProject.results?.images[0]?.url;
795
+ thumbnail = {
796
+ prompt: thumbProject.prompt.text,
797
+ ...imageUrl ? { imageUrl } : {}
798
+ };
799
+ } catch {}
800
+ }
801
+ const format = brief.format ?? VIDEO_FORMATS2.landscape;
395
802
  const totalDurationInFrames = scenes.reduce((sum, s) => sum + s.durationInFrames, 0);
396
803
  const project = {
397
804
  id: generateProjectId(),
@@ -399,7 +806,10 @@ class VideoGenerator {
399
806
  totalDurationInFrames,
400
807
  fps: this.fps,
401
808
  format,
402
- audio: narrationAudio ? { narration: narrationAudio } : undefined
809
+ audio: narrationAudio ? { narration: narrationAudio } : undefined,
810
+ subtitles,
811
+ voiceTimingMap: ttsProject?.timingMap,
812
+ thumbnail
403
813
  };
404
814
  return project;
405
815
  }
@@ -411,6 +821,5 @@ function generateProjectId() {
411
821
  }
412
822
  export {
413
823
  VideoGenerator,
414
- ScriptGenerator,
415
824
  ScenePlanner
416
825
  };