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