vidpipe 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 (213) hide show
  1. package/README.md +243 -0
  2. package/assets/fonts/Montserrat-Bold.ttf +0 -0
  3. package/assets/fonts/Montserrat-Regular.ttf +0 -0
  4. package/assets/fonts/OFL.txt +93 -0
  5. package/dist/__tests__/agents.test.d.ts +2 -0
  6. package/dist/__tests__/agents.test.d.ts.map +1 -0
  7. package/dist/__tests__/agents.test.js +434 -0
  8. package/dist/__tests__/agents.test.js.map +1 -0
  9. package/dist/__tests__/aspectRatio.test.d.ts +2 -0
  10. package/dist/__tests__/aspectRatio.test.d.ts.map +1 -0
  11. package/dist/__tests__/aspectRatio.test.js +406 -0
  12. package/dist/__tests__/aspectRatio.test.js.map +1 -0
  13. package/dist/__tests__/captionGenerator.test.d.ts +2 -0
  14. package/dist/__tests__/captionGenerator.test.d.ts.map +1 -0
  15. package/dist/__tests__/captionGenerator.test.js +435 -0
  16. package/dist/__tests__/captionGenerator.test.js.map +1 -0
  17. package/dist/__tests__/config.test.d.ts +2 -0
  18. package/dist/__tests__/config.test.d.ts.map +1 -0
  19. package/dist/__tests__/config.test.js +81 -0
  20. package/dist/__tests__/config.test.js.map +1 -0
  21. package/dist/__tests__/faceDetection.test.d.ts +2 -0
  22. package/dist/__tests__/faceDetection.test.d.ts.map +1 -0
  23. package/dist/__tests__/faceDetection.test.js +372 -0
  24. package/dist/__tests__/faceDetection.test.js.map +1 -0
  25. package/dist/__tests__/ffmpegTools.test.d.ts +2 -0
  26. package/dist/__tests__/ffmpegTools.test.d.ts.map +1 -0
  27. package/dist/__tests__/ffmpegTools.test.js +464 -0
  28. package/dist/__tests__/ffmpegTools.test.js.map +1 -0
  29. package/dist/__tests__/integration/captionBurn.test.d.ts +2 -0
  30. package/dist/__tests__/integration/captionBurn.test.d.ts.map +1 -0
  31. package/dist/__tests__/integration/captionBurn.test.js +103 -0
  32. package/dist/__tests__/integration/captionBurn.test.js.map +1 -0
  33. package/dist/__tests__/integration/clipComposite.test.d.ts +2 -0
  34. package/dist/__tests__/integration/clipComposite.test.d.ts.map +1 -0
  35. package/dist/__tests__/integration/clipComposite.test.js +56 -0
  36. package/dist/__tests__/integration/clipComposite.test.js.map +1 -0
  37. package/dist/__tests__/integration/faceDetection.test.d.ts +2 -0
  38. package/dist/__tests__/integration/faceDetection.test.d.ts.map +1 -0
  39. package/dist/__tests__/integration/faceDetection.test.js +85 -0
  40. package/dist/__tests__/integration/faceDetection.test.js.map +1 -0
  41. package/dist/__tests__/integration/ffmpegPipeline.test.d.ts +2 -0
  42. package/dist/__tests__/integration/ffmpegPipeline.test.d.ts.map +1 -0
  43. package/dist/__tests__/integration/ffmpegPipeline.test.js +88 -0
  44. package/dist/__tests__/integration/ffmpegPipeline.test.js.map +1 -0
  45. package/dist/__tests__/integration/fixture.d.ts +19 -0
  46. package/dist/__tests__/integration/fixture.d.ts.map +1 -0
  47. package/dist/__tests__/integration/fixture.js +112 -0
  48. package/dist/__tests__/integration/fixture.js.map +1 -0
  49. package/dist/__tests__/integration/fixture.test.d.ts +2 -0
  50. package/dist/__tests__/integration/fixture.test.d.ts.map +1 -0
  51. package/dist/__tests__/integration/fixture.test.js +27 -0
  52. package/dist/__tests__/integration/fixture.test.js.map +1 -0
  53. package/dist/__tests__/integration/realCaptions.test.d.ts +2 -0
  54. package/dist/__tests__/integration/realCaptions.test.d.ts.map +1 -0
  55. package/dist/__tests__/integration/realCaptions.test.js +226 -0
  56. package/dist/__tests__/integration/realCaptions.test.js.map +1 -0
  57. package/dist/__tests__/integration/realPipeline.test.d.ts +2 -0
  58. package/dist/__tests__/integration/realPipeline.test.d.ts.map +1 -0
  59. package/dist/__tests__/integration/realPipeline.test.js +210 -0
  60. package/dist/__tests__/integration/realPipeline.test.js.map +1 -0
  61. package/dist/__tests__/integration/silenceRemoval.test.d.ts +2 -0
  62. package/dist/__tests__/integration/silenceRemoval.test.d.ts.map +1 -0
  63. package/dist/__tests__/integration/silenceRemoval.test.js +93 -0
  64. package/dist/__tests__/integration/silenceRemoval.test.js.map +1 -0
  65. package/dist/__tests__/pipeline.test.d.ts +2 -0
  66. package/dist/__tests__/pipeline.test.d.ts.map +1 -0
  67. package/dist/__tests__/pipeline.test.js +434 -0
  68. package/dist/__tests__/pipeline.test.js.map +1 -0
  69. package/dist/__tests__/services.test.d.ts +2 -0
  70. package/dist/__tests__/services.test.d.ts.map +1 -0
  71. package/dist/__tests__/services.test.js +655 -0
  72. package/dist/__tests__/services.test.js.map +1 -0
  73. package/dist/__tests__/silenceRemoval.test.d.ts +2 -0
  74. package/dist/__tests__/silenceRemoval.test.d.ts.map +1 -0
  75. package/dist/__tests__/silenceRemoval.test.js +266 -0
  76. package/dist/__tests__/silenceRemoval.test.js.map +1 -0
  77. package/dist/__tests__/singlePassEdit.test.d.ts +2 -0
  78. package/dist/__tests__/singlePassEdit.test.d.ts.map +1 -0
  79. package/dist/__tests__/singlePassEdit.test.js +321 -0
  80. package/dist/__tests__/singlePassEdit.test.js.map +1 -0
  81. package/dist/__tests__/smoke.test.d.ts +2 -0
  82. package/dist/__tests__/smoke.test.d.ts.map +1 -0
  83. package/dist/__tests__/smoke.test.js +8 -0
  84. package/dist/__tests__/smoke.test.js.map +1 -0
  85. package/dist/__tests__/utilities.test.d.ts +2 -0
  86. package/dist/__tests__/utilities.test.d.ts.map +1 -0
  87. package/dist/__tests__/utilities.test.js +268 -0
  88. package/dist/__tests__/utilities.test.js.map +1 -0
  89. package/dist/agents/BaseAgent.d.ts +52 -0
  90. package/dist/agents/BaseAgent.d.ts.map +1 -0
  91. package/dist/agents/BaseAgent.js +108 -0
  92. package/dist/agents/BaseAgent.js.map +1 -0
  93. package/dist/agents/BlogAgent.d.ts +3 -0
  94. package/dist/agents/BlogAgent.d.ts.map +1 -0
  95. package/dist/agents/BlogAgent.js +163 -0
  96. package/dist/agents/BlogAgent.js.map +1 -0
  97. package/dist/agents/ChapterAgent.d.ts +11 -0
  98. package/dist/agents/ChapterAgent.d.ts.map +1 -0
  99. package/dist/agents/ChapterAgent.js +191 -0
  100. package/dist/agents/ChapterAgent.js.map +1 -0
  101. package/dist/agents/MediumVideoAgent.d.ts +3 -0
  102. package/dist/agents/MediumVideoAgent.d.ts.map +1 -0
  103. package/dist/agents/MediumVideoAgent.js +219 -0
  104. package/dist/agents/MediumVideoAgent.js.map +1 -0
  105. package/dist/agents/ShortsAgent.d.ts +3 -0
  106. package/dist/agents/ShortsAgent.d.ts.map +1 -0
  107. package/dist/agents/ShortsAgent.js +243 -0
  108. package/dist/agents/ShortsAgent.js.map +1 -0
  109. package/dist/agents/SilenceRemovalAgent.d.ts +9 -0
  110. package/dist/agents/SilenceRemovalAgent.d.ts.map +1 -0
  111. package/dist/agents/SilenceRemovalAgent.js +208 -0
  112. package/dist/agents/SilenceRemovalAgent.js.map +1 -0
  113. package/dist/agents/SocialMediaAgent.d.ts +4 -0
  114. package/dist/agents/SocialMediaAgent.d.ts.map +1 -0
  115. package/dist/agents/SocialMediaAgent.js +248 -0
  116. package/dist/agents/SocialMediaAgent.js.map +1 -0
  117. package/dist/agents/SummaryAgent.d.ts +11 -0
  118. package/dist/agents/SummaryAgent.d.ts.map +1 -0
  119. package/dist/agents/SummaryAgent.js +333 -0
  120. package/dist/agents/SummaryAgent.js.map +1 -0
  121. package/dist/config/brand.d.ts +29 -0
  122. package/dist/config/brand.d.ts.map +1 -0
  123. package/dist/config/brand.js +83 -0
  124. package/dist/config/brand.js.map +1 -0
  125. package/dist/config/environment.d.ts +36 -0
  126. package/dist/config/environment.d.ts.map +1 -0
  127. package/dist/config/environment.js +44 -0
  128. package/dist/config/environment.js.map +1 -0
  129. package/dist/config/logger.d.ts +5 -0
  130. package/dist/config/logger.d.ts.map +1 -0
  131. package/dist/config/logger.js +13 -0
  132. package/dist/config/logger.js.map +1 -0
  133. package/dist/index.d.ts +2 -0
  134. package/dist/index.d.ts.map +1 -0
  135. package/dist/index.js +135 -0
  136. package/dist/index.js.map +1 -0
  137. package/dist/pipeline.d.ts +57 -0
  138. package/dist/pipeline.d.ts.map +1 -0
  139. package/dist/pipeline.js +287 -0
  140. package/dist/pipeline.js.map +1 -0
  141. package/dist/services/captionGeneration.d.ts +7 -0
  142. package/dist/services/captionGeneration.d.ts.map +1 -0
  143. package/dist/services/captionGeneration.js +29 -0
  144. package/dist/services/captionGeneration.js.map +1 -0
  145. package/dist/services/fileWatcher.d.ts +19 -0
  146. package/dist/services/fileWatcher.d.ts.map +1 -0
  147. package/dist/services/fileWatcher.js +120 -0
  148. package/dist/services/fileWatcher.js.map +1 -0
  149. package/dist/services/gitOperations.d.ts +3 -0
  150. package/dist/services/gitOperations.d.ts.map +1 -0
  151. package/dist/services/gitOperations.js +43 -0
  152. package/dist/services/gitOperations.js.map +1 -0
  153. package/dist/services/socialPosting.d.ts +38 -0
  154. package/dist/services/socialPosting.d.ts.map +1 -0
  155. package/dist/services/socialPosting.js +102 -0
  156. package/dist/services/socialPosting.js.map +1 -0
  157. package/dist/services/transcription.d.ts +3 -0
  158. package/dist/services/transcription.d.ts.map +1 -0
  159. package/dist/services/transcription.js +100 -0
  160. package/dist/services/transcription.js.map +1 -0
  161. package/dist/services/videoIngestion.d.ts +3 -0
  162. package/dist/services/videoIngestion.d.ts.map +1 -0
  163. package/dist/services/videoIngestion.js +103 -0
  164. package/dist/services/videoIngestion.js.map +1 -0
  165. package/dist/tools/captions/captionGenerator.d.ts +84 -0
  166. package/dist/tools/captions/captionGenerator.d.ts.map +1 -0
  167. package/dist/tools/captions/captionGenerator.js +390 -0
  168. package/dist/tools/captions/captionGenerator.js.map +1 -0
  169. package/dist/tools/ffmpeg/aspectRatio.d.ts +101 -0
  170. package/dist/tools/ffmpeg/aspectRatio.d.ts.map +1 -0
  171. package/dist/tools/ffmpeg/aspectRatio.js +338 -0
  172. package/dist/tools/ffmpeg/aspectRatio.js.map +1 -0
  173. package/dist/tools/ffmpeg/audioExtraction.d.ts +16 -0
  174. package/dist/tools/ffmpeg/audioExtraction.d.ts.map +1 -0
  175. package/dist/tools/ffmpeg/audioExtraction.js +86 -0
  176. package/dist/tools/ffmpeg/audioExtraction.js.map +1 -0
  177. package/dist/tools/ffmpeg/captionBurning.d.ts +8 -0
  178. package/dist/tools/ffmpeg/captionBurning.d.ts.map +1 -0
  179. package/dist/tools/ffmpeg/captionBurning.js +71 -0
  180. package/dist/tools/ffmpeg/captionBurning.js.map +1 -0
  181. package/dist/tools/ffmpeg/clipExtraction.d.ts +23 -0
  182. package/dist/tools/ffmpeg/clipExtraction.d.ts.map +1 -0
  183. package/dist/tools/ffmpeg/clipExtraction.js +178 -0
  184. package/dist/tools/ffmpeg/clipExtraction.js.map +1 -0
  185. package/dist/tools/ffmpeg/faceDetection.d.ts +127 -0
  186. package/dist/tools/ffmpeg/faceDetection.d.ts.map +1 -0
  187. package/dist/tools/ffmpeg/faceDetection.js +500 -0
  188. package/dist/tools/ffmpeg/faceDetection.js.map +1 -0
  189. package/dist/tools/ffmpeg/frameCapture.d.ts +10 -0
  190. package/dist/tools/ffmpeg/frameCapture.d.ts.map +1 -0
  191. package/dist/tools/ffmpeg/frameCapture.js +48 -0
  192. package/dist/tools/ffmpeg/frameCapture.js.map +1 -0
  193. package/dist/tools/ffmpeg/silenceDetection.d.ts +10 -0
  194. package/dist/tools/ffmpeg/silenceDetection.d.ts.map +1 -0
  195. package/dist/tools/ffmpeg/silenceDetection.js +55 -0
  196. package/dist/tools/ffmpeg/silenceDetection.js.map +1 -0
  197. package/dist/tools/ffmpeg/singlePassEdit.d.ts +25 -0
  198. package/dist/tools/ffmpeg/singlePassEdit.d.ts.map +1 -0
  199. package/dist/tools/ffmpeg/singlePassEdit.js +123 -0
  200. package/dist/tools/ffmpeg/singlePassEdit.js.map +1 -0
  201. package/dist/tools/search/exaClient.d.ts +8 -0
  202. package/dist/tools/search/exaClient.d.ts.map +1 -0
  203. package/dist/tools/search/exaClient.js +38 -0
  204. package/dist/tools/search/exaClient.js.map +1 -0
  205. package/dist/tools/whisper/whisperClient.d.ts +3 -0
  206. package/dist/tools/whisper/whisperClient.d.ts.map +1 -0
  207. package/dist/tools/whisper/whisperClient.js +77 -0
  208. package/dist/tools/whisper/whisperClient.js.map +1 -0
  209. package/dist/types/index.d.ts +305 -0
  210. package/dist/types/index.d.ts.map +1 -0
  211. package/dist/types/index.js +44 -0
  212. package/dist/types/index.js.map +1 -0
  213. package/package.json +63 -0
