@studiomeyer/mcp-video 1.0.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/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/.github/workflows/ci.yml +34 -0
- package/CHANGELOG.md +24 -0
- package/CONTRIBUTING.md +75 -0
- package/LICENSE +21 -0
- package/README.md +198 -0
- package/USAGE.md +144 -0
- package/dist/handlers/capcut.d.ts +6 -0
- package/dist/handlers/capcut.js +229 -0
- package/dist/handlers/capcut.js.map +1 -0
- package/dist/handlers/editing.d.ts +6 -0
- package/dist/handlers/editing.js +242 -0
- package/dist/handlers/editing.js.map +1 -0
- package/dist/handlers/index.d.ts +2 -0
- package/dist/handlers/index.js +33 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/post-production.d.ts +5 -0
- package/dist/handlers/post-production.js +109 -0
- package/dist/handlers/post-production.js.map +1 -0
- package/dist/handlers/smart-screenshot.d.ts +5 -0
- package/dist/handlers/smart-screenshot.js +83 -0
- package/dist/handlers/smart-screenshot.js.map +1 -0
- package/dist/handlers/tts.d.ts +5 -0
- package/dist/handlers/tts.js +83 -0
- package/dist/handlers/tts.js.map +1 -0
- package/dist/handlers/video.d.ts +5 -0
- package/dist/handlers/video.js +127 -0
- package/dist/handlers/video.js.map +1 -0
- package/dist/lib/dual-transport.d.ts +42 -0
- package/dist/lib/dual-transport.js +208 -0
- package/dist/lib/dual-transport.js.map +1 -0
- package/dist/lib/logger.d.ts +12 -0
- package/dist/lib/logger.js +42 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/types.d.ts +16 -0
- package/dist/lib/types.js +15 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/schemas/capcut.d.ts +608 -0
- package/dist/schemas/capcut.js +411 -0
- package/dist/schemas/capcut.js.map +1 -0
- package/dist/schemas/editing.d.ts +822 -0
- package/dist/schemas/editing.js +466 -0
- package/dist/schemas/editing.js.map +1 -0
- package/dist/schemas/index.d.ts +2366 -0
- package/dist/schemas/index.js +15 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/post-production.d.ts +379 -0
- package/dist/schemas/post-production.js +268 -0
- package/dist/schemas/post-production.js.map +1 -0
- package/dist/schemas/smart-screenshot.d.ts +127 -0
- package/dist/schemas/smart-screenshot.js +122 -0
- package/dist/schemas/smart-screenshot.js.map +1 -0
- package/dist/schemas/tts.d.ts +220 -0
- package/dist/schemas/tts.js +194 -0
- package/dist/schemas/tts.js.map +1 -0
- package/dist/schemas/video.d.ts +236 -0
- package/dist/schemas/video.js +210 -0
- package/dist/schemas/video.js.map +1 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +239 -0
- package/dist/server.js.map +1 -0
- package/dist/server.test.d.ts +1 -0
- package/dist/server.test.js +87 -0
- package/dist/server.test.js.map +1 -0
- package/dist/tools/engine/audio-mixer.d.ts +40 -0
- package/dist/tools/engine/audio-mixer.js +169 -0
- package/dist/tools/engine/audio-mixer.js.map +1 -0
- package/dist/tools/engine/audio.d.ts +22 -0
- package/dist/tools/engine/audio.js +73 -0
- package/dist/tools/engine/audio.js.map +1 -0
- package/dist/tools/engine/beat-sync.d.ts +31 -0
- package/dist/tools/engine/beat-sync.js +270 -0
- package/dist/tools/engine/beat-sync.js.map +1 -0
- package/dist/tools/engine/capture.d.ts +12 -0
- package/dist/tools/engine/capture.js +290 -0
- package/dist/tools/engine/capture.js.map +1 -0
- package/dist/tools/engine/chroma-key.d.ts +27 -0
- package/dist/tools/engine/chroma-key.js +154 -0
- package/dist/tools/engine/chroma-key.js.map +1 -0
- package/dist/tools/engine/concat.d.ts +49 -0
- package/dist/tools/engine/concat.js +149 -0
- package/dist/tools/engine/concat.js.map +1 -0
- package/dist/tools/engine/cursor.d.ts +26 -0
- package/dist/tools/engine/cursor.js +185 -0
- package/dist/tools/engine/cursor.js.map +1 -0
- package/dist/tools/engine/easing.d.ts +15 -0
- package/dist/tools/engine/easing.js +100 -0
- package/dist/tools/engine/easing.js.map +1 -0
- package/dist/tools/engine/editing.d.ts +158 -0
- package/dist/tools/engine/editing.js +541 -0
- package/dist/tools/engine/editing.js.map +1 -0
- package/dist/tools/engine/encoder.d.ts +31 -0
- package/dist/tools/engine/encoder.js +154 -0
- package/dist/tools/engine/encoder.js.map +1 -0
- package/dist/tools/engine/index.d.ts +30 -0
- package/dist/tools/engine/index.js +23 -0
- package/dist/tools/engine/index.js.map +1 -0
- package/dist/tools/engine/lut-presets.d.ts +25 -0
- package/dist/tools/engine/lut-presets.js +141 -0
- package/dist/tools/engine/lut-presets.js.map +1 -0
- package/dist/tools/engine/narrated-video.d.ts +63 -0
- package/dist/tools/engine/narrated-video.js +163 -0
- package/dist/tools/engine/narrated-video.js.map +1 -0
- package/dist/tools/engine/scenes.d.ts +17 -0
- package/dist/tools/engine/scenes.js +223 -0
- package/dist/tools/engine/scenes.js.map +1 -0
- package/dist/tools/engine/smart-screenshot.d.ts +80 -0
- package/dist/tools/engine/smart-screenshot.js +744 -0
- package/dist/tools/engine/smart-screenshot.js.map +1 -0
- package/dist/tools/engine/social-format.d.ts +66 -0
- package/dist/tools/engine/social-format.js +107 -0
- package/dist/tools/engine/social-format.js.map +1 -0
- package/dist/tools/engine/template-renderer.d.ts +45 -0
- package/dist/tools/engine/template-renderer.js +233 -0
- package/dist/tools/engine/template-renderer.js.map +1 -0
- package/dist/tools/engine/templates.d.ts +87 -0
- package/dist/tools/engine/templates.js +272 -0
- package/dist/tools/engine/templates.js.map +1 -0
- package/dist/tools/engine/text-animations.d.ts +33 -0
- package/dist/tools/engine/text-animations.js +192 -0
- package/dist/tools/engine/text-animations.js.map +1 -0
- package/dist/tools/engine/text-overlay.d.ts +27 -0
- package/dist/tools/engine/text-overlay.js +84 -0
- package/dist/tools/engine/text-overlay.js.map +1 -0
- package/dist/tools/engine/tts.d.ts +54 -0
- package/dist/tools/engine/tts.js +186 -0
- package/dist/tools/engine/tts.js.map +1 -0
- package/dist/tools/engine/types.d.ts +166 -0
- package/dist/tools/engine/types.js +13 -0
- package/dist/tools/engine/types.js.map +1 -0
- package/dist/tools/engine/voice-effects.d.ts +18 -0
- package/dist/tools/engine/voice-effects.js +215 -0
- package/dist/tools/engine/voice-effects.js.map +1 -0
- package/dist/tools/index.d.ts +32 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/index.js.map +1 -0
- package/package.json +56 -0
- package/scripts/check-deps.js +39 -0
- package/src/handlers/capcut.ts +245 -0
- package/src/handlers/editing.ts +260 -0
- package/src/handlers/index.ts +34 -0
- package/src/handlers/post-production.ts +136 -0
- package/src/handlers/smart-screenshot.ts +86 -0
- package/src/handlers/tts.ts +103 -0
- package/src/handlers/video.ts +137 -0
- package/src/lib/dual-transport.ts +272 -0
- package/src/lib/logger.ts +59 -0
- package/src/lib/types.ts +25 -0
- package/src/schemas/capcut.ts +418 -0
- package/src/schemas/editing.ts +476 -0
- package/src/schemas/index.ts +15 -0
- package/src/schemas/post-production.ts +273 -0
- package/src/schemas/smart-screenshot.ts +122 -0
- package/src/schemas/tts.ts +197 -0
- package/src/schemas/video.ts +211 -0
- package/src/server.test.ts +99 -0
- package/src/server.ts +289 -0
- package/src/tools/engine/audio-mixer.ts +244 -0
- package/src/tools/engine/audio.ts +115 -0
- package/src/tools/engine/beat-sync.ts +356 -0
- package/src/tools/engine/capture.ts +360 -0
- package/src/tools/engine/chroma-key.ts +202 -0
- package/src/tools/engine/concat.ts +242 -0
- package/src/tools/engine/cursor.ts +222 -0
- package/src/tools/engine/easing.ts +120 -0
- package/src/tools/engine/editing.ts +809 -0
- package/src/tools/engine/encoder.ts +208 -0
- package/src/tools/engine/index.ts +33 -0
- package/src/tools/engine/lut-presets.ts +235 -0
- package/src/tools/engine/narrated-video.ts +267 -0
- package/src/tools/engine/scenes.ts +309 -0
- package/src/tools/engine/smart-screenshot.ts +923 -0
- package/src/tools/engine/social-format.ts +146 -0
- package/src/tools/engine/template-renderer.ts +294 -0
- package/src/tools/engine/templates.ts +370 -0
- package/src/tools/engine/text-animations.ts +282 -0
- package/src/tools/engine/text-overlay.ts +143 -0
- package/src/tools/engine/tts.ts +284 -0
- package/src/tools/engine/types.ts +191 -0
- package/src/tools/engine/voice-effects.ts +258 -0
- package/src/tools/index.ts +67 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ffmpeg encoding pipeline — stitches PNG frames into cinema-grade video
|
|
3
|
+
*/
|
|
4
|
+
import { execFile } from 'child_process';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { logger } from '../../lib/logger.js';
|
|
8
|
+
const CODEC_MAP = {
|
|
9
|
+
h264: {
|
|
10
|
+
codec: 'libx264',
|
|
11
|
+
format: 'mp4',
|
|
12
|
+
extraArgs: ['-pix_fmt', 'yuv420p', '-movflags', '+faststart'],
|
|
13
|
+
},
|
|
14
|
+
h265: {
|
|
15
|
+
codec: 'libx265',
|
|
16
|
+
format: 'mp4',
|
|
17
|
+
extraArgs: ['-pix_fmt', 'yuv420p', '-tag:v', 'hvc1'],
|
|
18
|
+
},
|
|
19
|
+
vp9: {
|
|
20
|
+
codec: 'libvpx-vp9',
|
|
21
|
+
format: 'webm',
|
|
22
|
+
extraArgs: ['-pix_fmt', 'yuv420p', '-row-mt', '1'],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Encode a sequence of PNG frames into a video file using ffmpeg
|
|
27
|
+
*/
|
|
28
|
+
export async function encodeFrames(framesDir, framePattern, outputPath, totalFrames, config = {}) {
|
|
29
|
+
const { codec: codecName = 'h264', crf = 18, preset = 'slow', fps = 60, } = config;
|
|
30
|
+
const codecConfig = CODEC_MAP[codecName];
|
|
31
|
+
const format = config.format ?? codecConfig.format;
|
|
32
|
+
const finalOutput = outputPath.endsWith(`.${format}`)
|
|
33
|
+
? outputPath
|
|
34
|
+
: `${outputPath}.${format}`;
|
|
35
|
+
// Ensure output directory exists
|
|
36
|
+
const outputDir = path.dirname(finalOutput);
|
|
37
|
+
if (!fs.existsSync(outputDir)) {
|
|
38
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
const inputPattern = path.join(framesDir, framePattern);
|
|
41
|
+
const args = [
|
|
42
|
+
'-y', // Overwrite output
|
|
43
|
+
'-framerate', String(fps), // Input frame rate
|
|
44
|
+
'-i', inputPattern, // Input pattern
|
|
45
|
+
'-c:v', codecConfig.codec, // Video codec
|
|
46
|
+
'-preset', preset, // Encoding speed/quality
|
|
47
|
+
'-crf', String(crf), // Quality factor
|
|
48
|
+
...codecConfig.extraArgs, // Codec-specific args
|
|
49
|
+
finalOutput, // Output file
|
|
50
|
+
];
|
|
51
|
+
logger.info(`Encoding ${totalFrames} frames → ${finalOutput} (${codecName}, CRF ${crf}, ${fps}fps)`);
|
|
52
|
+
await runFfmpeg(args);
|
|
53
|
+
// Get file stats
|
|
54
|
+
const stats = fs.statSync(finalOutput);
|
|
55
|
+
const duration = totalFrames / fps;
|
|
56
|
+
return {
|
|
57
|
+
outputPath: finalOutput,
|
|
58
|
+
format,
|
|
59
|
+
codec: codecName,
|
|
60
|
+
fps,
|
|
61
|
+
sizeBytes: stats.size,
|
|
62
|
+
sizeMB: (stats.size / (1024 * 1024)).toFixed(2),
|
|
63
|
+
duration,
|
|
64
|
+
totalFrames,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Add a fade-in and/or fade-out to an existing video
|
|
69
|
+
*/
|
|
70
|
+
export async function addFade(inputPath, outputPath, fadeInDuration = 0.5, fadeOutDuration = 0.5, totalDuration) {
|
|
71
|
+
const fadeOutStart = totalDuration - fadeOutDuration;
|
|
72
|
+
const filter = `fade=t=in:st=0:d=${fadeInDuration},fade=t=out:st=${fadeOutStart}:d=${fadeOutDuration}`;
|
|
73
|
+
const args = [
|
|
74
|
+
'-y',
|
|
75
|
+
'-i', inputPath,
|
|
76
|
+
'-vf', filter,
|
|
77
|
+
'-c:v', 'libx264',
|
|
78
|
+
'-crf', '18',
|
|
79
|
+
'-preset', 'medium',
|
|
80
|
+
'-pix_fmt', 'yuv420p',
|
|
81
|
+
'-movflags', '+faststart',
|
|
82
|
+
outputPath,
|
|
83
|
+
];
|
|
84
|
+
await runFfmpeg(args);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Concatenate multiple video clips with crossfade transitions
|
|
88
|
+
*/
|
|
89
|
+
export async function concatenateWithTransition(clips, outputPath, transitionDuration = 1, transitionType = 'fade') {
|
|
90
|
+
if (clips.length < 2) {
|
|
91
|
+
// Single clip — just copy
|
|
92
|
+
if (clips[0] && clips[0] !== outputPath) {
|
|
93
|
+
fs.copyFileSync(clips[0], outputPath);
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Build ffmpeg xfade filter chain for multiple clips
|
|
98
|
+
const inputs = [];
|
|
99
|
+
const filterParts = [];
|
|
100
|
+
for (const clip of clips) {
|
|
101
|
+
inputs.push('-i', clip);
|
|
102
|
+
}
|
|
103
|
+
// Chain xfade filters
|
|
104
|
+
let prevLabel = '0:v';
|
|
105
|
+
for (let i = 1; i < clips.length; i++) {
|
|
106
|
+
const outLabel = i === clips.length - 1 ? '' : `[v${i}]`;
|
|
107
|
+
const offset = i * 5 - transitionDuration; // Approximate offset
|
|
108
|
+
const filter = `[${prevLabel}][${i}:v]xfade=transition=${transitionType}:duration=${transitionDuration}:offset=${offset}`;
|
|
109
|
+
filterParts.push(filter + (outLabel ? outLabel : ''));
|
|
110
|
+
prevLabel = outLabel ? `v${i}` : '';
|
|
111
|
+
}
|
|
112
|
+
const args = [
|
|
113
|
+
'-y',
|
|
114
|
+
...inputs,
|
|
115
|
+
'-filter_complex', filterParts.join(';'),
|
|
116
|
+
'-c:v', 'libx264',
|
|
117
|
+
'-crf', '18',
|
|
118
|
+
'-pix_fmt', 'yuv420p',
|
|
119
|
+
'-movflags', '+faststart',
|
|
120
|
+
outputPath,
|
|
121
|
+
];
|
|
122
|
+
await runFfmpeg(args);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Run ffmpeg command and return a promise
|
|
126
|
+
*/
|
|
127
|
+
function runFfmpeg(args) {
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
execFile('ffmpeg', args, { maxBuffer: 50 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
130
|
+
if (error) {
|
|
131
|
+
logger.error(`ffmpeg failed: ${stderr}`);
|
|
132
|
+
reject(new Error(`ffmpeg failed: ${stderr || error.message}`));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
logger.debug(`ffmpeg output: ${stderr.slice(-200)}`);
|
|
136
|
+
resolve(stdout);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Clean up temporary frame files
|
|
142
|
+
*/
|
|
143
|
+
export function cleanupFrames(framesDir) {
|
|
144
|
+
try {
|
|
145
|
+
if (fs.existsSync(framesDir)) {
|
|
146
|
+
fs.rmSync(framesDir, { recursive: true, force: true });
|
|
147
|
+
logger.debug(`Cleaned up frames: ${framesDir}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
logger.warn(`Failed to cleanup frames: ${framesDir}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=encoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encoder.js","sourceRoot":"","sources":["../../../src/tools/engine/encoder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export { recordWebsite } from './capture.js';
|
|
2
|
+
export { encodeFrames, addFade, concatenateWithTransition, cleanupFrames } from './encoder.js';
|
|
3
|
+
export { getEasing, applyEasing, EASINGS } from './easing.js';
|
|
4
|
+
export { injectCursor, moveCursor, moveCursorToElement, animateClick, hideCursor } from './cursor.js';
|
|
5
|
+
export { executeScenes, createDefaultScenes } from './scenes.js';
|
|
6
|
+
export { addBackgroundMusic, getMediaDuration } from './audio.js';
|
|
7
|
+
export { concatenateVideos, generateIntro, TRANSITIONS } from './concat.js';
|
|
8
|
+
export { convertToSocialFormat, convertToAllFormats, SOCIAL_FORMATS } from './social-format.js';
|
|
9
|
+
export { addTextOverlays } from './text-overlay.js';
|
|
10
|
+
export { generateSpeech, listElevenLabsVoices } from './tts.js';
|
|
11
|
+
export { createNarratedVideo } from './narrated-video.js';
|
|
12
|
+
export { smartScreenshot } from './smart-screenshot.js';
|
|
13
|
+
export type { SmartScreenshotConfig, SmartScreenshotResult, SmartTarget, DetectedFeature } from './smart-screenshot.js';
|
|
14
|
+
export { applyLutPreset, listLutPresets, ALL_LUT_PRESETS, PRESET_DESCRIPTIONS } from './lut-presets.js';
|
|
15
|
+
export type { LutPreset, LutPresetConfig } from './lut-presets.js';
|
|
16
|
+
export { applyVoiceEffect, ALL_VOICE_EFFECTS, VOICE_EFFECT_DESCRIPTIONS } from './voice-effects.js';
|
|
17
|
+
export type { VoiceEffect, VoiceEffectConfig } from './voice-effects.js';
|
|
18
|
+
export { applyChromaKey } from './chroma-key.js';
|
|
19
|
+
export type { ChromaKeyConfig } from './chroma-key.js';
|
|
20
|
+
export { syncToBeats } from './beat-sync.js';
|
|
21
|
+
export type { BeatSyncConfig, BeatSyncResult } from './beat-sync.js';
|
|
22
|
+
export { animateText, ALL_TEXT_ANIMATIONS, TEXT_ANIMATION_DESCRIPTIONS } from './text-animations.js';
|
|
23
|
+
export type { TextAnimation, TextAnimationConfig, TextPosition as AnimTextPosition } from './text-animations.js';
|
|
24
|
+
export { mixAudioTracks } from './audio-mixer.js';
|
|
25
|
+
export type { AudioTrack, AudioMixConfig, AudioMixResult } from './audio-mixer.js';
|
|
26
|
+
export { listTemplates, getTemplate, getTemplateSummaries, getTemplateCategories } from './templates.js';
|
|
27
|
+
export type { VideoTemplate, TemplateCategory, TemplateSlot } from './templates.js';
|
|
28
|
+
export { renderTemplate } from './template-renderer.js';
|
|
29
|
+
export type { RenderTemplateConfig, RenderResult, TemplateAssets } from './template-renderer.js';
|
|
30
|
+
export * from './types.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { recordWebsite } from './capture.js';
|
|
2
|
+
export { encodeFrames, addFade, concatenateWithTransition, cleanupFrames } from './encoder.js';
|
|
3
|
+
export { getEasing, applyEasing, EASINGS } from './easing.js';
|
|
4
|
+
export { injectCursor, moveCursor, moveCursorToElement, animateClick, hideCursor } from './cursor.js';
|
|
5
|
+
export { executeScenes, createDefaultScenes } from './scenes.js';
|
|
6
|
+
export { addBackgroundMusic, getMediaDuration } from './audio.js';
|
|
7
|
+
export { concatenateVideos, generateIntro, TRANSITIONS } from './concat.js';
|
|
8
|
+
export { convertToSocialFormat, convertToAllFormats, SOCIAL_FORMATS } from './social-format.js';
|
|
9
|
+
export { addTextOverlays } from './text-overlay.js';
|
|
10
|
+
export { generateSpeech, listElevenLabsVoices } from './tts.js';
|
|
11
|
+
export { createNarratedVideo } from './narrated-video.js';
|
|
12
|
+
export { smartScreenshot } from './smart-screenshot.js';
|
|
13
|
+
// CapCut-tier engines
|
|
14
|
+
export { applyLutPreset, listLutPresets, ALL_LUT_PRESETS, PRESET_DESCRIPTIONS } from './lut-presets.js';
|
|
15
|
+
export { applyVoiceEffect, ALL_VOICE_EFFECTS, VOICE_EFFECT_DESCRIPTIONS } from './voice-effects.js';
|
|
16
|
+
export { applyChromaKey } from './chroma-key.js';
|
|
17
|
+
export { syncToBeats } from './beat-sync.js';
|
|
18
|
+
export { animateText, ALL_TEXT_ANIMATIONS, TEXT_ANIMATION_DESCRIPTIONS } from './text-animations.js';
|
|
19
|
+
export { mixAudioTracks } from './audio-mixer.js';
|
|
20
|
+
export { listTemplates, getTemplate, getTemplateSummaries, getTemplateCategories } from './templates.js';
|
|
21
|
+
export { renderTemplate } from './template-renderer.js';
|
|
22
|
+
export * from './types.js';
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/engine/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC/F,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACtG,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAChG,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,sBAAsB;AACtB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAExG,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAEpG,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AAErG,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEzG,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGxD,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LUT Preset Engine — 22 cinematic color grade presets via FFmpeg filter chains.
|
|
3
|
+
*
|
|
4
|
+
* No external .cube files needed — each preset is a combination of
|
|
5
|
+
* colorbalance, eq, curves, colorchannelmixer, and hue filters.
|
|
6
|
+
* Intensity parameter (0.0-1.0) blends graded output with the original.
|
|
7
|
+
*/
|
|
8
|
+
export type LutPreset = 'cinematic-teal-orange' | 'cinematic-teal-orange-subtle' | 'vintage-film' | 'vintage-kodachrome' | 'cross-process' | 'moody-dark' | 'warm-golden' | 'cold-blue' | 'film-noir' | 'noir-blue-tint' | 'bleach-bypass' | 'cyberpunk-neon' | 'cyberpunk-teal-pink' | 'desaturated-fincher' | 'pastel-dream' | 'matrix-green' | 'sepia' | 'blockbuster-extreme' | 'muted-forest' | 'high-contrast-music' | 'faded-lofi' | 'sunset-magic-hour';
|
|
9
|
+
export interface LutPresetConfig {
|
|
10
|
+
inputPath: string;
|
|
11
|
+
outputPath: string;
|
|
12
|
+
/** The color grade preset to apply */
|
|
13
|
+
preset: LutPreset;
|
|
14
|
+
/** Blend intensity: 0.0 (original) to 1.0 (full effect). Default: 1.0 */
|
|
15
|
+
intensity?: number;
|
|
16
|
+
}
|
|
17
|
+
/** Human-readable descriptions for each preset */
|
|
18
|
+
export declare const PRESET_DESCRIPTIONS: Record<LutPreset, string>;
|
|
19
|
+
export declare const ALL_LUT_PRESETS: LutPreset[];
|
|
20
|
+
export declare function applyLutPreset(config: LutPresetConfig): Promise<string>;
|
|
21
|
+
/** List all available presets with descriptions */
|
|
22
|
+
export declare function listLutPresets(): Array<{
|
|
23
|
+
name: LutPreset;
|
|
24
|
+
description: string;
|
|
25
|
+
}>;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LUT Preset Engine — 22 cinematic color grade presets via FFmpeg filter chains.
|
|
3
|
+
*
|
|
4
|
+
* No external .cube files needed — each preset is a combination of
|
|
5
|
+
* colorbalance, eq, curves, colorchannelmixer, and hue filters.
|
|
6
|
+
* Intensity parameter (0.0-1.0) blends graded output with the original.
|
|
7
|
+
*/
|
|
8
|
+
import { execFile } from 'child_process';
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { logger } from '../../lib/logger.js';
|
|
12
|
+
// ─── Preset Definitions ─────────────────────────────────────────────
|
|
13
|
+
const PRESET_FILTERS = {
|
|
14
|
+
'cinematic-teal-orange': "colorbalance=rs=-0.15:gs=-0.05:bs=0.25:rm=0.0:gm=-0.02:bm=0.05:rh=0.15:gh=0.02:bh=-0.2,eq=contrast=1.15:saturation=0.9:gamma=0.95,curves=r='0/0 0.25/0.22 0.5/0.55 0.75/0.80 1/1':b='0/0.05 0.25/0.28 0.5/0.45 0.75/0.70 1/0.9'",
|
|
15
|
+
'cinematic-teal-orange-subtle': "colorbalance=rs=-0.1:gs=-0.03:bs=0.18:rh=0.12:gh=0.02:bh=-0.15,eq=contrast=1.1:saturation=0.75:brightness=-0.02,curves=preset=medium_contrast",
|
|
16
|
+
'vintage-film': "curves=r='0/0.11 0.42/0.51 1/0.95':g='0/0 0.50/0.48 1/1':b='0/0.22 0.49/0.44 1/0.8',eq=saturation=0.8:contrast=0.9:gamma=1.1,colorbalance=rs=0.05:gs=0.02:bs=-0.05:rh=0.08:gh=0.05:bh=-0.03",
|
|
17
|
+
'vintage-kodachrome': "curves=r='0/0 0.15/0.18 0.5/0.58 0.85/0.88 1/1':g='0/0 0.5/0.48 1/0.92':b='0/0.06 0.5/0.44 1/0.85',eq=saturation=1.15:contrast=1.1,colorbalance=rs=0.04:gs=-0.02:bs=0.06:rh=0.06:gh=0.03:bh=-0.08",
|
|
18
|
+
'cross-process': "curves=r='0/0.2 0.5/0.6 1/0.9':g='0/0 0.5/0.55 1/1':b='0/0.3 0.5/0.4 1/0.8',eq=saturation=1.15:contrast=1.1",
|
|
19
|
+
'moody-dark': "eq=contrast=1.3:brightness=-0.08:saturation=0.65:gamma=0.85,colorbalance=rs=-0.05:gs=-0.02:bs=0.12:rm=-0.03:gm=-0.02:bm=0.05:rh=0.0:gh=-0.02:bh=0.05,curves=master='0/0 0.15/0.05 0.5/0.42 1/0.95'",
|
|
20
|
+
'warm-golden': "colorbalance=rs=0.12:gs=0.05:bs=-0.12:rm=0.06:gm=0.03:bm=-0.06:rh=0.1:gh=0.06:bh=-0.1,eq=saturation=1.15:contrast=1.05:brightness=0.03,curves=b='0/0 0.5/0.42 1/0.85'",
|
|
21
|
+
'cold-blue': "colorbalance=rs=-0.12:gs=-0.03:bs=0.2:rm=-0.06:gm=-0.02:bm=0.1:rh=-0.08:gh=0.0:bh=0.12,eq=saturation=0.7:contrast=1.1:brightness=0.05:gamma=1.1",
|
|
22
|
+
'film-noir': "colorchannelmixer=.3:.4:.3:0:.3:.4:.3:0:.3:.4:.3:0,eq=contrast=1.5:brightness=-0.05:gamma=0.9,curves=preset=strong_contrast",
|
|
23
|
+
'noir-blue-tint': "colorchannelmixer=.3:.4:.3:0:.3:.4:.3:0:.35:.45:.35:0,eq=contrast=1.4:brightness=-0.04:gamma=0.9",
|
|
24
|
+
'bleach-bypass': "eq=contrast=1.4:saturation=0.5:gamma=0.9,curves=preset=strong_contrast,colorbalance=rs=-0.02:gs=-0.02:bs=0.04",
|
|
25
|
+
'cyberpunk-neon': "eq=contrast=1.25:saturation=1.6:brightness=-0.05:gamma=0.9,colorbalance=rs=-0.1:gs=-0.15:bs=0.2:rm=0.15:gm=-0.1:bm=0.1:rh=0.2:gh=-0.05:bh=0.15,curves=r='0/0 0.3/0.2 0.6/0.7 1/1':b='0/0.05 0.4/0.5 1/1'",
|
|
26
|
+
'cyberpunk-teal-pink': "colorbalance=rs=-0.15:gs=-0.1:bs=0.25:rm=0.2:gm=-0.12:bm=0.12:rh=0.2:gh=-0.08:bh=0.2,eq=saturation=1.5:contrast=1.3:brightness=-0.06,hue=h=5",
|
|
27
|
+
'desaturated-fincher': "colorbalance=rs=-0.08:gs=-0.03:bs=0.12:rh=0.08:gh=0.02:bh=-0.1,eq=contrast=1.2:saturation=0.55:brightness=-0.03:gamma=0.92,curves=master='0/0 0.2/0.12 0.5/0.48 0.8/0.82 1/0.95'",
|
|
28
|
+
'pastel-dream': "eq=contrast=0.8:saturation=0.6:brightness=0.08:gamma=1.2,curves=master='0.0/0.15 0.5/0.55 1/0.9',colorbalance=rs=0.05:gs=0.03:bs=0.06:rh=0.04:gh=0.04:bh=0.02",
|
|
29
|
+
'matrix-green': "colorchannelmixer=rr=0.3:rg=0.6:rb=0.1:gr=0.1:gg=0.9:gb=0.0:br=0.1:bg=0.4:bb=0.5,eq=contrast=1.2:brightness=-0.03:gamma=0.9,curves=preset=increase_contrast",
|
|
30
|
+
'sepia': "colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131:0,eq=contrast=1.05:brightness=0.02",
|
|
31
|
+
'blockbuster-extreme': "colorbalance=rs=-0.2:gs=-0.1:bs=0.35:rm=0.05:gm=-0.03:bm=0.0:rh=0.2:gh=0.05:bh=-0.25,eq=contrast=1.25:saturation=1.1:gamma=0.92,curves=r='0/0 0.25/0.28 0.5/0.58 1/1':b='0/0.08 0.5/0.42 1/0.85'",
|
|
32
|
+
'muted-forest': "colorbalance=rs=0.03:gs=0.05:bs=-0.05:rm=-0.02:gm=0.04:bm=-0.03:rh=0.02:gh=0.02:bh=-0.04,eq=saturation=0.65:contrast=1.05:gamma=1.05,curves=g='0/0 0.5/0.52 1/0.92':r='0/0 0.5/0.48 1/0.95'",
|
|
33
|
+
'high-contrast-music': "eq=contrast=1.4:saturation=1.4:brightness=-0.02:gamma=0.85,curves=preset=strong_contrast",
|
|
34
|
+
'faded-lofi': "curves=master='0/0.08 0.25/0.2 0.75/0.78 1/0.92':r='0/0.05 1/0.95':b='0/0.08 1/0.88',eq=saturation=0.75:contrast=0.95",
|
|
35
|
+
'sunset-magic-hour': "colorbalance=rs=0.06:gs=-0.02:bs=0.08:rm=0.1:gm=0.04:bm=-0.05:rh=0.15:gh=0.08:bh=-0.12,eq=saturation=1.2:contrast=1.1:brightness=0.02,curves=r='0/0 0.5/0.56 1/1':b='0/0.03 0.5/0.44 1/0.88'",
|
|
36
|
+
};
|
|
37
|
+
/** Human-readable descriptions for each preset */
|
|
38
|
+
export const PRESET_DESCRIPTIONS = {
|
|
39
|
+
'cinematic-teal-orange': 'Hollywood blockbuster look — teal shadows, orange highlights (Transformers, Mad Max)',
|
|
40
|
+
'cinematic-teal-orange-subtle': 'Restrained teal-orange for drama/thriller tone',
|
|
41
|
+
'vintage-film': 'Faded 70s film — lifted blacks, warm cast, slightly desaturated',
|
|
42
|
+
'vintage-kodachrome': 'Iconic Kodachrome — saturated reds/yellows, slightly cool shadows',
|
|
43
|
+
'cross-process': 'Vivid, surreal color shifts — the "wrong chemistry" lab look',
|
|
44
|
+
'moody-dark': 'Crushed blacks, cold undertone — dark drama atmosphere',
|
|
45
|
+
'warm-golden': 'Sun-kissed warmth — golden hour / magic hour look',
|
|
46
|
+
'cold-blue': 'Icy blue, desaturated — arctic / winter feel',
|
|
47
|
+
'film-noir': 'Classic black & white with dramatic contrast and deep blacks',
|
|
48
|
+
'noir-blue-tint': 'B&W base with subtle cold blue wash',
|
|
49
|
+
'bleach-bypass': 'High contrast + desaturated + metallic — analog lab technique',
|
|
50
|
+
'cyberpunk-neon': 'Vivid blues, magentas, teals — oversaturated neon city',
|
|
51
|
+
'cyberpunk-teal-pink': 'Teal-pink variant cyberpunk — Blade Runner vibes',
|
|
52
|
+
'desaturated-fincher': 'Muted, controlled palette — David Fincher style (Gone Girl, Seven)',
|
|
53
|
+
'pastel-dream': 'Soft, lifted, airy — low contrast pastel feel',
|
|
54
|
+
'matrix-green': 'Green-tinted computer world from The Matrix',
|
|
55
|
+
'sepia': 'Classic warm sepia tone — antique photograph look',
|
|
56
|
+
'blockbuster-extreme': 'Aggressive orange & teal for action/superhero films',
|
|
57
|
+
'muted-forest': 'Desaturated greens and browns — indie film / A24 aesthetic',
|
|
58
|
+
'high-contrast-music': 'Punchy, vivid, crushed blacks — music video look',
|
|
59
|
+
'faded-lofi': 'Instagram-style faded shadows with slight color cast',
|
|
60
|
+
'sunset-magic-hour': 'Deep warm amber highlights, slightly purple shadows',
|
|
61
|
+
};
|
|
62
|
+
export const ALL_LUT_PRESETS = Object.keys(PRESET_FILTERS);
|
|
63
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
64
|
+
function runFfmpeg(args, timeoutMs = 300_000) {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
execFile('ffmpeg', args, { maxBuffer: 100 * 1024 * 1024, timeout: timeoutMs }, (error, stdout, stderr) => {
|
|
67
|
+
if (error) {
|
|
68
|
+
logger.error(`ffmpeg failed: ${stderr}`);
|
|
69
|
+
reject(new Error(`ffmpeg failed: ${stderr || error.message}`));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
resolve(stdout);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function ensureDir(filePath) {
|
|
77
|
+
const dir = path.dirname(filePath);
|
|
78
|
+
if (!fs.existsSync(dir))
|
|
79
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
function assertExists(filePath, label = 'File') {
|
|
82
|
+
if (!fs.existsSync(filePath))
|
|
83
|
+
throw new Error(`${label} not found: ${filePath}`);
|
|
84
|
+
}
|
|
85
|
+
function fileInfo(filePath) {
|
|
86
|
+
const stats = fs.statSync(filePath);
|
|
87
|
+
return `${(stats.size / 1024 / 1024).toFixed(2)} MB`;
|
|
88
|
+
}
|
|
89
|
+
// ─── Main Function ──────────────────────────────────────────────────
|
|
90
|
+
export async function applyLutPreset(config) {
|
|
91
|
+
const { inputPath, outputPath, preset, intensity = 1.0 } = config;
|
|
92
|
+
assertExists(inputPath, 'Input video');
|
|
93
|
+
ensureDir(outputPath);
|
|
94
|
+
const filterChain = PRESET_FILTERS[preset];
|
|
95
|
+
if (!filterChain) {
|
|
96
|
+
throw new Error(`Unknown LUT preset: ${preset}. Available: ${ALL_LUT_PRESETS.join(', ')}`);
|
|
97
|
+
}
|
|
98
|
+
const clampedIntensity = Math.max(0, Math.min(1, intensity));
|
|
99
|
+
logger.info(`Applying LUT preset: ${preset} (intensity: ${clampedIntensity})`);
|
|
100
|
+
let args;
|
|
101
|
+
if (clampedIntensity >= 0.99) {
|
|
102
|
+
// Full intensity — no blending needed
|
|
103
|
+
args = [
|
|
104
|
+
'-y', '-i', inputPath,
|
|
105
|
+
'-vf', filterChain,
|
|
106
|
+
'-c:a', 'copy',
|
|
107
|
+
'-c:v', 'libx264', '-crf', '18', '-preset', 'medium',
|
|
108
|
+
'-pix_fmt', 'yuv420p', '-movflags', '+faststart',
|
|
109
|
+
outputPath,
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Partial intensity — blend with original using split+blend
|
|
114
|
+
const origWeight = (1 - clampedIntensity).toFixed(4);
|
|
115
|
+
const gradedWeight = clampedIntensity.toFixed(4);
|
|
116
|
+
const filterComplex = [
|
|
117
|
+
`[0:v]split[original][tograde]`,
|
|
118
|
+
`[tograde]${filterChain}[graded]`,
|
|
119
|
+
`[original][graded]blend=all_expr='A*${origWeight}+B*${gradedWeight}'[out]`,
|
|
120
|
+
].join(';');
|
|
121
|
+
args = [
|
|
122
|
+
'-y', '-i', inputPath,
|
|
123
|
+
'-filter_complex', filterComplex,
|
|
124
|
+
'-map', '[out]', '-map', '0:a?',
|
|
125
|
+
'-c:v', 'libx264', '-crf', '18', '-preset', 'medium',
|
|
126
|
+
'-pix_fmt', 'yuv420p', '-movflags', '+faststart',
|
|
127
|
+
outputPath,
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
await runFfmpeg(args);
|
|
131
|
+
logger.info(`LUT preset applied: ${preset} → ${outputPath} (${fileInfo(outputPath)})`);
|
|
132
|
+
return outputPath;
|
|
133
|
+
}
|
|
134
|
+
/** List all available presets with descriptions */
|
|
135
|
+
export function listLutPresets() {
|
|
136
|
+
return ALL_LUT_PRESETS.map(name => ({
|
|
137
|
+
name,
|
|
138
|
+
description: PRESET_DESCRIPTIONS[name],
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=lut-presets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lut-presets.js","sourceRoot":"","sources":["../../../src/tools/engine/lut-presets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrated Video Engine
|
|
3
|
+
* Combines TTS voice generation with website recording
|
|
4
|
+
* for fully automated explainer videos
|
|
5
|
+
*
|
|
6
|
+
* Flow:
|
|
7
|
+
* 1. Generate speech from script segments
|
|
8
|
+
* 2. Record website scenes synchronized to speech durations
|
|
9
|
+
* 3. Merge video + audio into final output
|
|
10
|
+
*/
|
|
11
|
+
import type { TTSProvider, ElevenLabsVoice, ElevenLabsModel, OpenAIVoice, OpenAIModel } from './tts.js';
|
|
12
|
+
import type { Scene, ViewportPreset } from './types.js';
|
|
13
|
+
export interface NarrationSegment {
|
|
14
|
+
/** Text to speak for this segment */
|
|
15
|
+
text: string;
|
|
16
|
+
/** Scene action during this segment */
|
|
17
|
+
scene: Scene;
|
|
18
|
+
/** Extra padding time after speech (seconds, default: 0.5) */
|
|
19
|
+
paddingAfter?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface NarratedVideoConfig {
|
|
22
|
+
/** URL to record */
|
|
23
|
+
url: string;
|
|
24
|
+
/** Narration script segments */
|
|
25
|
+
segments: NarrationSegment[];
|
|
26
|
+
/** Output path (without extension) */
|
|
27
|
+
outputPath: string;
|
|
28
|
+
/** TTS provider (default: elevenlabs) */
|
|
29
|
+
provider?: TTSProvider;
|
|
30
|
+
/** Language (default: en) */
|
|
31
|
+
language?: string;
|
|
32
|
+
/** Viewport (default: desktop) */
|
|
33
|
+
viewport?: ViewportPreset;
|
|
34
|
+
/** ElevenLabs voice (default: adam) */
|
|
35
|
+
elevenLabsVoice?: ElevenLabsVoice | string;
|
|
36
|
+
/** ElevenLabs model */
|
|
37
|
+
elevenLabsModel?: ElevenLabsModel;
|
|
38
|
+
/** OpenAI voice */
|
|
39
|
+
openaiVoice?: OpenAIVoice;
|
|
40
|
+
/** OpenAI model */
|
|
41
|
+
openaiModel?: OpenAIModel;
|
|
42
|
+
/** Speaking speed (default: 1.0) */
|
|
43
|
+
speed?: number;
|
|
44
|
+
/** Music volume if background music provided (default: 0.1) */
|
|
45
|
+
backgroundMusicVolume?: number;
|
|
46
|
+
/** Background music path (optional) */
|
|
47
|
+
backgroundMusicPath?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface NarratedVideoResult {
|
|
50
|
+
success: boolean;
|
|
51
|
+
video: {
|
|
52
|
+
path: string;
|
|
53
|
+
duration: number;
|
|
54
|
+
sizeMB: string;
|
|
55
|
+
};
|
|
56
|
+
audio: {
|
|
57
|
+
totalSegments: number;
|
|
58
|
+
totalDuration: number;
|
|
59
|
+
provider: TTSProvider;
|
|
60
|
+
};
|
|
61
|
+
url: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function createNarratedVideo(config: NarratedVideoConfig): Promise<NarratedVideoResult>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrated Video Engine
|
|
3
|
+
* Combines TTS voice generation with website recording
|
|
4
|
+
* for fully automated explainer videos
|
|
5
|
+
*
|
|
6
|
+
* Flow:
|
|
7
|
+
* 1. Generate speech from script segments
|
|
8
|
+
* 2. Record website scenes synchronized to speech durations
|
|
9
|
+
* 3. Merge video + audio into final output
|
|
10
|
+
*/
|
|
11
|
+
import { execFile } from 'child_process';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import { logger } from '../../lib/logger.js';
|
|
15
|
+
import { generateSpeech } from './tts.js';
|
|
16
|
+
import { recordWebsite } from './capture.js';
|
|
17
|
+
import { getMediaDuration } from './audio.js';
|
|
18
|
+
// ─── Main Function ──────────────────────────────────────────────────
|
|
19
|
+
export async function createNarratedVideo(config) {
|
|
20
|
+
const { url, segments, outputPath, provider = 'elevenlabs', language = 'en', viewport = 'desktop', } = config;
|
|
21
|
+
const tempDir = `/tmp/narrated-video-${Date.now()}`;
|
|
22
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
23
|
+
try {
|
|
24
|
+
// ─── Step 1: Generate all speech segments ───────────────────
|
|
25
|
+
logger.info(`Generating ${segments.length} speech segment(s)...`);
|
|
26
|
+
const audioPaths = [];
|
|
27
|
+
const audioDurations = [];
|
|
28
|
+
for (let i = 0; i < segments.length; i++) {
|
|
29
|
+
const seg = segments[i];
|
|
30
|
+
const audioPath = path.join(tempDir, `segment-${String(i).padStart(3, '0')}.mp3`);
|
|
31
|
+
const ttsResult = await generateSpeech({
|
|
32
|
+
text: seg.text,
|
|
33
|
+
outputPath: audioPath,
|
|
34
|
+
provider,
|
|
35
|
+
language,
|
|
36
|
+
elevenLabsVoice: config.elevenLabsVoice,
|
|
37
|
+
elevenLabsModel: config.elevenLabsModel,
|
|
38
|
+
openaiVoice: config.openaiVoice,
|
|
39
|
+
openaiModel: config.openaiModel,
|
|
40
|
+
speed: config.speed,
|
|
41
|
+
});
|
|
42
|
+
audioPaths.push(ttsResult.audioPath);
|
|
43
|
+
audioDurations.push(ttsResult.duration);
|
|
44
|
+
logger.info(`Segment ${i + 1}: "${seg.text.slice(0, 50)}..." → ${ttsResult.duration.toFixed(1)}s`);
|
|
45
|
+
}
|
|
46
|
+
// ─── Step 2: Concatenate audio segments ─────────────────────
|
|
47
|
+
logger.info('Concatenating audio segments...');
|
|
48
|
+
const fullAudioPath = path.join(tempDir, 'full-narration.mp3');
|
|
49
|
+
await concatenateAudio(audioPaths, fullAudioPath);
|
|
50
|
+
const totalAudioDuration = await getMediaDuration(fullAudioPath);
|
|
51
|
+
logger.info(`Total narration: ${totalAudioDuration.toFixed(1)}s`);
|
|
52
|
+
// ─── Step 3: Build scenes with matched durations ────────────
|
|
53
|
+
logger.info('Building synchronized scenes...');
|
|
54
|
+
const scenes = [];
|
|
55
|
+
for (let i = 0; i < segments.length; i++) {
|
|
56
|
+
const seg = segments[i];
|
|
57
|
+
const padding = seg.paddingAfter ?? 0.5;
|
|
58
|
+
const sceneDuration = audioDurations[i] + padding;
|
|
59
|
+
// Override scene duration to match audio
|
|
60
|
+
const scene = { ...seg.scene };
|
|
61
|
+
if ('duration' in scene) {
|
|
62
|
+
scene.duration = sceneDuration;
|
|
63
|
+
}
|
|
64
|
+
scenes.push(scene);
|
|
65
|
+
}
|
|
66
|
+
// ─── Step 4: Record website with synced scenes ──────────────
|
|
67
|
+
logger.info('Recording website with synchronized scenes...');
|
|
68
|
+
const videoOnlyPath = path.join(tempDir, 'video-only');
|
|
69
|
+
const recordResult = await recordWebsite({
|
|
70
|
+
url,
|
|
71
|
+
outputPath: videoOnlyPath,
|
|
72
|
+
viewport,
|
|
73
|
+
fps: 60,
|
|
74
|
+
scenes,
|
|
75
|
+
cursor: { enabled: false },
|
|
76
|
+
encoding: { codec: 'h264', crf: 18 },
|
|
77
|
+
});
|
|
78
|
+
// ─── Step 5: Merge video + audio ────────────────────────────
|
|
79
|
+
logger.info('Merging video + narration...');
|
|
80
|
+
const finalPath = outputPath.endsWith('.mp4') ? outputPath : `${outputPath}.mp4`;
|
|
81
|
+
const outDir = path.dirname(finalPath);
|
|
82
|
+
if (!fs.existsSync(outDir))
|
|
83
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
84
|
+
const mergeArgs = [
|
|
85
|
+
'-y',
|
|
86
|
+
'-i', recordResult.video.path,
|
|
87
|
+
'-i', fullAudioPath,
|
|
88
|
+
];
|
|
89
|
+
// Optional background music
|
|
90
|
+
if (config.backgroundMusicPath && fs.existsSync(config.backgroundMusicPath)) {
|
|
91
|
+
const musicVol = config.backgroundMusicVolume ?? 0.1;
|
|
92
|
+
mergeArgs.push('-stream_loop', '-1', '-i', config.backgroundMusicPath);
|
|
93
|
+
// Mix narration + background music
|
|
94
|
+
mergeArgs.push('-filter_complex', `[1:a]volume=1.0[narration];[2:a]volume=${musicVol},afade=t=in:st=0:d=2[music];[narration][music]amix=inputs=2:duration=first[aout]`, '-map', '0:v', '-map', '[aout]');
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
mergeArgs.push('-map', '0:v', '-map', '1:a');
|
|
98
|
+
}
|
|
99
|
+
mergeArgs.push('-c:v', 'copy', '-c:a', 'aac', '-b:a', '192k', '-shortest', '-movflags', '+faststart', finalPath);
|
|
100
|
+
await runFfmpeg(mergeArgs);
|
|
101
|
+
const finalStats = fs.statSync(finalPath);
|
|
102
|
+
const finalDuration = await getMediaDuration(finalPath);
|
|
103
|
+
// ─── Cleanup ────────────────────────────────────────────────
|
|
104
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
105
|
+
logger.info(`Narrated video ready: ${finalPath} (${finalDuration.toFixed(1)}s, ${(finalStats.size / 1024 / 1024).toFixed(2)} MB)`);
|
|
106
|
+
return {
|
|
107
|
+
success: true,
|
|
108
|
+
video: {
|
|
109
|
+
path: finalPath,
|
|
110
|
+
duration: finalDuration,
|
|
111
|
+
sizeMB: (finalStats.size / 1024 / 1024).toFixed(2),
|
|
112
|
+
},
|
|
113
|
+
audio: {
|
|
114
|
+
totalSegments: segments.length,
|
|
115
|
+
totalDuration: totalAudioDuration,
|
|
116
|
+
provider,
|
|
117
|
+
},
|
|
118
|
+
url,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
// Cleanup on error
|
|
123
|
+
try {
|
|
124
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
catch { /* ignore */ }
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
131
|
+
/**
|
|
132
|
+
* Concatenate multiple audio files using ffmpeg concat demuxer
|
|
133
|
+
*/
|
|
134
|
+
async function concatenateAudio(files, outputPath) {
|
|
135
|
+
// Create concat list file
|
|
136
|
+
const listPath = outputPath + '.txt';
|
|
137
|
+
const listContent = files.map((f) => `file '${f}'`).join('\n');
|
|
138
|
+
fs.writeFileSync(listPath, listContent);
|
|
139
|
+
await runFfmpeg([
|
|
140
|
+
'-y',
|
|
141
|
+
'-f', 'concat',
|
|
142
|
+
'-safe', '0',
|
|
143
|
+
'-i', listPath,
|
|
144
|
+
'-c:a', 'libmp3lame',
|
|
145
|
+
'-b:a', '192k',
|
|
146
|
+
outputPath,
|
|
147
|
+
]);
|
|
148
|
+
// Cleanup list file
|
|
149
|
+
fs.unlinkSync(listPath);
|
|
150
|
+
}
|
|
151
|
+
function runFfmpeg(args) {
|
|
152
|
+
return new Promise((resolve, reject) => {
|
|
153
|
+
execFile('ffmpeg', args, { maxBuffer: 50 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
154
|
+
if (error) {
|
|
155
|
+
logger.error(`ffmpeg failed: ${stderr}`);
|
|
156
|
+
reject(new Error(`ffmpeg failed: ${stderr || error.message}`));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
resolve(stdout);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=narrated-video.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrated-video.js","sourceRoot":"","sources":["../../../src/tools/engine/narrated-video.ts"],"names":[],"mappings":"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"}
|