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.
- package/README.md +243 -0
- package/assets/fonts/Montserrat-Bold.ttf +0 -0
- package/assets/fonts/Montserrat-Regular.ttf +0 -0
- package/assets/fonts/OFL.txt +93 -0
- package/dist/__tests__/agents.test.d.ts +2 -0
- package/dist/__tests__/agents.test.d.ts.map +1 -0
- package/dist/__tests__/agents.test.js +434 -0
- package/dist/__tests__/agents.test.js.map +1 -0
- package/dist/__tests__/aspectRatio.test.d.ts +2 -0
- package/dist/__tests__/aspectRatio.test.d.ts.map +1 -0
- package/dist/__tests__/aspectRatio.test.js +406 -0
- package/dist/__tests__/aspectRatio.test.js.map +1 -0
- package/dist/__tests__/captionGenerator.test.d.ts +2 -0
- package/dist/__tests__/captionGenerator.test.d.ts.map +1 -0
- package/dist/__tests__/captionGenerator.test.js +435 -0
- package/dist/__tests__/captionGenerator.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +81 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/faceDetection.test.d.ts +2 -0
- package/dist/__tests__/faceDetection.test.d.ts.map +1 -0
- package/dist/__tests__/faceDetection.test.js +372 -0
- package/dist/__tests__/faceDetection.test.js.map +1 -0
- package/dist/__tests__/ffmpegTools.test.d.ts +2 -0
- package/dist/__tests__/ffmpegTools.test.d.ts.map +1 -0
- package/dist/__tests__/ffmpegTools.test.js +464 -0
- package/dist/__tests__/ffmpegTools.test.js.map +1 -0
- package/dist/__tests__/integration/captionBurn.test.d.ts +2 -0
- package/dist/__tests__/integration/captionBurn.test.d.ts.map +1 -0
- package/dist/__tests__/integration/captionBurn.test.js +103 -0
- package/dist/__tests__/integration/captionBurn.test.js.map +1 -0
- package/dist/__tests__/integration/clipComposite.test.d.ts +2 -0
- package/dist/__tests__/integration/clipComposite.test.d.ts.map +1 -0
- package/dist/__tests__/integration/clipComposite.test.js +56 -0
- package/dist/__tests__/integration/clipComposite.test.js.map +1 -0
- package/dist/__tests__/integration/faceDetection.test.d.ts +2 -0
- package/dist/__tests__/integration/faceDetection.test.d.ts.map +1 -0
- package/dist/__tests__/integration/faceDetection.test.js +85 -0
- package/dist/__tests__/integration/faceDetection.test.js.map +1 -0
- package/dist/__tests__/integration/ffmpegPipeline.test.d.ts +2 -0
- package/dist/__tests__/integration/ffmpegPipeline.test.d.ts.map +1 -0
- package/dist/__tests__/integration/ffmpegPipeline.test.js +88 -0
- package/dist/__tests__/integration/ffmpegPipeline.test.js.map +1 -0
- package/dist/__tests__/integration/fixture.d.ts +19 -0
- package/dist/__tests__/integration/fixture.d.ts.map +1 -0
- package/dist/__tests__/integration/fixture.js +112 -0
- package/dist/__tests__/integration/fixture.js.map +1 -0
- package/dist/__tests__/integration/fixture.test.d.ts +2 -0
- package/dist/__tests__/integration/fixture.test.d.ts.map +1 -0
- package/dist/__tests__/integration/fixture.test.js +27 -0
- package/dist/__tests__/integration/fixture.test.js.map +1 -0
- package/dist/__tests__/integration/realCaptions.test.d.ts +2 -0
- package/dist/__tests__/integration/realCaptions.test.d.ts.map +1 -0
- package/dist/__tests__/integration/realCaptions.test.js +226 -0
- package/dist/__tests__/integration/realCaptions.test.js.map +1 -0
- package/dist/__tests__/integration/realPipeline.test.d.ts +2 -0
- package/dist/__tests__/integration/realPipeline.test.d.ts.map +1 -0
- package/dist/__tests__/integration/realPipeline.test.js +210 -0
- package/dist/__tests__/integration/realPipeline.test.js.map +1 -0
- package/dist/__tests__/integration/silenceRemoval.test.d.ts +2 -0
- package/dist/__tests__/integration/silenceRemoval.test.d.ts.map +1 -0
- package/dist/__tests__/integration/silenceRemoval.test.js +93 -0
- package/dist/__tests__/integration/silenceRemoval.test.js.map +1 -0
- package/dist/__tests__/pipeline.test.d.ts +2 -0
- package/dist/__tests__/pipeline.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline.test.js +434 -0
- package/dist/__tests__/pipeline.test.js.map +1 -0
- package/dist/__tests__/services.test.d.ts +2 -0
- package/dist/__tests__/services.test.d.ts.map +1 -0
- package/dist/__tests__/services.test.js +655 -0
- package/dist/__tests__/services.test.js.map +1 -0
- package/dist/__tests__/silenceRemoval.test.d.ts +2 -0
- package/dist/__tests__/silenceRemoval.test.d.ts.map +1 -0
- package/dist/__tests__/silenceRemoval.test.js +266 -0
- package/dist/__tests__/silenceRemoval.test.js.map +1 -0
- package/dist/__tests__/singlePassEdit.test.d.ts +2 -0
- package/dist/__tests__/singlePassEdit.test.d.ts.map +1 -0
- package/dist/__tests__/singlePassEdit.test.js +321 -0
- package/dist/__tests__/singlePassEdit.test.js.map +1 -0
- package/dist/__tests__/smoke.test.d.ts +2 -0
- package/dist/__tests__/smoke.test.d.ts.map +1 -0
- package/dist/__tests__/smoke.test.js +8 -0
- package/dist/__tests__/smoke.test.js.map +1 -0
- package/dist/__tests__/utilities.test.d.ts +2 -0
- package/dist/__tests__/utilities.test.d.ts.map +1 -0
- package/dist/__tests__/utilities.test.js +268 -0
- package/dist/__tests__/utilities.test.js.map +1 -0
- package/dist/agents/BaseAgent.d.ts +52 -0
- package/dist/agents/BaseAgent.d.ts.map +1 -0
- package/dist/agents/BaseAgent.js +108 -0
- package/dist/agents/BaseAgent.js.map +1 -0
- package/dist/agents/BlogAgent.d.ts +3 -0
- package/dist/agents/BlogAgent.d.ts.map +1 -0
- package/dist/agents/BlogAgent.js +163 -0
- package/dist/agents/BlogAgent.js.map +1 -0
- package/dist/agents/ChapterAgent.d.ts +11 -0
- package/dist/agents/ChapterAgent.d.ts.map +1 -0
- package/dist/agents/ChapterAgent.js +191 -0
- package/dist/agents/ChapterAgent.js.map +1 -0
- package/dist/agents/MediumVideoAgent.d.ts +3 -0
- package/dist/agents/MediumVideoAgent.d.ts.map +1 -0
- package/dist/agents/MediumVideoAgent.js +219 -0
- package/dist/agents/MediumVideoAgent.js.map +1 -0
- package/dist/agents/ShortsAgent.d.ts +3 -0
- package/dist/agents/ShortsAgent.d.ts.map +1 -0
- package/dist/agents/ShortsAgent.js +243 -0
- package/dist/agents/ShortsAgent.js.map +1 -0
- package/dist/agents/SilenceRemovalAgent.d.ts +9 -0
- package/dist/agents/SilenceRemovalAgent.d.ts.map +1 -0
- package/dist/agents/SilenceRemovalAgent.js +208 -0
- package/dist/agents/SilenceRemovalAgent.js.map +1 -0
- package/dist/agents/SocialMediaAgent.d.ts +4 -0
- package/dist/agents/SocialMediaAgent.d.ts.map +1 -0
- package/dist/agents/SocialMediaAgent.js +248 -0
- package/dist/agents/SocialMediaAgent.js.map +1 -0
- package/dist/agents/SummaryAgent.d.ts +11 -0
- package/dist/agents/SummaryAgent.d.ts.map +1 -0
- package/dist/agents/SummaryAgent.js +333 -0
- package/dist/agents/SummaryAgent.js.map +1 -0
- package/dist/config/brand.d.ts +29 -0
- package/dist/config/brand.d.ts.map +1 -0
- package/dist/config/brand.js +83 -0
- package/dist/config/brand.js.map +1 -0
- package/dist/config/environment.d.ts +36 -0
- package/dist/config/environment.d.ts.map +1 -0
- package/dist/config/environment.js +44 -0
- package/dist/config/environment.js.map +1 -0
- package/dist/config/logger.d.ts +5 -0
- package/dist/config/logger.d.ts.map +1 -0
- package/dist/config/logger.js +13 -0
- package/dist/config/logger.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +135 -0
- package/dist/index.js.map +1 -0
- package/dist/pipeline.d.ts +57 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +287 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/services/captionGeneration.d.ts +7 -0
- package/dist/services/captionGeneration.d.ts.map +1 -0
- package/dist/services/captionGeneration.js +29 -0
- package/dist/services/captionGeneration.js.map +1 -0
- package/dist/services/fileWatcher.d.ts +19 -0
- package/dist/services/fileWatcher.d.ts.map +1 -0
- package/dist/services/fileWatcher.js +120 -0
- package/dist/services/fileWatcher.js.map +1 -0
- package/dist/services/gitOperations.d.ts +3 -0
- package/dist/services/gitOperations.d.ts.map +1 -0
- package/dist/services/gitOperations.js +43 -0
- package/dist/services/gitOperations.js.map +1 -0
- package/dist/services/socialPosting.d.ts +38 -0
- package/dist/services/socialPosting.d.ts.map +1 -0
- package/dist/services/socialPosting.js +102 -0
- package/dist/services/socialPosting.js.map +1 -0
- package/dist/services/transcription.d.ts +3 -0
- package/dist/services/transcription.d.ts.map +1 -0
- package/dist/services/transcription.js +100 -0
- package/dist/services/transcription.js.map +1 -0
- package/dist/services/videoIngestion.d.ts +3 -0
- package/dist/services/videoIngestion.d.ts.map +1 -0
- package/dist/services/videoIngestion.js +103 -0
- package/dist/services/videoIngestion.js.map +1 -0
- package/dist/tools/captions/captionGenerator.d.ts +84 -0
- package/dist/tools/captions/captionGenerator.d.ts.map +1 -0
- package/dist/tools/captions/captionGenerator.js +390 -0
- package/dist/tools/captions/captionGenerator.js.map +1 -0
- package/dist/tools/ffmpeg/aspectRatio.d.ts +101 -0
- package/dist/tools/ffmpeg/aspectRatio.d.ts.map +1 -0
- package/dist/tools/ffmpeg/aspectRatio.js +338 -0
- package/dist/tools/ffmpeg/aspectRatio.js.map +1 -0
- package/dist/tools/ffmpeg/audioExtraction.d.ts +16 -0
- package/dist/tools/ffmpeg/audioExtraction.d.ts.map +1 -0
- package/dist/tools/ffmpeg/audioExtraction.js +86 -0
- package/dist/tools/ffmpeg/audioExtraction.js.map +1 -0
- package/dist/tools/ffmpeg/captionBurning.d.ts +8 -0
- package/dist/tools/ffmpeg/captionBurning.d.ts.map +1 -0
- package/dist/tools/ffmpeg/captionBurning.js +71 -0
- package/dist/tools/ffmpeg/captionBurning.js.map +1 -0
- package/dist/tools/ffmpeg/clipExtraction.d.ts +23 -0
- package/dist/tools/ffmpeg/clipExtraction.d.ts.map +1 -0
- package/dist/tools/ffmpeg/clipExtraction.js +178 -0
- package/dist/tools/ffmpeg/clipExtraction.js.map +1 -0
- package/dist/tools/ffmpeg/faceDetection.d.ts +127 -0
- package/dist/tools/ffmpeg/faceDetection.d.ts.map +1 -0
- package/dist/tools/ffmpeg/faceDetection.js +500 -0
- package/dist/tools/ffmpeg/faceDetection.js.map +1 -0
- package/dist/tools/ffmpeg/frameCapture.d.ts +10 -0
- package/dist/tools/ffmpeg/frameCapture.d.ts.map +1 -0
- package/dist/tools/ffmpeg/frameCapture.js +48 -0
- package/dist/tools/ffmpeg/frameCapture.js.map +1 -0
- package/dist/tools/ffmpeg/silenceDetection.d.ts +10 -0
- package/dist/tools/ffmpeg/silenceDetection.d.ts.map +1 -0
- package/dist/tools/ffmpeg/silenceDetection.js +55 -0
- package/dist/tools/ffmpeg/silenceDetection.js.map +1 -0
- package/dist/tools/ffmpeg/singlePassEdit.d.ts +25 -0
- package/dist/tools/ffmpeg/singlePassEdit.d.ts.map +1 -0
- package/dist/tools/ffmpeg/singlePassEdit.js +123 -0
- package/dist/tools/ffmpeg/singlePassEdit.js.map +1 -0
- package/dist/tools/search/exaClient.d.ts +8 -0
- package/dist/tools/search/exaClient.d.ts.map +1 -0
- package/dist/tools/search/exaClient.js +38 -0
- package/dist/tools/search/exaClient.js.map +1 -0
- package/dist/tools/whisper/whisperClient.d.ts +3 -0
- package/dist/tools/whisper/whisperClient.d.ts.map +1 -0
- package/dist/tools/whisper/whisperClient.js +77 -0
- package/dist/tools/whisper/whisperClient.js.map +1 -0
- package/dist/types/index.d.ts +305 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +44 -0
- package/dist/types/index.js.map +1 -0
- 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"}
|