@@ -0,0 +1,500 @@
1
+ import { execFile } from 'child_process';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import sharp from 'sharp';
6
+ import logger from '../../config/logger';
7
+ const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg';
8
+ const ffprobePath = process.env.FFPROBE_PATH || 'ffprobe';
9
+ // ── Constants ────────────────────────────────────────────────────────────────
10
+ /** Number of frames sampled evenly across the video for analysis. */
11
+ const SAMPLE_FRAMES = 5;
12
+ /** Width to downscale frames to for fast pixel analysis. */
13
+ const ANALYSIS_WIDTH = 320;
14
+ /** Height to downscale frames to for fast pixel analysis. */
15
+ const ANALYSIS_HEIGHT = 180;
16
+ /** Each corner region is 25% of the frame width/height. */
17
+ const CORNER_FRACTION = 0.25;
18
+ /** Minimum skin-tone pixel ratio to consider a corner as a webcam candidate. */
19
+ const MIN_SKIN_RATIO = 0.05;
20
+ /** Minimum confidence score to accept a webcam detection. */
21
+ const MIN_CONFIDENCE = 0.3;
22
+ // ── Refinement constants ─────────────────────────────────────────────────────
23
+ /**
24
+ * Minimum inter-column/row mean difference to accept as a valid overlay edge.
25
+ * The webcam overlay border creates a sharp intensity step between the
26
+ * overlay and the screen content behind it. Values below this threshold
27
+ * are treated as noise or soft gradients.
28
+ */
29
+ const REFINE_MIN_EDGE_DIFF = 3.0;
30
+ /** Webcam must be at least 5% of the frame in each dimension. */
31
+ const REFINE_MIN_SIZE_FRAC = 0.05;
32
+ /** Webcam must be at most 55% of the frame in each dimension. */
33
+ const REFINE_MAX_SIZE_FRAC = 0.55;
34
+ // ── Helpers ──────────────────────────────────────────────────────────────────
35
+ async function getVideoDuration(videoPath) {
36
+ return new Promise((resolve, reject) => {
37
+ execFile(ffprobePath, ['-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', videoPath], (error, stdout) => {
38
+ if (error) {
39
+ reject(new Error(`ffprobe failed: ${error.message}`));
40
+ return;
41
+ }
42
+ resolve(parseFloat(stdout.trim()));
43
+ });
44
+ });
45
+ }
46
+ export async function getVideoResolution(videoPath) {
47
+ return new Promise((resolve, reject) => {
48
+ execFile(ffprobePath, [
49
+ '-v', 'error',
50
+ '-select_streams', 'v:0',
51
+ '-show_entries', 'stream=width,height',
52
+ '-of', 'csv=p=0',
53
+ videoPath,
54
+ ], (error, stdout) => {
55
+ if (error) {
56
+ reject(new Error(`ffprobe failed: ${error.message}`));
57
+ return;
58
+ }
59
+ const [w, h] = stdout.trim().split(',').map(Number);
60
+ resolve({ width: w, height: h });
61
+ });
62
+ });
63
+ }
64
+ async function extractSampleFrames(videoPath, tempDir) {
65
+ const duration = await getVideoDuration(videoPath);
66
+ // Space frames evenly, avoiding very start/end
67
+ const interval = Math.max(1, Math.floor(duration / (SAMPLE_FRAMES + 1)));
68
+ const timestamps = [];
69
+ for (let i = 1; i <= SAMPLE_FRAMES; i++) {
70
+ timestamps.push(i * interval);
71
+ }
72
+ const framePaths = [];
73
+ for (let i = 0; i < timestamps.length; i++) {
74
+ const framePath = path.join(tempDir, `frame_${i}.png`);
75
+ framePaths.push(framePath);
76
+ await new Promise((resolve, reject) => {
77
+ execFile(ffmpegPath, [
78
+ '-y',
79
+ '-ss', timestamps[i].toFixed(2),
80
+ '-i', videoPath,
81
+ '-vf', `scale=${ANALYSIS_WIDTH}:${ANALYSIS_HEIGHT}`,
82
+ '-frames:v', '1',
83
+ '-q:v', '2',
84
+ framePath,
85
+ ], { maxBuffer: 10 * 1024 * 1024 }, (error) => {
86
+ if (error) {
87
+ reject(new Error(`Frame extraction failed at ${timestamps[i]}s: ${error.message}`));
88
+ return;
89
+ }
90
+ resolve();
91
+ });
92
+ });
93
+ }
94
+ return framePaths;
95
+ }
96
+ /**
97
+ * Check if a pixel (in RGB) falls within skin-tone range.
98
+ * Uses simplified HSV heuristic: hue ~0-50°, moderate saturation.
99
+ */
100
+ export function isSkinTone(r, g, b) {
101
+ // Rule-based skin detection in RGB space (avoids HSV conversion overhead)
102
+ // Skin typically: R > 95, G > 40, B > 20, max-min > 15, |R-G| > 15, R > G, R > B
103
+ const max = Math.max(r, g, b);
104
+ const min = Math.min(r, g, b);
105
+ return (r > 95 && g > 40 && b > 20 &&
106
+ (max - min) > 15 &&
107
+ Math.abs(r - g) > 15 &&
108
+ r > g && r > b);
109
+ }
110
+ async function analyzeCorner(framePath, position) {
111
+ const cornerW = Math.floor(ANALYSIS_WIDTH * CORNER_FRACTION);
112
+ const cornerH = Math.floor(ANALYSIS_HEIGHT * CORNER_FRACTION);
113
+ let left;
114
+ let top;
115
+ switch (position) {
116
+ case 'top-left':
117
+ left = 0;
118
+ top = 0;
119
+ break;
120
+ case 'top-right':
121
+ left = ANALYSIS_WIDTH - cornerW;
122
+ top = 0;
123
+ break;
124
+ case 'bottom-left':
125
+ left = 0;
126
+ top = ANALYSIS_HEIGHT - cornerH;
127
+ break;
128
+ case 'bottom-right':
129
+ left = ANALYSIS_WIDTH - cornerW;
130
+ top = ANALYSIS_HEIGHT - cornerH;
131
+ break;
132
+ }
133
+ const { data, info } = await sharp(framePath)
134
+ .extract({ left, top, width: cornerW, height: cornerH })
135
+ .raw()
136
+ .toBuffer({ resolveWithObject: true });
137
+ const totalPixels = info.width * info.height;
138
+ const channels = info.channels;
139
+ let skinCount = 0;
140
+ let sumR = 0, sumG = 0, sumB = 0;
141
+ let sumR2 = 0, sumG2 = 0, sumB2 = 0;
142
+ for (let i = 0; i < data.length; i += channels) {
143
+ const r = data[i];
144
+ const g = data[i + 1];
145
+ const b = data[i + 2];
146
+ if (isSkinTone(r, g, b))
147
+ skinCount++;
148
+ sumR += r;
149
+ sumG += g;
150
+ sumB += b;
151
+ sumR2 += r * r;
152
+ sumG2 += g * g;
153
+ sumB2 += b * b;
154
+ }
155
+ const skinToneRatio = skinCount / totalPixels;
156
+ // Compute variance across all channels as a measure of visual complexity
157
+ const meanR = sumR / totalPixels;
158
+ const meanG = sumG / totalPixels;
159
+ const meanB = sumB / totalPixels;
160
+ const varR = sumR2 / totalPixels - meanR * meanR;
161
+ const varG = sumG2 / totalPixels - meanG * meanG;
162
+ const varB = sumB2 / totalPixels - meanB * meanB;
163
+ const variance = (varR + varG + varB) / 3;
164
+ return {
165
+ position,
166
+ x: left,
167
+ y: top,
168
+ width: cornerW,
169
+ height: cornerH,
170
+ skinToneRatio,
171
+ variance,
172
+ };
173
+ }
174
+ // ── Refinement helpers ───────────────────────────────────────────────────────
175
+ /**
176
+ * Compute per-column mean grayscale intensity over a horizontal band of rows.
177
+ *
178
+ * Used to find the **vertical edge** of the webcam overlay. Each column gets
179
+ * a single mean brightness value averaged over `yFrom..yTo` rows. The
180
+ * resulting 1-D signal has a sharp step at the overlay boundary, which
181
+ * {@link findPeakDiff} locates.
182
+ *
183
+ * @param data - Raw pixel buffer (RGB or RGBA interleaved)
184
+ * @param width - Image width in pixels
185
+ * @param channels - Bytes per pixel (3 for RGB, 4 for RGBA)
186
+ * @param yFrom - First row (inclusive)
187
+ * @param yTo - Last row (exclusive)
188
+ * @returns Float64Array of length `width` with per-column mean grayscale
189
+ */
190
+ function columnMeansForRows(data, width, channels, yFrom, yTo) {
191
+ const means = new Float64Array(width);
192
+ const count = yTo - yFrom;
193
+ if (count <= 0)
194
+ return means;
195
+ for (let x = 0; x < width; x++) {
196
+ let sum = 0;
197
+ for (let y = yFrom; y < yTo; y++) {
198
+ const idx = (y * width + x) * channels;
199
+ sum += (data[idx] + data[idx + 1] + data[idx + 2]) / 3;
200
+ }
201
+ means[x] = sum / count;
202
+ }
203
+ return means;
204
+ }
205
+ /**
206
+ * Compute per-row mean grayscale intensity over a vertical band of columns.
207
+ *
208
+ * Used to find the **horizontal edge** of the webcam overlay. Each row gets
209
+ * a single mean brightness value averaged over `xFrom..xTo` columns. Works
210
+ * the same way as {@link columnMeansForRows} but rotated 90°.
211
+ *
212
+ * @param data - Raw pixel buffer
213
+ * @param width - Image width in pixels
214
+ * @param channels - Bytes per pixel
215
+ * @param height - Image height in pixels
216
+ * @param xFrom - First column (inclusive)
217
+ * @param xTo - Last column (exclusive)
218
+ * @returns Float64Array of length `height` with per-row mean grayscale
219
+ */
220
+ function rowMeansForCols(data, width, channels, height, xFrom, xTo) {
221
+ const means = new Float64Array(height);
222
+ const count = xTo - xFrom;
223
+ if (count <= 0)
224
+ return means;
225
+ for (let y = 0; y < height; y++) {
226
+ let sum = 0;
227
+ for (let x = xFrom; x < xTo; x++) {
228
+ const idx = (y * width + x) * channels;
229
+ sum += (data[idx] + data[idx + 1] + data[idx + 2]) / 3;
230
+ }
231
+ means[y] = sum / count;
232
+ }
233
+ return means;
234
+ }
235
+ /** Element-wise average of Float64Arrays. */
236
+ function averageFloat64Arrays(arrays) {
237
+ if (arrays.length === 0)
238
+ return new Float64Array(0);
239
+ const len = arrays[0].length;
240
+ const result = new Float64Array(len);
241
+ for (const arr of arrays) {
242
+ for (let i = 0; i < len; i++)
243
+ result[i] += arr[i];
244
+ }
245
+ for (let i = 0; i < len; i++)
246
+ result[i] /= arrays.length;
247
+ return result;
248
+ }
249
+ /**
250
+ * Find the position with the largest intensity step between adjacent elements.
251
+ *
252
+ * "Peak difference" = the index where `|means[i+1] - means[i]|` is maximized
253
+ * within the search range. This corresponds to the webcam overlay's edge,
254
+ * because the overlay border creates a hard brightness transition that
255
+ * persists across all frames, while content-based edges average out.
256
+ *
257
+ * @param means - 1-D array of averaged intensities (from column or row means)
258
+ * @param searchFrom - Start of search range (inclusive)
259
+ * @param searchTo - End of search range (inclusive)
260
+ * @param minDiff - Minimum step magnitude to accept (rejects noise)
261
+ * @returns `{index, magnitude}` — index of the edge, or -1 if no edge exceeds minDiff
262
+ */
263
+ export function findPeakDiff(means, searchFrom, searchTo, minDiff) {
264
+ const lo = Math.max(0, Math.min(searchFrom, searchTo));
265
+ const hi = Math.min(means.length - 1, Math.max(searchFrom, searchTo));
266
+ let maxDiff = 0;
267
+ let maxIdx = -1;
268
+ for (let i = lo; i < hi; i++) {
269
+ const d = Math.abs(means[i + 1] - means[i]);
270
+ if (d > maxDiff) {
271
+ maxDiff = d;
272
+ maxIdx = i;
273
+ }
274
+ }
275
+ return maxDiff >= minDiff ? { index: maxIdx, magnitude: maxDiff } : { index: -1, magnitude: maxDiff };
276
+ }
277
+ /**
278
+ * Refine the webcam bounding box by detecting the overlay's spatial edges.
279
+ *
280
+ * ### Why refinement is needed
281
+ * The coarse phase ({@link detectWebcamRegion}'s corner analysis) only identifies
282
+ * which corner contains a webcam — it uses a fixed 25% region and doesn't know
283
+ * the overlay's exact boundaries. Refinement finds pixel-accurate edges.
284
+ *
285
+ * ### Edge detection algorithm
286
+ * 1. For each sample frame, compute **per-column** and **per-row** mean grayscale
287
+ * intensities (restricted to the webcam's half of the frame for stronger signal).
288
+ * 2. **Average across all frames** — the overlay border is spatially fixed and
289
+ * produces a consistent intensity step, while changing video content (slides,
290
+ * code, etc.) averages out to a smooth gradient. This is the key insight that
291
+ * makes the approach work without traditional edge detection filters.
292
+ * 3. Use {@link findPeakDiff} to locate the maximum inter-adjacent intensity
293
+ * difference in the averaged signal — this is the overlay's vertical and
294
+ * horizontal edge.
295
+ * 4. Sanity-check: the resulting rectangle must be 5–55% of the frame in each
296
+ * dimension (webcams are never tiny or most of the screen).
297
+ *
298
+ * @param framePaths - Paths to sample frames at analysis resolution (320×180)
299
+ * @param position - Which corner contains the webcam (from coarse phase)
300
+ * @returns Refined bounding box in analysis-resolution coordinates, or null
301
+ * if no strong edges are found or the result is implausibly sized
302
+ */
303
+ export async function refineBoundingBox(framePaths, position) {
304
+ if (framePaths.length === 0)
305
+ return null;
306
+ const isRight = position.includes('right');
307
+ const isBottom = position.includes('bottom');
308
+ let fw = 0, fh = 0;
309
+ const colMeansAll = [];
310
+ const rowMeansAll = [];
311
+ for (const fp of framePaths) {
312
+ const { data, info } = await sharp(fp).raw().toBuffer({ resolveWithObject: true });
313
+ fw = info.width;
314
+ fh = info.height;
315
+ // Column means: restrict to rows near the webcam for stronger signal
316
+ const yFrom = isBottom ? Math.floor(fh * 0.35) : 0;
317
+ const yTo = isBottom ? fh : Math.ceil(fh * 0.65);
318
+ colMeansAll.push(columnMeansForRows(data, fw, info.channels, yFrom, yTo));
319
+ // Row means: restrict to columns near the webcam
320
+ const xFrom = isRight ? Math.floor(fw * 0.35) : 0;
321
+ const xTo = isRight ? fw : Math.ceil(fw * 0.65);
322
+ rowMeansAll.push(rowMeansForCols(data, fw, info.channels, fh, xFrom, xTo));
323
+ }
324
+ const avgCols = averageFloat64Arrays(colMeansAll);
325
+ const avgRows = averageFloat64Arrays(rowMeansAll);
326
+ // Search for the inner edge in the relevant portion of the frame
327
+ const xFrom = isRight ? Math.floor(fw * 0.35) : Math.floor(fw * 0.05);
328
+ const xTo = isRight ? Math.floor(fw * 0.95) : Math.floor(fw * 0.65);
329
+ const xEdge = findPeakDiff(avgCols, xFrom, xTo, REFINE_MIN_EDGE_DIFF);
330
+ const yFrom = isBottom ? Math.floor(fh * 0.35) : Math.floor(fh * 0.05);
331
+ const yTo = isBottom ? Math.floor(fh * 0.95) : Math.floor(fh * 0.65);
332
+ const yEdge = findPeakDiff(avgRows, yFrom, yTo, REFINE_MIN_EDGE_DIFF);
333
+ if (xEdge.index < 0 || yEdge.index < 0) {
334
+ logger.info(`[FaceDetection] Edge refinement: no strong edges ` +
335
+ `(xDiff=${xEdge.magnitude.toFixed(1)}, yDiff=${yEdge.magnitude.toFixed(1)})`);
336
+ return null;
337
+ }
338
+ // Build the refined rectangle
339
+ let x, y, w, h;
340
+ if (isRight) {
341
+ x = xEdge.index + 1;
342
+ w = fw - x;
343
+ }
344
+ else {
345
+ x = 0;
346
+ w = xEdge.index;
347
+ }
348
+ if (isBottom) {
349
+ y = yEdge.index + 1;
350
+ h = fh - y;
351
+ }
352
+ else {
353
+ y = 0;
354
+ h = yEdge.index;
355
+ }
356
+ // Sanity: webcam should be 5-55% of frame in each dimension
357
+ if (w < fw * REFINE_MIN_SIZE_FRAC || h < fh * REFINE_MIN_SIZE_FRAC ||
358
+ w > fw * REFINE_MAX_SIZE_FRAC || h > fh * REFINE_MAX_SIZE_FRAC) {
359
+ logger.info(`[FaceDetection] Refined bounds implausible ` +
360
+ `(${w}x${h} in ${fw}x${fh}), using coarse bounds`);
361
+ return null;
362
+ }
363
+ logger.info(`[FaceDetection] Refined webcam: (${x},${y}) ${w}x${h} at analysis scale ` +
364
+ `(xDiff=${xEdge.magnitude.toFixed(1)}, yDiff=${yEdge.magnitude.toFixed(1)})`);
365
+ return { x, y, width: w, height: h };
366
+ }
367
+ // ── Public API ───────────────────────────────────────────────────────────────
368
+ /**
369
+ * Calculate confidence that a corner contains a webcam overlay based on
370
+ * per-frame scores. Combines consistency (fraction of non-zero frames) with
371
+ * average score.
372
+ */
373
+ export function calculateCornerConfidence(scores) {
374
+ if (scores.length === 0)
375
+ return 0;
376
+ const nonZeroCount = scores.filter(s => s > 0).length;
377
+ const consistency = nonZeroCount / scores.length;
378
+ const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
379
+ return consistency * Math.min(avgScore * 10, 1);
380
+ }
381
+ /**
382
+ * Detect a webcam overlay region in a screen recording.
383
+ *
384
+ * ### Two-phase approach
385
+ *
386
+ * **Phase 1 — Coarse corner scan:**
387
+ * Samples 5 frames at even intervals across the video and analyzes each of the
388
+ * four corners (25% × 25% regions) for skin-tone pixels and visual variance.
389
+ * A corner with consistent skin-tone presence across multiple frames is likely
390
+ * a webcam overlay. The scoring formula weights skin ratio by variance — webcam
391
+ * corners are visually busy (a moving face), while solid-color UI elements
392
+ * (like a colored status bar) have low variance even if they match skin tones.
393
+ *
394
+ * **Phase 2 — Refined edge detection ({@link refineBoundingBox}):**
395
+ * Once we know which corner, we find the overlay's exact pixel boundaries by
396
+ * looking for persistent intensity edges across frames.
397
+ *
398
+ * All analysis is performed on downscaled frames (320×180) for speed, then
399
+ * results are scaled back to the original video resolution.
400
+ *
401
+ * @param videoPath - Path to the source video file
402
+ * @returns The detected webcam region in original video resolution, or null
403
+ * if no webcam overlay is found with sufficient confidence
404
+ */
405
+ export async function detectWebcamRegion(videoPath) {
406
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'face-detect-'));
407
+ try {
408
+ const resolution = await getVideoResolution(videoPath);
409
+ const framePaths = await extractSampleFrames(videoPath, tempDir);
410
+ const positions = [
411
+ 'top-left', 'top-right', 'bottom-left', 'bottom-right',
412
+ ];
413
+ // Analyze all corners across all frames
414
+ const scoresByPosition = new Map();
415
+ for (const pos of positions) {
416
+ scoresByPosition.set(pos, []);
417
+ }
418
+ for (const framePath of framePaths) {
419
+ for (const pos of positions) {
420
+ const analysis = await analyzeCorner(framePath, pos);
421
+ // Score = skin ratio weighted by variance (webcam corners are visually busy)
422
+ const score = analysis.skinToneRatio > MIN_SKIN_RATIO
423
+ ? analysis.skinToneRatio * Math.min(analysis.variance / 1000, 1)
424
+ : 0;
425
+ scoresByPosition.get(pos).push(score);
426
+ }
427
+ }
428
+ // Find the corner with the highest consistent score
429
+ let bestPosition = null;
430
+ let bestConfidence = 0;
431
+ for (const [pos, scores] of scoresByPosition) {
432
+ const confidence = calculateCornerConfidence(scores);
433
+ if (confidence > bestConfidence) {
434
+ bestConfidence = confidence;
435
+ bestPosition = pos;
436
+ }
437
+ }
438
+ if (!bestPosition || bestConfidence < MIN_CONFIDENCE) {
439
+ logger.info('[FaceDetection] No webcam region detected');
440
+ return null;
441
+ }
442
+ // Refine the bounding box using edge detection, then map to original resolution
443
+ const refined = await refineBoundingBox(framePaths, bestPosition);
444
+ const scaleX = resolution.width / ANALYSIS_WIDTH;
445
+ const scaleY = resolution.height / ANALYSIS_HEIGHT;
446
+ let origX, origY, origW, origH;
447
+ if (refined) {
448
+ origX = Math.round(refined.x * scaleX);
449
+ origY = Math.round(refined.y * scaleY);
450
+ origW = Math.round(refined.width * scaleX);
451
+ origH = Math.round(refined.height * scaleY);
452
+ }
453
+ else {
454
+ // Fall back to coarse 25% corner bounds
455
+ const cornerW = Math.floor(ANALYSIS_WIDTH * CORNER_FRACTION);
456
+ const cornerH = Math.floor(ANALYSIS_HEIGHT * CORNER_FRACTION);
457
+ origW = Math.round(cornerW * scaleX);
458
+ origH = Math.round(cornerH * scaleY);
459
+ switch (bestPosition) {
460
+ case 'top-left':
461
+ origX = 0;
462
+ origY = 0;
463
+ break;
464
+ case 'top-right':
465
+ origX = resolution.width - origW;
466
+ origY = 0;
467
+ break;
468
+ case 'bottom-left':
469
+ origX = 0;
470
+ origY = resolution.height - origH;
471
+ break;
472
+ case 'bottom-right':
473
+ origX = resolution.width - origW;
474
+ origY = resolution.height - origH;
475
+ break;
476
+ }
477
+ }
478
+ const region = {
479
+ x: origX,
480
+ y: origY,
481
+ width: origW,
482
+ height: origH,
483
+ position: bestPosition,
484
+ confidence: Math.round(bestConfidence * 100) / 100,
485
+ };
486
+ logger.info(`[FaceDetection] Webcam detected at ${region.position} ` +
487
+ `(${region.x},${region.y} ${region.width}x${region.height}) ` +
488
+ `confidence=${region.confidence} refined=${!!refined}`);
489
+ return region;
490
+ }
491
+ finally {
492
+ // Clean up temp frames
493
+ const files = await fs.readdir(tempDir).catch(() => []);
494
+ for (const f of files) {
495
+ await fs.unlink(path.join(tempDir, f)).catch(() => { });
496
+ }
497
+ await fs.rmdir(tempDir).catch(() => { });
498
+ }
499
+ }
500
+ //# sourceMappingURL=faceDetection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faceDetection.js","sourceRoot":"","sources":["../../../src/tools/ffmpeg/faceDetection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Extract a single PNG frame at the given timestamp (seconds).
3
+ */
4
+ export declare function captureFrame(videoPath: string, timestamp: number, outputPath: string): Promise<string>;
5
+ /**
6
+ * Extract multiple frames at the given timestamps.
7
+ * Files are named snapshot-001.png, snapshot-002.png, etc.
8
+ */
9
+ export declare function captureFrames(videoPath: string, timestamps: number[], outputDir: string): Promise<string[]>;
10
+ //# sourceMappingURL=frameCapture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frameCapture.d.ts","sourceRoot":"","sources":["../../../src/tools/ffmpeg/frameCapture.ts"],"names":[],"mappings":"AAUA;;GAEG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAqBjB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAAE,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAcnB"}
@@ -0,0 +1,48 @@
1
+ import ffmpeg from 'fluent-ffmpeg';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ import logger from '../../config/logger';
5
+ const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg';
6
+ const ffprobePath = process.env.FFPROBE_PATH || 'ffprobe';
7
+ ffmpeg.setFfmpegPath(ffmpegPath);
8
+ ffmpeg.setFfprobePath(ffprobePath);
9
+ /**
10
+ * Extract a single PNG frame at the given timestamp (seconds).
11
+ */
12
+ export async function captureFrame(videoPath, timestamp, outputPath) {
13
+ const outputDir = path.dirname(outputPath);
14
+ await fs.mkdir(outputDir, { recursive: true });
15
+ logger.info(`Capturing frame at ${timestamp}s → ${outputPath}`);
16
+ return new Promise((resolve, reject) => {
17
+ ffmpeg(videoPath)
18
+ .seekInput(timestamp)
19
+ .frames(1)
20
+ .output(outputPath)
21
+ .on('end', () => {
22
+ logger.info(`Frame captured: ${outputPath}`);
23
+ resolve(outputPath);
24
+ })
25
+ .on('error', (err) => {
26
+ logger.error(`Frame capture failed: ${err.message}`);
27
+ reject(new Error(`Frame capture failed: ${err.message}`));
28
+ })
29
+ .run();
30
+ });
31
+ }
32
+ /**
33
+ * Extract multiple frames at the given timestamps.
34
+ * Files are named snapshot-001.png, snapshot-002.png, etc.
35
+ */
36
+ export async function captureFrames(videoPath, timestamps, outputDir) {
37
+ await fs.mkdir(outputDir, { recursive: true });
38
+ const results = [];
39
+ for (let i = 0; i < timestamps.length; i++) {
40
+ const idx = String(i + 1).padStart(3, '0');
41
+ const outputPath = path.join(outputDir, `snapshot-${idx}.png`);
42
+ await captureFrame(videoPath, timestamps[i], outputPath);
43
+ results.push(outputPath);
44
+ }
45
+ logger.info(`Captured ${results.length} frames in ${outputDir}`);
46
+ return results;
47
+ }
48
+ //# sourceMappingURL=frameCapture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frameCapture.js","sourceRoot":"","sources":["../../../src/tools/ffmpeg/frameCapture.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,MAAM,MAAM,qBAAqB,CAAC;AAEzC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,QAAQ,CAAC;AACvD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,SAAS,CAAC;AAC1D,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;AACjC,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;AAEnC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,SAAiB,EACjB,SAAiB,EACjB,UAAkB;IAElB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/C,MAAM,CAAC,IAAI,CAAC,sBAAsB,SAAS,OAAO,UAAU,EAAE,CAAC,CAAC;IAEhE,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC7C,MAAM,CAAC,SAAS,CAAC;aACd,SAAS,CAAC,SAAS,CAAC;aACpB,MAAM,CAAC,CAAC,CAAC;aACT,MAAM,CAAC,UAAU,CAAC;aAClB,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACd,MAAM,CAAC,IAAI,CAAC,mBAAmB,UAAU,EAAE,CAAC,CAAC;YAC7C,OAAO,CAAC,UAAU,CAAC,CAAC;QACtB,CAAC,CAAC;aACD,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACnB,MAAM,CAAC,KAAK,CAAC,yBAAyB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACrD,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC5D,CAAC,CAAC;aACD,GAAG,EAAE,CAAC;IACX,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,UAAoB,EACpB,SAAiB;IAEjB,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/C,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,GAAG,MAAM,CAAC,CAAC;QAC/D,MAAM,YAAY,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QACzD,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,MAAM,cAAc,SAAS,EAAE,CAAC,CAAC;IACjE,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface SilenceRegion {
2
+ start: number;
3
+ end: number;
4
+ duration: number;
5
+ }
6
+ /**
7
+ * Use FFmpeg silencedetect filter to find silence regions in an audio/video file.
8
+ */
9
+ export declare function detectSilence(audioPath: string, minDuration?: number, noiseThreshold?: string): Promise<SilenceRegion[]>;
10
+ //# sourceMappingURL=silenceDetection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"silenceDetection.d.ts","sourceRoot":"","sources":["../../../src/tools/ffmpeg/silenceDetection.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,WAAW,GAAE,MAAY,EACzB,cAAc,GAAE,MAAgB,GAC/B,OAAO,CAAC,aAAa,EAAE,CAAC,CAqD1B"}