@contractspec/lib.video-gen 1.42.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/dist/browser/compositions/api-overview.js +645 -0
- package/dist/browser/compositions/index.js +1133 -0
- package/dist/browser/compositions/primitives/animated-text.js +144 -0
- package/dist/browser/compositions/primitives/brand-frame.js +181 -0
- package/dist/browser/compositions/primitives/code-block.js +226 -0
- package/dist/browser/compositions/primitives/index.js +656 -0
- package/dist/browser/compositions/primitives/progress-bar.js +59 -0
- package/dist/browser/compositions/primitives/terminal.js +265 -0
- package/dist/browser/compositions/primitives/transition.js +98 -0
- package/dist/browser/compositions/social-clip.js +500 -0
- package/dist/browser/compositions/terminal-demo.js +558 -0
- package/dist/browser/design/index.js +155 -0
- package/dist/browser/design/layouts.js +50 -0
- package/dist/browser/design/motion.js +43 -0
- package/dist/browser/design/tokens.js +28 -0
- package/dist/browser/design/typography.js +61 -0
- package/dist/browser/docs/compositions.docblock.js +182 -0
- package/dist/browser/docs/design.docblock.js +187 -0
- package/dist/browser/docs/generators.docblock.js +187 -0
- package/dist/browser/docs/rendering.docblock.js +197 -0
- package/dist/browser/docs/video-gen.docblock.js +141 -0
- package/dist/browser/generators/index.js +416 -0
- package/dist/browser/generators/scene-planner.js +205 -0
- package/dist/browser/generators/script-generator.js +147 -0
- package/dist/browser/generators/video-generator.js +414 -0
- package/dist/browser/index.js +1550 -0
- package/dist/browser/player/demo-player.js +1136 -0
- package/dist/browser/player/index.js +1136 -0
- package/dist/browser/remotion/Root.js +1189 -0
- package/dist/browser/remotion/index.js +1190 -0
- package/dist/browser/renderers/config.js +40 -0
- package/dist/browser/renderers/index.js +160 -0
- package/dist/browser/renderers/local.js +156 -0
- package/dist/browser/types.js +13 -0
- package/dist/compositions/api-overview.d.ts +16 -0
- package/dist/compositions/api-overview.js +640 -0
- package/dist/compositions/index.d.ts +7 -0
- package/dist/compositions/index.js +1128 -0
- package/dist/compositions/primitives/animated-text.d.ts +22 -0
- package/dist/compositions/primitives/animated-text.js +139 -0
- package/dist/compositions/primitives/brand-frame.d.ts +14 -0
- package/dist/compositions/primitives/brand-frame.js +176 -0
- package/dist/compositions/primitives/code-block.d.ts +18 -0
- package/dist/compositions/primitives/code-block.js +221 -0
- package/dist/compositions/primitives/index.d.ts +12 -0
- package/dist/compositions/primitives/index.js +651 -0
- package/dist/compositions/primitives/progress-bar.d.ts +12 -0
- package/dist/compositions/primitives/progress-bar.js +54 -0
- package/dist/compositions/primitives/terminal.d.ts +24 -0
- package/dist/compositions/primitives/terminal.js +260 -0
- package/dist/compositions/primitives/transition.d.ts +14 -0
- package/dist/compositions/primitives/transition.js +93 -0
- package/dist/compositions/social-clip.d.ts +16 -0
- package/dist/compositions/social-clip.js +495 -0
- package/dist/compositions/terminal-demo.d.ts +17 -0
- package/dist/compositions/terminal-demo.js +553 -0
- package/dist/design/index.d.ts +4 -0
- package/dist/design/index.js +150 -0
- package/dist/design/layouts.d.ts +69 -0
- package/dist/design/layouts.js +45 -0
- package/dist/design/motion.d.ts +72 -0
- package/dist/design/motion.js +38 -0
- package/dist/design/tokens.d.ts +31 -0
- package/dist/design/tokens.js +23 -0
- package/dist/design/typography.d.ts +61 -0
- package/dist/design/typography.js +56 -0
- package/dist/docs/compositions.docblock.d.ts +1 -0
- package/dist/docs/compositions.docblock.js +183 -0
- package/dist/docs/design.docblock.d.ts +1 -0
- package/dist/docs/design.docblock.js +188 -0
- package/dist/docs/generators.docblock.d.ts +1 -0
- package/dist/docs/generators.docblock.js +188 -0
- package/dist/docs/rendering.docblock.d.ts +1 -0
- package/dist/docs/rendering.docblock.js +198 -0
- package/dist/docs/video-gen.docblock.d.ts +1 -0
- package/dist/docs/video-gen.docblock.js +142 -0
- package/dist/generators/index.d.ts +5 -0
- package/dist/generators/index.js +411 -0
- package/dist/generators/scene-planner.d.ts +23 -0
- package/dist/generators/scene-planner.js +200 -0
- package/dist/generators/script-generator.d.ts +49 -0
- package/dist/generators/script-generator.js +142 -0
- package/dist/generators/video-generator.d.ts +20 -0
- package/dist/generators/video-generator.js +409 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1545 -0
- package/dist/node/compositions/api-overview.js +640 -0
- package/dist/node/compositions/index.js +1128 -0
- package/dist/node/compositions/primitives/animated-text.js +139 -0
- package/dist/node/compositions/primitives/brand-frame.js +176 -0
- package/dist/node/compositions/primitives/code-block.js +221 -0
- package/dist/node/compositions/primitives/index.js +651 -0
- package/dist/node/compositions/primitives/progress-bar.js +54 -0
- package/dist/node/compositions/primitives/terminal.js +260 -0
- package/dist/node/compositions/primitives/transition.js +93 -0
- package/dist/node/compositions/social-clip.js +495 -0
- package/dist/node/compositions/terminal-demo.js +553 -0
- package/dist/node/design/index.js +150 -0
- package/dist/node/design/layouts.js +45 -0
- package/dist/node/design/motion.js +38 -0
- package/dist/node/design/tokens.js +23 -0
- package/dist/node/design/typography.js +56 -0
- package/dist/node/docs/compositions.docblock.js +182 -0
- package/dist/node/docs/design.docblock.js +187 -0
- package/dist/node/docs/generators.docblock.js +187 -0
- package/dist/node/docs/rendering.docblock.js +197 -0
- package/dist/node/docs/video-gen.docblock.js +141 -0
- package/dist/node/generators/index.js +411 -0
- package/dist/node/generators/scene-planner.js +200 -0
- package/dist/node/generators/script-generator.js +142 -0
- package/dist/node/generators/video-generator.js +409 -0
- package/dist/node/index.js +1545 -0
- package/dist/node/player/demo-player.js +1131 -0
- package/dist/node/player/index.js +1131 -0
- package/dist/node/remotion/Root.js +1184 -0
- package/dist/node/remotion/index.js +1185 -0
- package/dist/node/renderers/config.js +35 -0
- package/dist/node/renderers/index.js +155 -0
- package/dist/node/renderers/local.js +151 -0
- package/dist/node/types.js +8 -0
- package/dist/player/demo-player.d.ts +55 -0
- package/dist/player/demo-player.js +1131 -0
- package/dist/player/index.d.ts +2 -0
- package/dist/player/index.js +1131 -0
- package/dist/remotion/Root.d.ts +2 -0
- package/dist/remotion/Root.js +1184 -0
- package/dist/remotion/index.d.ts +1 -0
- package/dist/remotion/index.js +1185 -0
- package/dist/renderers/config.d.ts +28 -0
- package/dist/renderers/config.js +35 -0
- package/dist/renderers/index.d.ts +3 -0
- package/dist/renderers/index.js +155 -0
- package/dist/renderers/local.d.ts +17 -0
- package/dist/renderers/local.js +151 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.js +8 -0
- package/package.json +637 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
4
|
+
// src/design/layouts.ts
|
|
5
|
+
import { VIDEO_FORMATS } from "@contractspec/lib.contracts-integrations/integrations/providers/video";
|
|
6
|
+
var DEFAULT_FPS = 30;
|
|
7
|
+
var videoSafeZone = {
|
|
8
|
+
horizontal: 120,
|
|
9
|
+
vertical: 80,
|
|
10
|
+
contentWidth: 1680,
|
|
11
|
+
contentHeight: 920
|
|
12
|
+
};
|
|
13
|
+
function scaleSafeZone(format) {
|
|
14
|
+
const scaleX = format.width / 1920;
|
|
15
|
+
const scaleY = format.height / 1080;
|
|
16
|
+
return {
|
|
17
|
+
horizontal: Math.round(videoSafeZone.horizontal * scaleX),
|
|
18
|
+
vertical: Math.round(videoSafeZone.vertical * scaleY),
|
|
19
|
+
contentWidth: Math.round(videoSafeZone.contentWidth * scaleX),
|
|
20
|
+
contentHeight: Math.round(videoSafeZone.contentHeight * scaleY)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
var videoPositions = {
|
|
24
|
+
center: { x: 960, y: 540 },
|
|
25
|
+
topLeft: { x: 120, y: 80 },
|
|
26
|
+
topRight: { x: 1800, y: 80 },
|
|
27
|
+
bottomLeft: { x: 120, y: 1000 },
|
|
28
|
+
bottomRight: { x: 1800, y: 1000 },
|
|
29
|
+
bottomCenter: { x: 960, y: 960 }
|
|
30
|
+
};
|
|
31
|
+
function getAllFormatVariants() {
|
|
32
|
+
return [
|
|
33
|
+
VIDEO_FORMATS.landscape,
|
|
34
|
+
VIDEO_FORMATS.square,
|
|
35
|
+
VIDEO_FORMATS.portrait
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/generators/scene-planner.ts
|
|
40
|
+
class ScenePlanner {
|
|
41
|
+
llm;
|
|
42
|
+
model;
|
|
43
|
+
temperature;
|
|
44
|
+
fps;
|
|
45
|
+
constructor(options) {
|
|
46
|
+
this.llm = options?.llm;
|
|
47
|
+
this.model = options?.model;
|
|
48
|
+
this.temperature = options?.temperature ?? 0.3;
|
|
49
|
+
this.fps = options?.fps ?? DEFAULT_FPS;
|
|
50
|
+
}
|
|
51
|
+
async plan(brief) {
|
|
52
|
+
if (this.llm) {
|
|
53
|
+
return this.planWithLlm(brief);
|
|
54
|
+
}
|
|
55
|
+
return this.planDeterministic(brief);
|
|
56
|
+
}
|
|
57
|
+
planDeterministic(brief) {
|
|
58
|
+
const { content } = brief;
|
|
59
|
+
const scenes = [];
|
|
60
|
+
const fps = this.fps;
|
|
61
|
+
scenes.push({
|
|
62
|
+
compositionId: "SocialClip",
|
|
63
|
+
props: {
|
|
64
|
+
hook: content.title,
|
|
65
|
+
message: content.summary,
|
|
66
|
+
points: content.solutions.slice(0, 3),
|
|
67
|
+
cta: content.callToAction ?? "Learn more"
|
|
68
|
+
},
|
|
69
|
+
durationInFrames: 3 * fps,
|
|
70
|
+
narrationText: `${content.title}. ${content.summary}`
|
|
71
|
+
});
|
|
72
|
+
if (content.problems.length > 0) {
|
|
73
|
+
scenes.push({
|
|
74
|
+
compositionId: "SocialClip",
|
|
75
|
+
props: {
|
|
76
|
+
hook: "The Problem",
|
|
77
|
+
message: content.problems[0] ?? "",
|
|
78
|
+
points: content.problems.slice(1, 4)
|
|
79
|
+
},
|
|
80
|
+
durationInFrames: 4 * fps,
|
|
81
|
+
narrationText: `The problem: ${content.problems.join(". ")}`
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (content.solutions.length > 0) {
|
|
85
|
+
scenes.push({
|
|
86
|
+
compositionId: "SocialClip",
|
|
87
|
+
props: {
|
|
88
|
+
hook: "The Solution",
|
|
89
|
+
message: content.solutions[0] ?? "",
|
|
90
|
+
points: content.solutions.slice(1, 4)
|
|
91
|
+
},
|
|
92
|
+
durationInFrames: 5 * fps,
|
|
93
|
+
narrationText: `The solution: ${content.solutions.join(". ")}`
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (content.metrics && content.metrics.length > 0) {
|
|
97
|
+
scenes.push({
|
|
98
|
+
compositionId: "SocialClip",
|
|
99
|
+
props: {
|
|
100
|
+
hook: "Results",
|
|
101
|
+
message: content.metrics[0] ?? "",
|
|
102
|
+
points: content.metrics.slice(1, 3)
|
|
103
|
+
},
|
|
104
|
+
durationInFrames: 3 * fps,
|
|
105
|
+
narrationText: content.metrics.join(". ")
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (content.callToAction) {
|
|
109
|
+
scenes.push({
|
|
110
|
+
compositionId: "SocialClip",
|
|
111
|
+
props: {
|
|
112
|
+
hook: content.callToAction,
|
|
113
|
+
message: "",
|
|
114
|
+
cta: content.callToAction
|
|
115
|
+
},
|
|
116
|
+
durationInFrames: 2 * fps,
|
|
117
|
+
narrationText: content.callToAction
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (brief.targetDurationSeconds) {
|
|
121
|
+
const targetFrames = brief.targetDurationSeconds * fps;
|
|
122
|
+
const currentFrames = scenes.reduce((sum, s) => sum + s.durationInFrames, 0);
|
|
123
|
+
const ratio = targetFrames / currentFrames;
|
|
124
|
+
for (const scene of scenes) {
|
|
125
|
+
scene.durationInFrames = Math.round(scene.durationInFrames * ratio);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const totalDuration = scenes.reduce((sum, s) => sum + s.durationInFrames, 0);
|
|
129
|
+
const narrationScript = scenes.filter((s) => s.narrationText).map((s) => s.narrationText).join(" ");
|
|
130
|
+
return {
|
|
131
|
+
scenes,
|
|
132
|
+
estimatedDurationSeconds: totalDuration / fps,
|
|
133
|
+
narrationScript
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
async planWithLlm(brief) {
|
|
137
|
+
const messages = [
|
|
138
|
+
{
|
|
139
|
+
role: "system",
|
|
140
|
+
content: [
|
|
141
|
+
{
|
|
142
|
+
type: "text",
|
|
143
|
+
text: `You are a video scene planner for ContractSpec marketing/documentation videos.
|
|
144
|
+
Given a content brief, break it into video scenes.
|
|
145
|
+
|
|
146
|
+
Each scene must have:
|
|
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.`
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
role: "user",
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: "text",
|
|
168
|
+
text: JSON.stringify(brief.content)
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
];
|
|
173
|
+
if (!this.llm) {
|
|
174
|
+
return this.planDeterministic(brief);
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const response = await this.llm.chat(messages, {
|
|
178
|
+
model: this.model,
|
|
179
|
+
temperature: this.temperature,
|
|
180
|
+
responseFormat: "json"
|
|
181
|
+
});
|
|
182
|
+
const text = response.message.content.find((p) => p.type === "text");
|
|
183
|
+
if (!text || text.type !== "text") {
|
|
184
|
+
return this.planDeterministic(brief);
|
|
185
|
+
}
|
|
186
|
+
const parsed = JSON.parse(text.text);
|
|
187
|
+
const totalDuration = parsed.scenes.reduce((sum, s) => sum + s.durationInFrames, 0);
|
|
188
|
+
return {
|
|
189
|
+
scenes: parsed.scenes,
|
|
190
|
+
estimatedDurationSeconds: totalDuration / this.fps,
|
|
191
|
+
narrationScript: parsed.narrationScript
|
|
192
|
+
};
|
|
193
|
+
} catch {
|
|
194
|
+
return this.planDeterministic(brief);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
export {
|
|
199
|
+
ScenePlanner
|
|
200
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { LLMProvider } from '@contractspec/lib.contracts-integrations/integrations/providers/llm';
|
|
2
|
+
import type { ContentBrief } from '@contractspec/lib.content-gen/types';
|
|
3
|
+
import type { NarrationConfig } from '@contractspec/lib.contracts-integrations/integrations/providers/video';
|
|
4
|
+
export interface NarrationScript {
|
|
5
|
+
/** Full narration text */
|
|
6
|
+
fullText: string;
|
|
7
|
+
/** Per-scene narration segments */
|
|
8
|
+
segments: NarrationSegment[];
|
|
9
|
+
/** Estimated speaking duration in seconds (at ~150 words/min) */
|
|
10
|
+
estimatedDurationSeconds: number;
|
|
11
|
+
/** Style used */
|
|
12
|
+
style: NarrationConfig['style'];
|
|
13
|
+
}
|
|
14
|
+
export interface NarrationSegment {
|
|
15
|
+
/** Scene identifier this segment maps to */
|
|
16
|
+
sceneId: string;
|
|
17
|
+
/** Narration text for this segment */
|
|
18
|
+
text: string;
|
|
19
|
+
/** Estimated duration in seconds */
|
|
20
|
+
estimatedDurationSeconds: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ScriptGeneratorOptions {
|
|
23
|
+
llm?: LLMProvider;
|
|
24
|
+
model?: string;
|
|
25
|
+
temperature?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare class ScriptGenerator {
|
|
28
|
+
private readonly llm?;
|
|
29
|
+
private readonly model?;
|
|
30
|
+
private readonly temperature;
|
|
31
|
+
constructor(options?: ScriptGeneratorOptions);
|
|
32
|
+
/**
|
|
33
|
+
* Generate a narration script from a content brief.
|
|
34
|
+
*/
|
|
35
|
+
generate(brief: ContentBrief, config?: NarrationConfig): Promise<NarrationScript>;
|
|
36
|
+
private generateDeterministic;
|
|
37
|
+
private generateWithLlm;
|
|
38
|
+
/**
|
|
39
|
+
* Adjust text tone based on style.
|
|
40
|
+
* In the deterministic path, this is a simple pass-through.
|
|
41
|
+
* The LLM path handles style natively.
|
|
42
|
+
*/
|
|
43
|
+
private formatForStyle;
|
|
44
|
+
/**
|
|
45
|
+
* Estimate speaking duration based on word count.
|
|
46
|
+
* Average speaking rate: ~150 words per minute.
|
|
47
|
+
*/
|
|
48
|
+
private estimateDuration;
|
|
49
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
4
|
+
// src/generators/script-generator.ts
|
|
5
|
+
class ScriptGenerator {
|
|
6
|
+
llm;
|
|
7
|
+
model;
|
|
8
|
+
temperature;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.llm = options?.llm;
|
|
11
|
+
this.model = options?.model;
|
|
12
|
+
this.temperature = options?.temperature ?? 0.5;
|
|
13
|
+
}
|
|
14
|
+
async generate(brief, config) {
|
|
15
|
+
const style = config?.style ?? "professional";
|
|
16
|
+
if (this.llm) {
|
|
17
|
+
return this.generateWithLlm(brief, style);
|
|
18
|
+
}
|
|
19
|
+
return this.generateDeterministic(brief, style);
|
|
20
|
+
}
|
|
21
|
+
generateDeterministic(brief, style) {
|
|
22
|
+
const segments = [];
|
|
23
|
+
const intro = this.formatForStyle(`${brief.title}. ${brief.summary}`, style);
|
|
24
|
+
segments.push({
|
|
25
|
+
sceneId: "intro",
|
|
26
|
+
text: intro,
|
|
27
|
+
estimatedDurationSeconds: this.estimateDuration(intro)
|
|
28
|
+
});
|
|
29
|
+
if (brief.problems.length > 0) {
|
|
30
|
+
const problemText = this.formatForStyle(`The challenge: ${brief.problems.join(". ")}`, style);
|
|
31
|
+
segments.push({
|
|
32
|
+
sceneId: "problems",
|
|
33
|
+
text: problemText,
|
|
34
|
+
estimatedDurationSeconds: this.estimateDuration(problemText)
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (brief.solutions.length > 0) {
|
|
38
|
+
const solutionText = this.formatForStyle(`The solution: ${brief.solutions.join(". ")}`, style);
|
|
39
|
+
segments.push({
|
|
40
|
+
sceneId: "solutions",
|
|
41
|
+
text: solutionText,
|
|
42
|
+
estimatedDurationSeconds: this.estimateDuration(solutionText)
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (brief.metrics && brief.metrics.length > 0) {
|
|
46
|
+
const metricsText = this.formatForStyle(`The results: ${brief.metrics.join(". ")}`, style);
|
|
47
|
+
segments.push({
|
|
48
|
+
sceneId: "metrics",
|
|
49
|
+
text: metricsText,
|
|
50
|
+
estimatedDurationSeconds: this.estimateDuration(metricsText)
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (brief.callToAction) {
|
|
54
|
+
const ctaText = this.formatForStyle(brief.callToAction, style);
|
|
55
|
+
segments.push({
|
|
56
|
+
sceneId: "cta",
|
|
57
|
+
text: ctaText,
|
|
58
|
+
estimatedDurationSeconds: this.estimateDuration(ctaText)
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const fullText = segments.map((s) => s.text).join(" ");
|
|
62
|
+
const totalDuration = segments.reduce((sum, s) => sum + s.estimatedDurationSeconds, 0);
|
|
63
|
+
return {
|
|
64
|
+
fullText,
|
|
65
|
+
segments,
|
|
66
|
+
estimatedDurationSeconds: totalDuration,
|
|
67
|
+
style
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async generateWithLlm(brief, style) {
|
|
71
|
+
const styleGuide = {
|
|
72
|
+
professional: "Use a clear, authoritative, professional tone. Be concise and direct.",
|
|
73
|
+
casual: "Use a friendly, conversational tone. Be approachable and relatable.",
|
|
74
|
+
technical: "Use precise technical language. Be detailed and accurate."
|
|
75
|
+
};
|
|
76
|
+
const styleKey = style ?? "professional";
|
|
77
|
+
const messages = [
|
|
78
|
+
{
|
|
79
|
+
role: "system",
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: "text",
|
|
83
|
+
text: `You are a video narration script writer.
|
|
84
|
+
Write a narration script for a short video (30-60 seconds).
|
|
85
|
+
${styleGuide[styleKey]}
|
|
86
|
+
|
|
87
|
+
Return JSON with shape:
|
|
88
|
+
{
|
|
89
|
+
"segments": [{ "sceneId": string, "text": string }],
|
|
90
|
+
"fullText": string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Scene IDs should be: "intro", "problems", "solutions", "metrics", "cta".
|
|
94
|
+
Only include segments that are relevant to the brief content.`
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
role: "user",
|
|
100
|
+
content: [{ type: "text", text: JSON.stringify(brief) }]
|
|
101
|
+
}
|
|
102
|
+
];
|
|
103
|
+
if (!this.llm) {
|
|
104
|
+
return this.generateDeterministic(brief, style);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const response = await this.llm.chat(messages, {
|
|
108
|
+
model: this.model,
|
|
109
|
+
temperature: this.temperature,
|
|
110
|
+
responseFormat: "json"
|
|
111
|
+
});
|
|
112
|
+
const text = response.message.content.find((p) => p.type === "text");
|
|
113
|
+
if (!text || text.type !== "text") {
|
|
114
|
+
return this.generateDeterministic(brief, style);
|
|
115
|
+
}
|
|
116
|
+
const parsed = JSON.parse(text.text);
|
|
117
|
+
const segments = parsed.segments.map((s) => ({
|
|
118
|
+
sceneId: s.sceneId,
|
|
119
|
+
text: s.text,
|
|
120
|
+
estimatedDurationSeconds: this.estimateDuration(s.text)
|
|
121
|
+
}));
|
|
122
|
+
return {
|
|
123
|
+
fullText: parsed.fullText,
|
|
124
|
+
segments,
|
|
125
|
+
estimatedDurationSeconds: segments.reduce((sum, s) => sum + s.estimatedDurationSeconds, 0),
|
|
126
|
+
style
|
|
127
|
+
};
|
|
128
|
+
} catch {
|
|
129
|
+
return this.generateDeterministic(brief, style);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
formatForStyle(text, _style) {
|
|
133
|
+
return text;
|
|
134
|
+
}
|
|
135
|
+
estimateDuration(text) {
|
|
136
|
+
const wordCount = text.split(/\s+/).length;
|
|
137
|
+
return Math.ceil(wordCount / 150 * 60);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export {
|
|
141
|
+
ScriptGenerator
|
|
142
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { VideoProject } from '@contractspec/lib.contracts-integrations/integrations/providers/video';
|
|
2
|
+
import type { VideoBrief, VideoGeneratorOptions } from '../types';
|
|
3
|
+
export declare class VideoGenerator {
|
|
4
|
+
private readonly scenePlanner;
|
|
5
|
+
private readonly scriptGenerator;
|
|
6
|
+
private readonly voice?;
|
|
7
|
+
private readonly defaultVoiceId?;
|
|
8
|
+
private readonly fps;
|
|
9
|
+
constructor(options?: VideoGeneratorOptions);
|
|
10
|
+
/**
|
|
11
|
+
* Generate a complete video project from a brief.
|
|
12
|
+
*
|
|
13
|
+
* Pipeline:
|
|
14
|
+
* 1. Plan scenes from the content brief
|
|
15
|
+
* 2. Generate narration script
|
|
16
|
+
* 3. (Optional) Synthesize voice audio via VoiceProvider
|
|
17
|
+
* 4. Assemble into a VideoProject
|
|
18
|
+
*/
|
|
19
|
+
generate(brief: VideoBrief): Promise<VideoProject>;
|
|
20
|
+
}
|