@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,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voice Effects Engine — 9 audio effects via FFmpeg filter chains.
|
|
3
|
+
*
|
|
4
|
+
* Effects: echo, reverb, deep, chipmunk, robot, whisper, radio, megaphone, underwater.
|
|
5
|
+
* Works on both audio files and video files (preserves video stream).
|
|
6
|
+
*/
|
|
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
|
+
|
|
13
|
+
// ─── Types ──────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export type VoiceEffect =
|
|
16
|
+
| 'echo'
|
|
17
|
+
| 'reverb'
|
|
18
|
+
| 'deep'
|
|
19
|
+
| 'chipmunk'
|
|
20
|
+
| 'robot'
|
|
21
|
+
| 'whisper'
|
|
22
|
+
| 'radio'
|
|
23
|
+
| 'megaphone'
|
|
24
|
+
| 'underwater';
|
|
25
|
+
|
|
26
|
+
export interface VoiceEffectConfig {
|
|
27
|
+
inputPath: string;
|
|
28
|
+
outputPath: string;
|
|
29
|
+
/** Voice effect to apply */
|
|
30
|
+
effect: VoiceEffect;
|
|
31
|
+
/** Intensity 0.0-1.0 (default: 0.5). Controls how strong the effect is. */
|
|
32
|
+
intensity?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const ALL_VOICE_EFFECTS: VoiceEffect[] = [
|
|
36
|
+
'echo', 'reverb', 'deep', 'chipmunk', 'robot',
|
|
37
|
+
'whisper', 'radio', 'megaphone', 'underwater',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
export const VOICE_EFFECT_DESCRIPTIONS: Record<VoiceEffect, string> = {
|
|
41
|
+
echo: 'Indoor/outdoor echo with configurable delay',
|
|
42
|
+
reverb: 'Dense multi-tap reverb (hall-like)',
|
|
43
|
+
deep: 'Lower pitch — deep/bass voice',
|
|
44
|
+
chipmunk: 'Higher pitch — chipmunk/squeaky voice',
|
|
45
|
+
robot: 'Metallic robotic voice (phase zeroing)',
|
|
46
|
+
whisper: 'Breathy whisper effect with randomized phase',
|
|
47
|
+
radio: 'AM radio / telephone quality (bandpass)',
|
|
48
|
+
megaphone: 'Distorted megaphone with resonance',
|
|
49
|
+
underwater: 'Muffled underwater sound (heavy lowpass)',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// ─── Effect Filter Builders ─────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
function getEffectFilter(effect: VoiceEffect, intensity: number): { filter: string; needsComplexPitch?: boolean; pitchFactor?: number } {
|
|
55
|
+
const i = Math.max(0, Math.min(1, intensity));
|
|
56
|
+
|
|
57
|
+
switch (effect) {
|
|
58
|
+
case 'echo': {
|
|
59
|
+
// Scale delay and decay with intensity
|
|
60
|
+
const delay1 = Math.round(30 + i * 470); // 30-500ms
|
|
61
|
+
const delay2 = Math.round(60 + i * 940); // 60-1000ms
|
|
62
|
+
const decay1 = (0.2 + i * 0.3).toFixed(2); // 0.2-0.5
|
|
63
|
+
const decay2 = (0.1 + i * 0.2).toFixed(2); // 0.1-0.3
|
|
64
|
+
return { filter: `aecho=0.8:0.9:${delay1}|${delay2}:${decay1}|${decay2}` };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
case 'reverb': {
|
|
68
|
+
// Dense multi-tap approximation of reverb
|
|
69
|
+
const taps = Math.round(3 + i * 7); // 3-10 taps
|
|
70
|
+
const delays: string[] = [];
|
|
71
|
+
const decays: string[] = [];
|
|
72
|
+
for (let t = 1; t <= taps; t++) {
|
|
73
|
+
delays.push(String(Math.round(t * 20)));
|
|
74
|
+
decays.push((0.6 - t * (0.5 / taps)).toFixed(2));
|
|
75
|
+
}
|
|
76
|
+
return { filter: `aecho=0.8:0.9:${delays.join('|')}:${decays.join('|')}` };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case 'deep': {
|
|
80
|
+
// Lower pitch: factor < 1.0 lowers pitch
|
|
81
|
+
// Intensity 0.0 → factor 0.95 (subtle), 1.0 → factor 0.5 (very deep)
|
|
82
|
+
const factor = 0.95 - i * 0.45; // 0.95 → 0.5
|
|
83
|
+
return { filter: '', needsComplexPitch: true, pitchFactor: factor };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
case 'chipmunk': {
|
|
87
|
+
// Higher pitch: factor > 1.0 raises pitch
|
|
88
|
+
// Intensity 0.0 → factor 1.1 (subtle), 1.0 → factor 2.0 (extreme)
|
|
89
|
+
const factor = 1.1 + i * 0.9; // 1.1 → 2.0
|
|
90
|
+
return { filter: '', needsComplexPitch: true, pitchFactor: factor };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case 'robot': {
|
|
94
|
+
// Phase zeroing creates metallic robot effect
|
|
95
|
+
// At lower intensity, mix with short echo for metallic quality
|
|
96
|
+
if (i > 0.5) {
|
|
97
|
+
return { filter: "afftfilt=real='hypot(re,im)*sin(0)':imag='hypot(re,im)*cos(0)':win_size=512:overlap=0.75" };
|
|
98
|
+
}
|
|
99
|
+
// Lighter robot via very short echo
|
|
100
|
+
const delay = Math.round(3 + i * 10);
|
|
101
|
+
return { filter: `aecho=0.8:0.88:${delay}:${(0.3 + i * 0.3).toFixed(2)}` };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
case 'whisper': {
|
|
105
|
+
if (i > 0.5) {
|
|
106
|
+
// Full whisper via phase randomization
|
|
107
|
+
return { filter: "afftfilt=real='hypot(re,im)*cos((random(0)*2-1)*2*3.14)':imag='hypot(re,im)*sin((random(1)*2-1)*2*3.14)':win_size=128:overlap=0.8" };
|
|
108
|
+
}
|
|
109
|
+
// Lighter whisper via filtering
|
|
110
|
+
const vol = (0.6 - i * 0.3).toFixed(2);
|
|
111
|
+
return { filter: `highpass=f=1000,lowpass=f=4000,volume=${vol}` };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
case 'radio': {
|
|
115
|
+
// Bandpass gets narrower with intensity
|
|
116
|
+
const lowCut = Math.round(200 + i * 400); // 200-600 Hz
|
|
117
|
+
const highCut = Math.round(4000 - i * 2000); // 4000-2000 Hz
|
|
118
|
+
const crush = i > 0.5 ? `,acrusher=bits=${Math.round(12 - i * 6)}:mode=log:aa=1` : '';
|
|
119
|
+
return { filter: `highpass=f=${lowCut},lowpass=f=${highCut}${crush}` };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
case 'megaphone': {
|
|
123
|
+
const bits = Math.round(12 - i * 6); // 12→6 bits
|
|
124
|
+
const vol = (1.5 + i * 1.0).toFixed(1);
|
|
125
|
+
return { filter: `highpass=f=500,lowpass=f=4000,acrusher=bits=${bits}:mode=log:aa=1:samples=1,aecho=0.8:0.88:10:${(0.3 + i * 0.3).toFixed(2)},volume=${vol}` };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case 'underwater': {
|
|
129
|
+
// Lowpass gets more extreme with intensity
|
|
130
|
+
const cutoff = Math.round(500 - i * 350); // 500→150 Hz
|
|
131
|
+
const vol = (0.9 - i * 0.3).toFixed(2);
|
|
132
|
+
const wobble = i > 0.3 ? `,flanger=delay=5:depth=${Math.round(1 + i * 4)}:speed=0.3` : '';
|
|
133
|
+
return { filter: `lowpass=f=${cutoff}${wobble},volume=${vol}` };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
default:
|
|
137
|
+
throw new Error(`Unknown voice effect: ${effect}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
function runFfmpeg(args: string[], timeoutMs = 300_000): Promise<string> {
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
execFile('ffmpeg', args, { maxBuffer: 100 * 1024 * 1024, timeout: timeoutMs }, (error, stdout, stderr) => {
|
|
146
|
+
if (error) {
|
|
147
|
+
logger.error(`ffmpeg failed: ${stderr}`);
|
|
148
|
+
reject(new Error(`ffmpeg failed: ${stderr || error.message}`));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
resolve(stdout);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function runFfprobe(args: string[]): Promise<string> {
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
execFile('ffprobe', args, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout) => {
|
|
159
|
+
if (error) { reject(new Error(`ffprobe failed: ${error.message}`)); return; }
|
|
160
|
+
resolve(stdout.trim());
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function ensureDir(filePath: string): void {
|
|
166
|
+
const dir = path.dirname(filePath);
|
|
167
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function assertExists(filePath: string, label = 'File'): void {
|
|
171
|
+
if (!fs.existsSync(filePath)) throw new Error(`${label} not found: ${filePath}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function fileInfo(filePath: string): string {
|
|
175
|
+
const stats = fs.statSync(filePath);
|
|
176
|
+
return `${(stats.size / 1024 / 1024).toFixed(2)} MB`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function hasVideoStream(filePath: string): Promise<boolean> {
|
|
180
|
+
try {
|
|
181
|
+
const result = await runFfprobe(['-v', 'quiet', '-select_streams', 'v', '-show_entries', 'stream=codec_type', '-of', 'csv=p=0', filePath]);
|
|
182
|
+
return result.length > 0;
|
|
183
|
+
} catch { return false; }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function getSampleRate(filePath: string): Promise<number> {
|
|
187
|
+
try {
|
|
188
|
+
const result = await runFfprobe(['-v', 'quiet', '-select_streams', 'a:0', '-show_entries', 'stream=sample_rate', '-of', 'csv=p=0', filePath]);
|
|
189
|
+
const rate = parseInt(result, 10);
|
|
190
|
+
return isNaN(rate) ? 44100 : rate;
|
|
191
|
+
} catch { return 44100; }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Build chained atempo filters (each must be 0.5-100.0 range) */
|
|
195
|
+
function buildAtempoChain(speed: number): string {
|
|
196
|
+
const filters: string[] = [];
|
|
197
|
+
let remaining = speed;
|
|
198
|
+
|
|
199
|
+
while (remaining > 2.0) {
|
|
200
|
+
filters.push('atempo=2.0');
|
|
201
|
+
remaining /= 2.0;
|
|
202
|
+
}
|
|
203
|
+
while (remaining < 0.5) {
|
|
204
|
+
filters.push('atempo=0.5');
|
|
205
|
+
remaining /= 0.5;
|
|
206
|
+
}
|
|
207
|
+
filters.push(`atempo=${remaining.toFixed(4)}`);
|
|
208
|
+
return filters.join(',');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ─── Main Function ──────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
export async function applyVoiceEffect(config: VoiceEffectConfig): Promise<string> {
|
|
214
|
+
const { inputPath, outputPath, effect, intensity = 0.5 } = config;
|
|
215
|
+
|
|
216
|
+
assertExists(inputPath, 'Input file');
|
|
217
|
+
ensureDir(outputPath);
|
|
218
|
+
|
|
219
|
+
const clampedIntensity = Math.max(0, Math.min(1, intensity));
|
|
220
|
+
logger.info(`Applying voice effect: ${effect} (intensity: ${clampedIntensity})`);
|
|
221
|
+
|
|
222
|
+
const effectData = getEffectFilter(effect, clampedIntensity);
|
|
223
|
+
const hasVideo = await hasVideoStream(inputPath);
|
|
224
|
+
|
|
225
|
+
if (effectData.needsComplexPitch && effectData.pitchFactor) {
|
|
226
|
+
// Pitch shifting requires asetrate + atempo combination
|
|
227
|
+
const sampleRate = await getSampleRate(inputPath);
|
|
228
|
+
const factor = effectData.pitchFactor;
|
|
229
|
+
const newRate = Math.round(sampleRate * factor);
|
|
230
|
+
const tempoCompensation = 1 / factor;
|
|
231
|
+
|
|
232
|
+
const audioFilter = `asetrate=${newRate},${buildAtempoChain(tempoCompensation)},aresample=${sampleRate}`;
|
|
233
|
+
|
|
234
|
+
const args = ['-y', '-i', inputPath];
|
|
235
|
+
if (hasVideo) {
|
|
236
|
+
args.push('-af', audioFilter, '-c:v', 'copy', '-movflags', '+faststart');
|
|
237
|
+
} else {
|
|
238
|
+
args.push('-af', audioFilter);
|
|
239
|
+
}
|
|
240
|
+
args.push(outputPath);
|
|
241
|
+
|
|
242
|
+
await runFfmpeg(args);
|
|
243
|
+
} else {
|
|
244
|
+
// Standard filter-based effects
|
|
245
|
+
const args = ['-y', '-i', inputPath];
|
|
246
|
+
if (hasVideo) {
|
|
247
|
+
args.push('-af', effectData.filter, '-c:v', 'copy', '-movflags', '+faststart');
|
|
248
|
+
} else {
|
|
249
|
+
args.push('-af', effectData.filter);
|
|
250
|
+
}
|
|
251
|
+
args.push(outputPath);
|
|
252
|
+
|
|
253
|
+
await runFfmpeg(args);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
logger.info(`Voice effect applied: ${effect} → ${outputPath} (${fileInfo(outputPath)})`);
|
|
257
|
+
return outputPath;
|
|
258
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Core recording
|
|
2
|
+
export { recordWebsite } from './engine/index.js';
|
|
3
|
+
export type { RecordingConfig, RecordingResult, Scene, ViewportPreset, EasingName } from './engine/index.js';
|
|
4
|
+
|
|
5
|
+
// Post-production
|
|
6
|
+
export { addBackgroundMusic } from './engine/audio.js';
|
|
7
|
+
export type { AddMusicConfig } from './engine/audio.js';
|
|
8
|
+
export { concatenateVideos, generateIntro } from './engine/concat.js';
|
|
9
|
+
export type { ConcatConfig, ConcatClip, IntroConfig, TransitionType } from './engine/concat.js';
|
|
10
|
+
export { convertToSocialFormat, convertToAllFormats, SOCIAL_FORMATS } from './engine/social-format.js';
|
|
11
|
+
export type { SocialFormat, CropStrategy, FormatConvertConfig } from './engine/social-format.js';
|
|
12
|
+
export { addTextOverlays } from './engine/text-overlay.js';
|
|
13
|
+
export type { TextOverlay, TextPosition } from './engine/text-overlay.js';
|
|
14
|
+
|
|
15
|
+
// TTS & Narration
|
|
16
|
+
export { generateSpeech, listElevenLabsVoices } from './engine/tts.js';
|
|
17
|
+
export type { TTSConfig, TTSResult, TTSProvider, ElevenLabsVoice, OpenAIVoice } from './engine/tts.js';
|
|
18
|
+
export { createNarratedVideo } from './engine/narrated-video.js';
|
|
19
|
+
export type { NarratedVideoConfig, NarratedVideoResult, NarrationSegment } from './engine/narrated-video.js';
|
|
20
|
+
|
|
21
|
+
// Editing (NEW — CapCut-tier features)
|
|
22
|
+
export {
|
|
23
|
+
adjustVideoSpeed,
|
|
24
|
+
applyColorGrade,
|
|
25
|
+
applyVideoEffect,
|
|
26
|
+
cropVideo,
|
|
27
|
+
reverseClip,
|
|
28
|
+
extractAudio,
|
|
29
|
+
burnSubtitles,
|
|
30
|
+
autoCaption,
|
|
31
|
+
addKeyframeAnimation,
|
|
32
|
+
composePip,
|
|
33
|
+
addAudioDucking,
|
|
34
|
+
} from './engine/editing.js';
|
|
35
|
+
export type {
|
|
36
|
+
SpeedConfig,
|
|
37
|
+
ColorGradeConfig,
|
|
38
|
+
VideoEffect, EffectConfig,
|
|
39
|
+
CropConfig,
|
|
40
|
+
ReverseConfig,
|
|
41
|
+
ExtractAudioConfig,
|
|
42
|
+
BurnSubtitlesConfig,
|
|
43
|
+
AutoCaptionConfig, AutoCaptionResult,
|
|
44
|
+
Keyframe, KeyframeAnimationConfig,
|
|
45
|
+
PipPosition, PipConfig,
|
|
46
|
+
AudioDuckingConfig,
|
|
47
|
+
} from './engine/editing.js';
|
|
48
|
+
|
|
49
|
+
// CapCut-tier engines
|
|
50
|
+
export { applyLutPreset, listLutPresets, ALL_LUT_PRESETS, PRESET_DESCRIPTIONS } from './engine/lut-presets.js';
|
|
51
|
+
export type { LutPreset, LutPresetConfig } from './engine/lut-presets.js';
|
|
52
|
+
export { applyVoiceEffect, ALL_VOICE_EFFECTS, VOICE_EFFECT_DESCRIPTIONS } from './engine/voice-effects.js';
|
|
53
|
+
export type { VoiceEffect, VoiceEffectConfig } from './engine/voice-effects.js';
|
|
54
|
+
export { applyChromaKey } from './engine/chroma-key.js';
|
|
55
|
+
export type { ChromaKeyConfig } from './engine/chroma-key.js';
|
|
56
|
+
export { syncToBeats } from './engine/beat-sync.js';
|
|
57
|
+
export type { BeatSyncConfig, BeatSyncResult } from './engine/beat-sync.js';
|
|
58
|
+
export { animateText, ALL_TEXT_ANIMATIONS, TEXT_ANIMATION_DESCRIPTIONS } from './engine/text-animations.js';
|
|
59
|
+
export type { TextAnimation, TextAnimationConfig } from './engine/text-animations.js';
|
|
60
|
+
export { mixAudioTracks } from './engine/audio-mixer.js';
|
|
61
|
+
export type { AudioTrack, AudioMixConfig, AudioMixResult } from './engine/audio-mixer.js';
|
|
62
|
+
|
|
63
|
+
// Template engine
|
|
64
|
+
export { listTemplates, getTemplate, getTemplateSummaries, getTemplateCategories } from './engine/templates.js';
|
|
65
|
+
export type { VideoTemplate, TemplateCategory, TemplateSlot } from './engine/templates.js';
|
|
66
|
+
export { renderTemplate } from './engine/template-renderer.js';
|
|
67
|
+
export type { RenderTemplateConfig, RenderResult, TemplateAssets } from './engine/template-renderer.js';
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022", "DOM"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|