@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.
Files changed (184) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  3. package/.github/workflows/ci.yml +34 -0
  4. package/CHANGELOG.md +24 -0
  5. package/CONTRIBUTING.md +75 -0
  6. package/LICENSE +21 -0
  7. package/README.md +198 -0
  8. package/USAGE.md +144 -0
  9. package/dist/handlers/capcut.d.ts +6 -0
  10. package/dist/handlers/capcut.js +229 -0
  11. package/dist/handlers/capcut.js.map +1 -0
  12. package/dist/handlers/editing.d.ts +6 -0
  13. package/dist/handlers/editing.js +242 -0
  14. package/dist/handlers/editing.js.map +1 -0
  15. package/dist/handlers/index.d.ts +2 -0
  16. package/dist/handlers/index.js +33 -0
  17. package/dist/handlers/index.js.map +1 -0
  18. package/dist/handlers/post-production.d.ts +5 -0
  19. package/dist/handlers/post-production.js +109 -0
  20. package/dist/handlers/post-production.js.map +1 -0
  21. package/dist/handlers/smart-screenshot.d.ts +5 -0
  22. package/dist/handlers/smart-screenshot.js +83 -0
  23. package/dist/handlers/smart-screenshot.js.map +1 -0
  24. package/dist/handlers/tts.d.ts +5 -0
  25. package/dist/handlers/tts.js +83 -0
  26. package/dist/handlers/tts.js.map +1 -0
  27. package/dist/handlers/video.d.ts +5 -0
  28. package/dist/handlers/video.js +127 -0
  29. package/dist/handlers/video.js.map +1 -0
  30. package/dist/lib/dual-transport.d.ts +42 -0
  31. package/dist/lib/dual-transport.js +208 -0
  32. package/dist/lib/dual-transport.js.map +1 -0
  33. package/dist/lib/logger.d.ts +12 -0
  34. package/dist/lib/logger.js +42 -0
  35. package/dist/lib/logger.js.map +1 -0
  36. package/dist/lib/types.d.ts +16 -0
  37. package/dist/lib/types.js +15 -0
  38. package/dist/lib/types.js.map +1 -0
  39. package/dist/schemas/capcut.d.ts +608 -0
  40. package/dist/schemas/capcut.js +411 -0
  41. package/dist/schemas/capcut.js.map +1 -0
  42. package/dist/schemas/editing.d.ts +822 -0
  43. package/dist/schemas/editing.js +466 -0
  44. package/dist/schemas/editing.js.map +1 -0
  45. package/dist/schemas/index.d.ts +2366 -0
  46. package/dist/schemas/index.js +15 -0
  47. package/dist/schemas/index.js.map +1 -0
  48. package/dist/schemas/post-production.d.ts +379 -0
  49. package/dist/schemas/post-production.js +268 -0
  50. package/dist/schemas/post-production.js.map +1 -0
  51. package/dist/schemas/smart-screenshot.d.ts +127 -0
  52. package/dist/schemas/smart-screenshot.js +122 -0
  53. package/dist/schemas/smart-screenshot.js.map +1 -0
  54. package/dist/schemas/tts.d.ts +220 -0
  55. package/dist/schemas/tts.js +194 -0
  56. package/dist/schemas/tts.js.map +1 -0
  57. package/dist/schemas/video.d.ts +236 -0
  58. package/dist/schemas/video.js +210 -0
  59. package/dist/schemas/video.js.map +1 -0
  60. package/dist/server.d.ts +11 -0
  61. package/dist/server.js +239 -0
  62. package/dist/server.js.map +1 -0
  63. package/dist/server.test.d.ts +1 -0
  64. package/dist/server.test.js +87 -0
  65. package/dist/server.test.js.map +1 -0
  66. package/dist/tools/engine/audio-mixer.d.ts +40 -0
  67. package/dist/tools/engine/audio-mixer.js +169 -0
  68. package/dist/tools/engine/audio-mixer.js.map +1 -0
  69. package/dist/tools/engine/audio.d.ts +22 -0
  70. package/dist/tools/engine/audio.js +73 -0
  71. package/dist/tools/engine/audio.js.map +1 -0
  72. package/dist/tools/engine/beat-sync.d.ts +31 -0
  73. package/dist/tools/engine/beat-sync.js +270 -0
  74. package/dist/tools/engine/beat-sync.js.map +1 -0
  75. package/dist/tools/engine/capture.d.ts +12 -0
  76. package/dist/tools/engine/capture.js +290 -0
  77. package/dist/tools/engine/capture.js.map +1 -0
  78. package/dist/tools/engine/chroma-key.d.ts +27 -0
  79. package/dist/tools/engine/chroma-key.js +154 -0
  80. package/dist/tools/engine/chroma-key.js.map +1 -0
  81. package/dist/tools/engine/concat.d.ts +49 -0
  82. package/dist/tools/engine/concat.js +149 -0
  83. package/dist/tools/engine/concat.js.map +1 -0
  84. package/dist/tools/engine/cursor.d.ts +26 -0
  85. package/dist/tools/engine/cursor.js +185 -0
  86. package/dist/tools/engine/cursor.js.map +1 -0
  87. package/dist/tools/engine/easing.d.ts +15 -0
  88. package/dist/tools/engine/easing.js +100 -0
  89. package/dist/tools/engine/easing.js.map +1 -0
  90. package/dist/tools/engine/editing.d.ts +158 -0
  91. package/dist/tools/engine/editing.js +541 -0
  92. package/dist/tools/engine/editing.js.map +1 -0
  93. package/dist/tools/engine/encoder.d.ts +31 -0
  94. package/dist/tools/engine/encoder.js +154 -0
  95. package/dist/tools/engine/encoder.js.map +1 -0
  96. package/dist/tools/engine/index.d.ts +30 -0
  97. package/dist/tools/engine/index.js +23 -0
  98. package/dist/tools/engine/index.js.map +1 -0
  99. package/dist/tools/engine/lut-presets.d.ts +25 -0
  100. package/dist/tools/engine/lut-presets.js +141 -0
  101. package/dist/tools/engine/lut-presets.js.map +1 -0
  102. package/dist/tools/engine/narrated-video.d.ts +63 -0
  103. package/dist/tools/engine/narrated-video.js +163 -0
  104. package/dist/tools/engine/narrated-video.js.map +1 -0
  105. package/dist/tools/engine/scenes.d.ts +17 -0
  106. package/dist/tools/engine/scenes.js +223 -0
  107. package/dist/tools/engine/scenes.js.map +1 -0
  108. package/dist/tools/engine/smart-screenshot.d.ts +80 -0
  109. package/dist/tools/engine/smart-screenshot.js +744 -0
  110. package/dist/tools/engine/smart-screenshot.js.map +1 -0
  111. package/dist/tools/engine/social-format.d.ts +66 -0
  112. package/dist/tools/engine/social-format.js +107 -0
  113. package/dist/tools/engine/social-format.js.map +1 -0
  114. package/dist/tools/engine/template-renderer.d.ts +45 -0
  115. package/dist/tools/engine/template-renderer.js +233 -0
  116. package/dist/tools/engine/template-renderer.js.map +1 -0
  117. package/dist/tools/engine/templates.d.ts +87 -0
  118. package/dist/tools/engine/templates.js +272 -0
  119. package/dist/tools/engine/templates.js.map +1 -0
  120. package/dist/tools/engine/text-animations.d.ts +33 -0
  121. package/dist/tools/engine/text-animations.js +192 -0
  122. package/dist/tools/engine/text-animations.js.map +1 -0
  123. package/dist/tools/engine/text-overlay.d.ts +27 -0
  124. package/dist/tools/engine/text-overlay.js +84 -0
  125. package/dist/tools/engine/text-overlay.js.map +1 -0
  126. package/dist/tools/engine/tts.d.ts +54 -0
  127. package/dist/tools/engine/tts.js +186 -0
  128. package/dist/tools/engine/tts.js.map +1 -0
  129. package/dist/tools/engine/types.d.ts +166 -0
  130. package/dist/tools/engine/types.js +13 -0
  131. package/dist/tools/engine/types.js.map +1 -0
  132. package/dist/tools/engine/voice-effects.d.ts +18 -0
  133. package/dist/tools/engine/voice-effects.js +215 -0
  134. package/dist/tools/engine/voice-effects.js.map +1 -0
  135. package/dist/tools/index.d.ts +32 -0
  136. package/dist/tools/index.js +23 -0
  137. package/dist/tools/index.js.map +1 -0
  138. package/package.json +56 -0
  139. package/scripts/check-deps.js +39 -0
  140. package/src/handlers/capcut.ts +245 -0
  141. package/src/handlers/editing.ts +260 -0
  142. package/src/handlers/index.ts +34 -0
  143. package/src/handlers/post-production.ts +136 -0
  144. package/src/handlers/smart-screenshot.ts +86 -0
  145. package/src/handlers/tts.ts +103 -0
  146. package/src/handlers/video.ts +137 -0
  147. package/src/lib/dual-transport.ts +272 -0
  148. package/src/lib/logger.ts +59 -0
  149. package/src/lib/types.ts +25 -0
  150. package/src/schemas/capcut.ts +418 -0
  151. package/src/schemas/editing.ts +476 -0
  152. package/src/schemas/index.ts +15 -0
  153. package/src/schemas/post-production.ts +273 -0
  154. package/src/schemas/smart-screenshot.ts +122 -0
  155. package/src/schemas/tts.ts +197 -0
  156. package/src/schemas/video.ts +211 -0
  157. package/src/server.test.ts +99 -0
  158. package/src/server.ts +289 -0
  159. package/src/tools/engine/audio-mixer.ts +244 -0
  160. package/src/tools/engine/audio.ts +115 -0
  161. package/src/tools/engine/beat-sync.ts +356 -0
  162. package/src/tools/engine/capture.ts +360 -0
  163. package/src/tools/engine/chroma-key.ts +202 -0
  164. package/src/tools/engine/concat.ts +242 -0
  165. package/src/tools/engine/cursor.ts +222 -0
  166. package/src/tools/engine/easing.ts +120 -0
  167. package/src/tools/engine/editing.ts +809 -0
  168. package/src/tools/engine/encoder.ts +208 -0
  169. package/src/tools/engine/index.ts +33 -0
  170. package/src/tools/engine/lut-presets.ts +235 -0
  171. package/src/tools/engine/narrated-video.ts +267 -0
  172. package/src/tools/engine/scenes.ts +309 -0
  173. package/src/tools/engine/smart-screenshot.ts +923 -0
  174. package/src/tools/engine/social-format.ts +146 -0
  175. package/src/tools/engine/template-renderer.ts +294 -0
  176. package/src/tools/engine/templates.ts +370 -0
  177. package/src/tools/engine/text-animations.ts +282 -0
  178. package/src/tools/engine/text-overlay.ts +143 -0
  179. package/src/tools/engine/tts.ts +284 -0
  180. package/src/tools/engine/types.ts +191 -0
  181. package/src/tools/engine/voice-effects.ts +258 -0
  182. package/src/tools/index.ts +67 -0
  183. package/tsconfig.json +19 -0
  184. package/vitest.config.ts +7 -0
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Audio engine — background music, fade, loop, volume control
3
+ * All processing via ffmpeg + ffprobe (no npm dependencies)
4
+ */
5
+ import { execFile } from 'child_process';
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import { logger } from '../../lib/logger.js';
9
+ // ─── ffprobe helper ─────────────────────────────────────────────────
10
+ export function getMediaDuration(filePath) {
11
+ return new Promise((resolve, reject) => {
12
+ execFile('ffprobe', ['-v', 'quiet', '-show_entries', 'format=duration', '-of', 'csv=p=0', filePath], (error, stdout) => {
13
+ if (error)
14
+ reject(new Error(`ffprobe failed: ${error.message}`));
15
+ else
16
+ resolve(parseFloat(stdout.trim()) || 0);
17
+ });
18
+ });
19
+ }
20
+ export async function addBackgroundMusic(config) {
21
+ const { videoPath, musicPath, outputPath, musicVolume = 0.25, fadeInDuration = 2, fadeOutDuration = 3, loopMusic = true, } = config;
22
+ if (!fs.existsSync(videoPath))
23
+ throw new Error(`Video not found: ${videoPath}`);
24
+ if (!fs.existsSync(musicPath))
25
+ throw new Error(`Music not found: ${musicPath}`);
26
+ const videoDuration = await getMediaDuration(videoPath);
27
+ const fadeOutStart = Math.max(0, videoDuration - fadeOutDuration);
28
+ logger.info(`Adding music to video (${videoDuration.toFixed(1)}s, volume: ${musicVolume}, fade: ${fadeInDuration}s/${fadeOutDuration}s)`);
29
+ // Ensure output directory exists
30
+ const outDir = path.dirname(outputPath);
31
+ if (!fs.existsSync(outDir))
32
+ fs.mkdirSync(outDir, { recursive: true });
33
+ // Build audio filter chain
34
+ const musicFilter = [
35
+ `afade=t=in:st=0:d=${fadeInDuration}`,
36
+ `afade=t=out:st=${fadeOutStart}:d=${fadeOutDuration}`,
37
+ `volume=${musicVolume}`,
38
+ ].join(',');
39
+ const args = ['-y'];
40
+ // Video input
41
+ args.push('-i', videoPath);
42
+ // Music input (with optional loop)
43
+ if (loopMusic)
44
+ args.push('-stream_loop', '-1');
45
+ args.push('-i', musicPath);
46
+ // Filter: process music, map to output
47
+ args.push('-filter_complex', `[1:a]${musicFilter}[music]`);
48
+ args.push('-map', '0:v', '-map', '[music]');
49
+ // Encoding
50
+ args.push('-c:v', 'copy'); // Don't re-encode video
51
+ args.push('-c:a', 'aac', '-b:a', '192k');
52
+ args.push('-shortest'); // End when video ends
53
+ args.push('-movflags', '+faststart');
54
+ args.push(outputPath);
55
+ await runFfmpeg(args);
56
+ const stats = fs.statSync(outputPath);
57
+ logger.info(`Music added: ${outputPath} (${(stats.size / 1024 / 1024).toFixed(2)} MB)`);
58
+ return outputPath;
59
+ }
60
+ // ─── ffmpeg runner ──────────────────────────────────────────────────
61
+ function runFfmpeg(args) {
62
+ return new Promise((resolve, reject) => {
63
+ execFile('ffmpeg', args, { maxBuffer: 50 * 1024 * 1024 }, (error, stdout, stderr) => {
64
+ if (error) {
65
+ logger.error(`ffmpeg failed: ${stderr}`);
66
+ reject(new Error(`ffmpeg failed: ${stderr || error.message}`));
67
+ return;
68
+ }
69
+ resolve(stdout);
70
+ });
71
+ });
72
+ }
73
+ //# sourceMappingURL=audio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio.js","sourceRoot":"","sources":["../../../src/tools/engine/audio.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Beat-Sync Engine — Automatic beat detection + video clip cutting to music beats.
3
+ *
4
+ * Uses FFmpeg's `astats` filter for RMS energy analysis to detect beats/onsets.
5
+ * No external dependencies (no Meyda, no Python) — pure FFmpeg.
6
+ *
7
+ * Flow: Analyze audio → find beat positions → cut clips to beats → concatenate
8
+ */
9
+ export interface BeatSyncConfig {
10
+ /** Audio/music file to analyze for beats */
11
+ audioPath: string;
12
+ /** Video clips to cut and sync to beats (will be used in order, cycling if needed) */
13
+ clips: string[];
14
+ outputPath: string;
15
+ /** Minimum time between beats in seconds (filters out false positives). Default: 0.3 */
16
+ minBeatInterval?: number;
17
+ /** Maximum number of beats to use (limits output length). Default: 50 */
18
+ maxBeats?: number;
19
+ /** Effect to apply on beat transitions: 'cut' (hard cut), 'flash' (white flash), 'zoom' (quick zoom pulse). Default: 'cut' */
20
+ beatEffect?: 'cut' | 'flash' | 'zoom';
21
+ /** Energy threshold for beat detection: 0.0-1.0 (higher = fewer beats detected). Default: 0.6 */
22
+ sensitivity?: number;
23
+ }
24
+ export interface BeatSyncResult {
25
+ outputPath: string;
26
+ beatsDetected: number;
27
+ beatsUsed: number;
28
+ beatPositions: number[];
29
+ duration: number;
30
+ }
31
+ export declare function syncToBeats(config: BeatSyncConfig): Promise<BeatSyncResult>;
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Beat-Sync Engine — Automatic beat detection + video clip cutting to music beats.
3
+ *
4
+ * Uses FFmpeg's `astats` filter for RMS energy analysis to detect beats/onsets.
5
+ * No external dependencies (no Meyda, no Python) — pure FFmpeg.
6
+ *
7
+ * Flow: Analyze audio → find beat positions → cut clips to beats → concatenate
8
+ */
9
+ import { execFile } from 'child_process';
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { logger } from '../../lib/logger.js';
13
+ // ─── Helpers ────────────────────────────────────────────────────────
14
+ function runFfmpeg(args, timeoutMs = 600_000) {
15
+ return new Promise((resolve, reject) => {
16
+ execFile('ffmpeg', args, { maxBuffer: 100 * 1024 * 1024, timeout: timeoutMs }, (error, stdout, stderr) => {
17
+ if (error) {
18
+ logger.error(`ffmpeg failed: ${stderr}`);
19
+ reject(new Error(`ffmpeg failed: ${stderr || error.message}`));
20
+ return;
21
+ }
22
+ resolve(stderr); // ffmpeg outputs filter info to stderr
23
+ });
24
+ });
25
+ }
26
+ function runFfmpegStdout(args, timeoutMs = 300_000) {
27
+ return new Promise((resolve, reject) => {
28
+ execFile('ffmpeg', args, { maxBuffer: 100 * 1024 * 1024, timeout: timeoutMs }, (error, stdout, stderr) => {
29
+ if (error) {
30
+ logger.error(`ffmpeg failed: ${stderr}`);
31
+ reject(new Error(`ffmpeg failed: ${stderr || error.message}`));
32
+ return;
33
+ }
34
+ resolve(stdout);
35
+ });
36
+ });
37
+ }
38
+ function ensureDir(filePath) {
39
+ const dir = path.dirname(filePath);
40
+ if (!fs.existsSync(dir))
41
+ fs.mkdirSync(dir, { recursive: true });
42
+ }
43
+ function assertExists(filePath, label = 'File') {
44
+ if (!fs.existsSync(filePath))
45
+ throw new Error(`${label} not found: ${filePath}`);
46
+ }
47
+ function fileInfo(filePath) {
48
+ const stats = fs.statSync(filePath);
49
+ return `${(stats.size / 1024 / 1024).toFixed(2)} MB`;
50
+ }
51
+ function getMediaDuration(filePath) {
52
+ return new Promise((resolve, reject) => {
53
+ execFile('ffprobe', ['-v', 'quiet', '-show_entries', 'format=duration', '-of', 'csv=p=0', filePath], (error, stdout) => {
54
+ if (error) {
55
+ reject(new Error(`ffprobe failed: ${error.message}`));
56
+ return;
57
+ }
58
+ const dur = parseFloat(stdout.trim());
59
+ resolve(isNaN(dur) ? 0 : dur);
60
+ });
61
+ });
62
+ }
63
+ // ─── Beat Detection ─────────────────────────────────────────────────
64
+ /**
65
+ * Detect beats using FFmpeg's audio energy analysis.
66
+ *
67
+ * Strategy: Extract RMS energy per short window, find peaks above threshold.
68
+ * Uses `volumedetect` combined with frame-level energy via `astats`.
69
+ */
70
+ async function detectBeats(audioPath, minInterval, sensitivity, maxBeats) {
71
+ logger.info('Analyzing audio for beat detection...');
72
+ // Step 1: Get audio duration
73
+ const duration = await getMediaDuration(audioPath);
74
+ if (duration <= 0)
75
+ throw new Error('Audio file has no duration');
76
+ // Step 2: Extract per-frame RMS energy using astats
77
+ // Output format: one line per analysis window with RMS_level
78
+ const windowSize = 0.05; // 50ms analysis windows (20 frames/sec)
79
+ const tempFile = `/tmp/beat-analysis-${Date.now()}.txt`;
80
+ try {
81
+ // Use ebur128 for momentary loudness — outputs per-frame data to stderr
82
+ const stderr = await runFfmpeg([
83
+ '-i', audioPath,
84
+ '-af', `astats=metadata=1:reset=${Math.round(1 / windowSize)},ametadata=print:key=lavfi.astats.Overall.RMS_level:file=${tempFile}`,
85
+ '-f', 'null', '-',
86
+ ]);
87
+ // Parse the energy data
88
+ if (!fs.existsSync(tempFile)) {
89
+ // Fallback: use simpler approach with volumedetect
90
+ return detectBeatsFallback(audioPath, duration, minInterval, sensitivity, maxBeats);
91
+ }
92
+ const rawData = fs.readFileSync(tempFile, 'utf-8');
93
+ const lines = rawData.split('\n');
94
+ const energyPoints = [];
95
+ let currentTime = -1;
96
+ for (const line of lines) {
97
+ const trimmed = line.trim();
98
+ if (trimmed.startsWith('frame:')) {
99
+ // Extract pts_time
100
+ const timeMatch = trimmed.match(/pts_time:([\d.]+)/);
101
+ if (timeMatch) {
102
+ currentTime = parseFloat(timeMatch[1]);
103
+ }
104
+ }
105
+ else if (trimmed.startsWith('lavfi.astats.Overall.RMS_level=')) {
106
+ const val = parseFloat(trimmed.split('=')[1]);
107
+ if (currentTime >= 0 && !isNaN(val) && val > -100) {
108
+ // Convert from dB to linear energy (0-1 scale)
109
+ const linearEnergy = Math.pow(10, val / 20);
110
+ energyPoints.push({ time: currentTime, energy: linearEnergy });
111
+ }
112
+ }
113
+ }
114
+ // Cleanup temp file
115
+ try {
116
+ fs.unlinkSync(tempFile);
117
+ }
118
+ catch { /* ignore */ }
119
+ if (energyPoints.length < 10) {
120
+ return detectBeatsFallback(audioPath, duration, minInterval, sensitivity, maxBeats);
121
+ }
122
+ // Step 3: Find peaks — energy values that are local maxima and above threshold
123
+ const beats = findPeaks(energyPoints, minInterval, sensitivity, maxBeats);
124
+ logger.info(`Beat detection: ${energyPoints.length} energy frames → ${beats.length} beats`);
125
+ return beats;
126
+ }
127
+ catch {
128
+ // Cleanup temp file on error
129
+ try {
130
+ fs.unlinkSync(tempFile);
131
+ }
132
+ catch { /* ignore */ }
133
+ return detectBeatsFallback(audioPath, duration, minInterval, sensitivity, maxBeats);
134
+ }
135
+ }
136
+ /** Find peaks in energy data that represent beats */
137
+ function findPeaks(energyPoints, minInterval, sensitivity, maxBeats) {
138
+ if (energyPoints.length === 0)
139
+ return [];
140
+ // Calculate dynamic threshold based on energy distribution
141
+ const energies = energyPoints.map(p => p.energy).sort((a, b) => a - b);
142
+ const median = energies[Math.floor(energies.length / 2)];
143
+ const max = energies[energies.length - 1];
144
+ // Threshold: blend between median and max based on sensitivity
145
+ // Higher sensitivity → lower threshold → more beats
146
+ const threshold = median + (max - median) * (1 - sensitivity);
147
+ const beats = [];
148
+ let lastBeatTime = -minInterval;
149
+ for (let i = 1; i < energyPoints.length - 1; i++) {
150
+ const prev = energyPoints[i - 1].energy;
151
+ const curr = energyPoints[i].energy;
152
+ const next = energyPoints[i + 1].energy;
153
+ const time = energyPoints[i].time;
154
+ // Is this a local maximum above threshold?
155
+ if (curr > prev && curr >= next && curr > threshold) {
156
+ // Respect minimum interval
157
+ if (time - lastBeatTime >= minInterval) {
158
+ beats.push(Math.round(time * 1000) / 1000); // Round to ms
159
+ lastBeatTime = time;
160
+ if (beats.length >= maxBeats)
161
+ break;
162
+ }
163
+ }
164
+ }
165
+ return beats;
166
+ }
167
+ /** Fallback beat detection: evenly spaced based on estimated BPM */
168
+ async function detectBeatsFallback(audioPath, duration, minInterval, _sensitivity, maxBeats) {
169
+ logger.info('Using fallback beat detection (evenly spaced)');
170
+ // Default to ~120 BPM (0.5s interval) if we can't detect
171
+ const interval = Math.max(minInterval, 0.5);
172
+ const beats = [];
173
+ for (let t = interval; t < duration && beats.length < maxBeats; t += interval) {
174
+ beats.push(Math.round(t * 1000) / 1000);
175
+ }
176
+ return beats;
177
+ }
178
+ // ─── Main Function ──────────────────────────────────────────────────
179
+ export async function syncToBeats(config) {
180
+ const { audioPath, clips, outputPath, minBeatInterval = 0.3, maxBeats = 50, beatEffect = 'cut', sensitivity = 0.6, } = config;
181
+ assertExists(audioPath, 'Audio/music file');
182
+ if (clips.length === 0)
183
+ throw new Error('Need at least 1 video clip');
184
+ for (const clip of clips) {
185
+ assertExists(clip, 'Video clip');
186
+ }
187
+ ensureDir(outputPath);
188
+ // Step 1: Detect beats
189
+ const beatPositions = await detectBeats(audioPath, minBeatInterval, sensitivity, maxBeats);
190
+ if (beatPositions.length < 2) {
191
+ throw new Error('Could not detect enough beats in the audio. Try lowering sensitivity.');
192
+ }
193
+ logger.info(`Detected ${beatPositions.length} beats. Creating beat-synced video...`);
194
+ // Step 2: Create segment list — each beat transition = new clip segment
195
+ const tempDir = `/tmp/beatsync-${Date.now()}`;
196
+ fs.mkdirSync(tempDir, { recursive: true });
197
+ const segmentPaths = [];
198
+ const concatList = [];
199
+ try {
200
+ for (let i = 0; i < beatPositions.length - 1; i++) {
201
+ const segDuration = beatPositions[i + 1] - beatPositions[i];
202
+ const clipIdx = i % clips.length;
203
+ const clipPath = clips[clipIdx];
204
+ // Get clip duration to pick a random start point
205
+ const clipDur = await getMediaDuration(clipPath);
206
+ const maxStart = Math.max(0, clipDur - segDuration);
207
+ const startOffset = maxStart > 0 ? Math.random() * maxStart : 0;
208
+ const segPath = path.join(tempDir, `seg-${String(i).padStart(4, '0')}.mp4`);
209
+ // Extract segment from clip
210
+ const segArgs = [
211
+ '-y', '-ss', startOffset.toFixed(3),
212
+ '-i', clipPath,
213
+ '-t', segDuration.toFixed(3),
214
+ '-c:v', 'libx264', '-crf', '20', '-preset', 'fast',
215
+ '-pix_fmt', 'yuv420p', '-an',
216
+ ];
217
+ // Apply beat effect
218
+ if (beatEffect === 'flash') {
219
+ // White flash at start of each segment (0.05s)
220
+ segArgs.push('-vf', `fade=in:st=0:d=0.05:color=white`);
221
+ }
222
+ else if (beatEffect === 'zoom') {
223
+ // Quick zoom pulse at start (1.05x → 1.0x over 0.15s)
224
+ segArgs.push('-vf', `zoompan=z='if(lt(on,5),1.05-0.01*on,1)':d=1:s=1920x1080:fps=30`);
225
+ }
226
+ segArgs.push(segPath);
227
+ await runFfmpegStdout(segArgs);
228
+ segmentPaths.push(segPath);
229
+ concatList.push(`file '${segPath}'`);
230
+ }
231
+ // Step 3: Concatenate all segments
232
+ const concatFile = path.join(tempDir, 'concat.txt');
233
+ fs.writeFileSync(concatFile, concatList.join('\n'));
234
+ const tempOutput = path.join(tempDir, 'video-only.mp4');
235
+ await runFfmpegStdout([
236
+ '-y', '-f', 'concat', '-safe', '0', '-i', concatFile,
237
+ '-c', 'copy', tempOutput,
238
+ ]);
239
+ // Step 4: Merge with original audio
240
+ const audioDuration = await getMediaDuration(audioPath);
241
+ const videoDuration = beatPositions[beatPositions.length - 1];
242
+ await runFfmpegStdout([
243
+ '-y',
244
+ '-i', tempOutput,
245
+ '-i', audioPath,
246
+ '-c:v', 'copy', '-c:a', 'aac', '-b:a', '192k',
247
+ '-t', Math.min(audioDuration, videoDuration).toFixed(3),
248
+ '-movflags', '+faststart',
249
+ '-shortest',
250
+ outputPath,
251
+ ]);
252
+ const finalDuration = Math.min(audioDuration, videoDuration);
253
+ logger.info(`Beat-synced video: ${beatPositions.length - 1} segments, ${finalDuration.toFixed(1)}s → ${outputPath} (${fileInfo(outputPath)})`);
254
+ return {
255
+ outputPath,
256
+ beatsDetected: beatPositions.length,
257
+ beatsUsed: beatPositions.length - 1,
258
+ beatPositions,
259
+ duration: finalDuration,
260
+ };
261
+ }
262
+ finally {
263
+ // Cleanup temp files
264
+ try {
265
+ fs.rmSync(tempDir, { recursive: true });
266
+ }
267
+ catch { /* ignore */ }
268
+ }
269
+ }
270
+ //# sourceMappingURL=beat-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beat-sync.js","sourceRoot":"","sources":["../../../src/tools/engine/beat-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Frame-by-Frame Capture Engine
3
+ * The core of cinema-grade website video recording
4
+ *
5
+ * Pipeline: Playwright → Frame Screenshots → ffmpeg → MP4
6
+ * Result: Perfect 60fps video with zero frame drops
7
+ */
8
+ import type { RecordingConfig, RecordingResult } from './types.js';
9
+ /**
10
+ * Record a website with cinema-quality frame-by-frame capture
11
+ */
12
+ export declare function recordWebsite(config: RecordingConfig): Promise<RecordingResult>;