mcp-video-analyzer 0.2.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/LICENSE +21 -0
- package/README.md +245 -0
- package/dist/adapters/adapter.interface.d.ts +15 -0
- package/dist/adapters/adapter.interface.js +17 -0
- package/dist/adapters/adapter.interface.js.map +1 -0
- package/dist/adapters/direct.adapter.d.ts +13 -0
- package/dist/adapters/direct.adapter.js +67 -0
- package/dist/adapters/direct.adapter.js.map +1 -0
- package/dist/adapters/loom.adapter.d.ts +13 -0
- package/dist/adapters/loom.adapter.js +183 -0
- package/dist/adapters/loom.adapter.js.map +1 -0
- package/dist/config/detail-levels.d.ts +18 -0
- package/dist/config/detail-levels.js +30 -0
- package/dist/config/detail-levels.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/processors/annotated-timeline.d.ts +25 -0
- package/dist/processors/annotated-timeline.js +83 -0
- package/dist/processors/annotated-timeline.js.map +1 -0
- package/dist/processors/audio-transcriber.d.ts +16 -0
- package/dist/processors/audio-transcriber.js +191 -0
- package/dist/processors/audio-transcriber.js.map +1 -0
- package/dist/processors/browser-frame-extractor.d.ts +27 -0
- package/dist/processors/browser-frame-extractor.js +132 -0
- package/dist/processors/browser-frame-extractor.js.map +1 -0
- package/dist/processors/frame-dedup.d.ts +23 -0
- package/dist/processors/frame-dedup.js +76 -0
- package/dist/processors/frame-dedup.js.map +1 -0
- package/dist/processors/frame-extractor.d.ts +19 -0
- package/dist/processors/frame-extractor.js +201 -0
- package/dist/processors/frame-extractor.js.map +1 -0
- package/dist/processors/frame-ocr.d.ts +13 -0
- package/dist/processors/frame-ocr.js +45 -0
- package/dist/processors/frame-ocr.js.map +1 -0
- package/dist/processors/image-optimizer.d.ts +7 -0
- package/dist/processors/image-optimizer.js +21 -0
- package/dist/processors/image-optimizer.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +67 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/analyze-moment.d.ts +2 -0
- package/dist/tools/analyze-moment.js +145 -0
- package/dist/tools/analyze-moment.js.map +1 -0
- package/dist/tools/analyze-video.d.ts +2 -0
- package/dist/tools/analyze-video.js +320 -0
- package/dist/tools/analyze-video.js.map +1 -0
- package/dist/tools/get-frame-at.d.ts +2 -0
- package/dist/tools/get-frame-at.js +88 -0
- package/dist/tools/get-frame-at.js.map +1 -0
- package/dist/tools/get-frame-burst.d.ts +2 -0
- package/dist/tools/get-frame-burst.js +106 -0
- package/dist/tools/get-frame-burst.js.map +1 -0
- package/dist/tools/get-frames.d.ts +2 -0
- package/dist/tools/get-frames.js +143 -0
- package/dist/tools/get-frames.js.map +1 -0
- package/dist/tools/get-metadata.d.ts +2 -0
- package/dist/tools/get-metadata.js +65 -0
- package/dist/tools/get-metadata.js.map +1 -0
- package/dist/tools/get-transcript.d.ts +2 -0
- package/dist/tools/get-transcript.js +82 -0
- package/dist/tools/get-transcript.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/cache.d.ts +31 -0
- package/dist/utils/cache.js +87 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/field-filter.d.ts +10 -0
- package/dist/utils/field-filter.js +32 -0
- package/dist/utils/field-filter.js.map +1 -0
- package/dist/utils/temp-files.d.ts +3 -0
- package/dist/utils/temp-files.js +28 -0
- package/dist/utils/temp-files.js.map +1 -0
- package/dist/utils/url-detector.d.ts +4 -0
- package/dist/utils/url-detector.js +33 -0
- package/dist/utils/url-detector.js.map +1 -0
- package/dist/utils/vtt-parser.d.ts +2 -0
- package/dist/utils/vtt-parser.js +85 -0
- package/dist/utils/vtt-parser.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { imageContent, UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAdapter } from '../adapters/adapter.interface.js';
|
|
4
|
+
import { extractSceneFrames, extractDenseFrames, probeVideoDuration, formatTimestamp, } from '../processors/frame-extractor.js';
|
|
5
|
+
import { extractBrowserFrames, generateTimestamps } from '../processors/browser-frame-extractor.js';
|
|
6
|
+
import { deduplicateFrames } from '../processors/frame-dedup.js';
|
|
7
|
+
import { extractTextFromFrames } from '../processors/frame-ocr.js';
|
|
8
|
+
import { buildAnnotatedTimeline } from '../processors/annotated-timeline.js';
|
|
9
|
+
import { optimizeFrames } from '../processors/image-optimizer.js';
|
|
10
|
+
import { extractAudioTrack, transcribeAudio } from '../processors/audio-transcriber.js';
|
|
11
|
+
import { createTempDir, cleanupTempDir } from '../utils/temp-files.js';
|
|
12
|
+
import { AnalysisCache, cacheKey } from '../utils/cache.js';
|
|
13
|
+
import { getDetailConfig } from '../config/detail-levels.js';
|
|
14
|
+
import { filterAnalysisResult } from '../utils/field-filter.js';
|
|
15
|
+
const cache = new AnalysisCache();
|
|
16
|
+
const ANALYSIS_FIELDS = [
|
|
17
|
+
'metadata',
|
|
18
|
+
'transcript',
|
|
19
|
+
'frames',
|
|
20
|
+
'comments',
|
|
21
|
+
'chapters',
|
|
22
|
+
'ocrResults',
|
|
23
|
+
'timeline',
|
|
24
|
+
'aiSummary',
|
|
25
|
+
];
|
|
26
|
+
const AnalyzeOptionsSchema = z
|
|
27
|
+
.object({
|
|
28
|
+
maxFrames: z
|
|
29
|
+
.number()
|
|
30
|
+
.min(1)
|
|
31
|
+
.max(60)
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Maximum number of key frames to extract (default depends on detail level)'),
|
|
34
|
+
threshold: z
|
|
35
|
+
.number()
|
|
36
|
+
.min(0)
|
|
37
|
+
.max(1)
|
|
38
|
+
.default(0.1)
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('Scene-change sensitivity 0.0-1.0 (lower = more frames, default: 0.1). Use 0.1 for screencasts/demos, 0.3 for live-action video.'),
|
|
41
|
+
returnBase64: z
|
|
42
|
+
.boolean()
|
|
43
|
+
.default(false)
|
|
44
|
+
.optional()
|
|
45
|
+
.describe('Return frames as base64 inline instead of file paths'),
|
|
46
|
+
skipFrames: z
|
|
47
|
+
.boolean()
|
|
48
|
+
.default(false)
|
|
49
|
+
.optional()
|
|
50
|
+
.describe('Skip frame extraction (transcript + metadata only)'),
|
|
51
|
+
detail: z
|
|
52
|
+
.enum(['brief', 'standard', 'detailed'])
|
|
53
|
+
.default('standard')
|
|
54
|
+
.optional()
|
|
55
|
+
.describe('Analysis depth: "brief" (metadata + truncated transcript, no frames), "standard" (default), "detailed" (dense sampling, more frames)'),
|
|
56
|
+
fields: z
|
|
57
|
+
.array(z.enum(ANALYSIS_FIELDS))
|
|
58
|
+
.optional()
|
|
59
|
+
.describe('Specific fields to return (default: all). E.g., ["metadata", "transcript"] returns only those fields.'),
|
|
60
|
+
forceRefresh: z
|
|
61
|
+
.boolean()
|
|
62
|
+
.default(false)
|
|
63
|
+
.optional()
|
|
64
|
+
.describe('Bypass cache and re-analyze the video'),
|
|
65
|
+
})
|
|
66
|
+
.optional();
|
|
67
|
+
const AnalyzeVideoSchema = z.object({
|
|
68
|
+
url: z.string().url().describe('Video URL (Loom share link or direct mp4/webm URL)'),
|
|
69
|
+
options: AnalyzeOptionsSchema.describe('Analysis options'),
|
|
70
|
+
});
|
|
71
|
+
export function registerAnalyzeVideo(server) {
|
|
72
|
+
server.addTool({
|
|
73
|
+
name: 'analyze_video',
|
|
74
|
+
description: `Analyze a video URL to extract transcript, key frames, metadata, comments, OCR text, and annotated timeline.
|
|
75
|
+
|
|
76
|
+
Returns structured data about the video content:
|
|
77
|
+
- Transcript with timestamps and speakers
|
|
78
|
+
- Key frames extracted via scene-change detection (deduplicated, as images)
|
|
79
|
+
- OCR text extracted from frames (code, error messages, UI text visible on screen)
|
|
80
|
+
- Annotated timeline merging transcript + frames + OCR into a unified chronological view
|
|
81
|
+
- Metadata (title, duration, platform)
|
|
82
|
+
- Comments from viewers (if available)
|
|
83
|
+
|
|
84
|
+
Supports: Loom (loom.com/share/...) and direct video URLs (.mp4, .webm, .mov).
|
|
85
|
+
|
|
86
|
+
Detail levels:
|
|
87
|
+
- "brief": metadata + truncated transcript only (fast, no video download)
|
|
88
|
+
- "standard": full analysis with scene-change frames (default)
|
|
89
|
+
- "detailed": dense sampling (1 frame/sec), more frames, full OCR
|
|
90
|
+
|
|
91
|
+
Use options.fields to request only specific data (e.g., ["metadata", "transcript"]).
|
|
92
|
+
Use options.forceRefresh to bypass the cache.`,
|
|
93
|
+
parameters: AnalyzeVideoSchema,
|
|
94
|
+
annotations: {
|
|
95
|
+
title: 'Analyze Video',
|
|
96
|
+
readOnlyHint: true,
|
|
97
|
+
destructiveHint: false,
|
|
98
|
+
idempotentHint: true,
|
|
99
|
+
openWorldHint: true,
|
|
100
|
+
},
|
|
101
|
+
execute: async (args, { reportProgress }) => {
|
|
102
|
+
const { url, options } = args;
|
|
103
|
+
const detail = options?.detail ?? 'standard';
|
|
104
|
+
const forceRefresh = options?.forceRefresh ?? false;
|
|
105
|
+
const fields = options?.fields;
|
|
106
|
+
const threshold = options?.threshold ?? 0.1;
|
|
107
|
+
// Resolve detail config
|
|
108
|
+
const config = getDetailConfig(detail);
|
|
109
|
+
const maxFrames = options?.maxFrames ?? config.maxFrames;
|
|
110
|
+
const skipFrames = options?.skipFrames ?? !config.includeFrames;
|
|
111
|
+
// Cache check
|
|
112
|
+
const key = cacheKey(url, { detail, maxFrames, threshold });
|
|
113
|
+
if (!forceRefresh) {
|
|
114
|
+
const cached = cache.get(key);
|
|
115
|
+
if (cached) {
|
|
116
|
+
const filtered = filterAnalysisResult(cached, fields);
|
|
117
|
+
const textData = { ...filtered, frameCount: cached.frames.length };
|
|
118
|
+
const content = [{ type: 'text', text: JSON.stringify(textData, null, 2) }];
|
|
119
|
+
// Re-add frame images if included
|
|
120
|
+
if (!fields || fields.includes('frames')) {
|
|
121
|
+
for (const frame of cached.frames) {
|
|
122
|
+
try {
|
|
123
|
+
content.push(await imageContent({ path: frame.filePath }));
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Frame file may have been cleaned up
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { content };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
let adapter;
|
|
134
|
+
try {
|
|
135
|
+
adapter = getAdapter(url);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
if (error instanceof UserError)
|
|
139
|
+
throw error;
|
|
140
|
+
throw new UserError(`Failed to detect video platform for URL: ${url}`);
|
|
141
|
+
}
|
|
142
|
+
const warnings = [];
|
|
143
|
+
let tempDir = null;
|
|
144
|
+
try {
|
|
145
|
+
await reportProgress({ progress: 0, total: 100 });
|
|
146
|
+
// Fetch metadata, transcript, comments in parallel
|
|
147
|
+
const [metadata, transcript, comments, chapters, aiSummary] = await Promise.all([
|
|
148
|
+
adapter.getMetadata(url).catch((e) => {
|
|
149
|
+
warnings.push(`Failed to fetch metadata: ${e instanceof Error ? e.message : String(e)}`);
|
|
150
|
+
return {
|
|
151
|
+
platform: adapter.name,
|
|
152
|
+
title: 'Unknown',
|
|
153
|
+
duration: 0,
|
|
154
|
+
durationFormatted: '0:00',
|
|
155
|
+
url,
|
|
156
|
+
};
|
|
157
|
+
}),
|
|
158
|
+
adapter.getTranscript(url).catch((e) => {
|
|
159
|
+
warnings.push(`Failed to fetch transcript: ${e instanceof Error ? e.message : String(e)}`);
|
|
160
|
+
return [];
|
|
161
|
+
}),
|
|
162
|
+
adapter.getComments(url).catch((e) => {
|
|
163
|
+
warnings.push(`Failed to fetch comments: ${e instanceof Error ? e.message : String(e)}`);
|
|
164
|
+
return [];
|
|
165
|
+
}),
|
|
166
|
+
adapter.getChapters(url).catch(() => []),
|
|
167
|
+
adapter.getAiSummary(url).catch(() => null),
|
|
168
|
+
]);
|
|
169
|
+
await reportProgress({ progress: 40, total: 100 });
|
|
170
|
+
// Apply transcript limit for brief mode
|
|
171
|
+
const limitedTranscript = config.transcriptMaxEntries !== null
|
|
172
|
+
? transcript.slice(0, config.transcriptMaxEntries)
|
|
173
|
+
: transcript;
|
|
174
|
+
// Frame extraction (if not skipped)
|
|
175
|
+
const result = {
|
|
176
|
+
metadata,
|
|
177
|
+
transcript: limitedTranscript,
|
|
178
|
+
frames: [],
|
|
179
|
+
comments,
|
|
180
|
+
chapters,
|
|
181
|
+
ocrResults: [],
|
|
182
|
+
timeline: [],
|
|
183
|
+
aiSummary: aiSummary ?? undefined,
|
|
184
|
+
warnings,
|
|
185
|
+
};
|
|
186
|
+
let videoPath = null;
|
|
187
|
+
if (!skipFrames) {
|
|
188
|
+
tempDir = await createTempDir();
|
|
189
|
+
let framesExtracted = false;
|
|
190
|
+
// Strategy 1: yt-dlp download + ffmpeg frame extraction
|
|
191
|
+
if (adapter.capabilities.videoDownload) {
|
|
192
|
+
videoPath = await adapter.downloadVideo(url, tempDir);
|
|
193
|
+
if (videoPath) {
|
|
194
|
+
await reportProgress({ progress: 60, total: 100 });
|
|
195
|
+
// Probe duration if metadata didn't provide it
|
|
196
|
+
if (metadata.duration === 0) {
|
|
197
|
+
const duration = await probeVideoDuration(videoPath).catch(() => 0);
|
|
198
|
+
metadata.duration = duration;
|
|
199
|
+
metadata.durationFormatted = formatTimestamp(Math.floor(duration));
|
|
200
|
+
}
|
|
201
|
+
// Extract frames: dense or scene-based
|
|
202
|
+
const rawFrames = config.denseSampling
|
|
203
|
+
? await extractDenseFrames(videoPath, tempDir, { maxFrames }).catch((e) => {
|
|
204
|
+
warnings.push(`Dense frame extraction failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
205
|
+
return [];
|
|
206
|
+
})
|
|
207
|
+
: await extractSceneFrames(videoPath, tempDir, {
|
|
208
|
+
threshold,
|
|
209
|
+
maxFrames,
|
|
210
|
+
}).catch((e) => {
|
|
211
|
+
warnings.push(`Frame extraction failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
212
|
+
return [];
|
|
213
|
+
});
|
|
214
|
+
await reportProgress({ progress: 80, total: 100 });
|
|
215
|
+
if (rawFrames.length > 0) {
|
|
216
|
+
const optimizedPaths = await optimizeFrames(rawFrames.map((f) => f.filePath), tempDir).catch((e) => {
|
|
217
|
+
warnings.push(`Frame optimization failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
218
|
+
return rawFrames.map((f) => f.filePath);
|
|
219
|
+
});
|
|
220
|
+
result.frames = rawFrames.map((frame, i) => ({
|
|
221
|
+
...frame,
|
|
222
|
+
filePath: optimizedPaths[i] ?? frame.filePath,
|
|
223
|
+
}));
|
|
224
|
+
framesExtracted = true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Strategy 2: Browser-based extraction (fallback)
|
|
229
|
+
if (!framesExtracted && metadata.duration > 0) {
|
|
230
|
+
await reportProgress({ progress: 60, total: 100 });
|
|
231
|
+
const timestamps = generateTimestamps(metadata.duration, maxFrames);
|
|
232
|
+
const browserFrames = await extractBrowserFrames(url, tempDir, {
|
|
233
|
+
timestamps,
|
|
234
|
+
}).catch((e) => {
|
|
235
|
+
warnings.push(`Browser frame extraction failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
236
|
+
return [];
|
|
237
|
+
});
|
|
238
|
+
await reportProgress({ progress: 80, total: 100 });
|
|
239
|
+
if (browserFrames.length > 0) {
|
|
240
|
+
result.frames = browserFrames;
|
|
241
|
+
framesExtracted = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (!framesExtracted) {
|
|
245
|
+
warnings.push('Frame extraction not available — returning transcript and metadata only. Install yt-dlp or Chrome/Chromium for frame extraction.');
|
|
246
|
+
}
|
|
247
|
+
// Post-processing: dedup, OCR, timeline
|
|
248
|
+
if (result.frames.length > 0) {
|
|
249
|
+
const beforeDedup = result.frames.length;
|
|
250
|
+
result.frames = await deduplicateFrames(result.frames).catch((e) => {
|
|
251
|
+
warnings.push(`Frame dedup failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
252
|
+
return result.frames;
|
|
253
|
+
});
|
|
254
|
+
if (result.frames.length < beforeDedup) {
|
|
255
|
+
warnings.push(`Removed ${beforeDedup - result.frames.length} near-duplicate frames (${beforeDedup} → ${result.frames.length})`);
|
|
256
|
+
}
|
|
257
|
+
await reportProgress({ progress: 85, total: 100 });
|
|
258
|
+
// OCR: extract text visible on screen
|
|
259
|
+
if (config.includeOcr) {
|
|
260
|
+
result.ocrResults = await extractTextFromFrames(result.frames).catch((e) => {
|
|
261
|
+
warnings.push(`OCR failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
262
|
+
return [];
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
await reportProgress({ progress: 95, total: 100 });
|
|
266
|
+
}
|
|
267
|
+
// Build annotated timeline
|
|
268
|
+
if (config.includeTimeline) {
|
|
269
|
+
result.timeline = buildAnnotatedTimeline(result.transcript, result.frames, result.ocrResults);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
// Even without frames, try to get the video for whisper fallback
|
|
274
|
+
if (result.transcript.length === 0 && adapter.capabilities.videoDownload) {
|
|
275
|
+
tempDir = tempDir ?? (await createTempDir());
|
|
276
|
+
videoPath = await adapter.downloadVideo(url, tempDir).catch(() => null);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Whisper fallback: if no transcript and we have a video file
|
|
280
|
+
if (result.transcript.length === 0 && videoPath) {
|
|
281
|
+
try {
|
|
282
|
+
const audioPath = await extractAudioTrack(videoPath, tempDir ?? '');
|
|
283
|
+
const whisperTranscript = await transcribeAudio(audioPath);
|
|
284
|
+
if (whisperTranscript.length > 0) {
|
|
285
|
+
result.transcript = whisperTranscript;
|
|
286
|
+
warnings.push('Transcript generated via Whisper fallback (no native transcript available).');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Audio extraction or transcription failed — not critical
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
await reportProgress({ progress: 100, total: 100 });
|
|
294
|
+
// Cache the full result
|
|
295
|
+
cache.set(key, result);
|
|
296
|
+
// Apply field filter
|
|
297
|
+
const filtered = filterAnalysisResult(result, fields);
|
|
298
|
+
// Build response content
|
|
299
|
+
const textData = {
|
|
300
|
+
...filtered,
|
|
301
|
+
frameCount: result.frames.length,
|
|
302
|
+
};
|
|
303
|
+
const content = [{ type: 'text', text: JSON.stringify(textData, null, 2) }];
|
|
304
|
+
// Add frame images (if not filtered out)
|
|
305
|
+
if (!fields || fields.includes('frames')) {
|
|
306
|
+
for (const frame of result.frames) {
|
|
307
|
+
content.push(await imageContent({ path: frame.filePath }));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return { content };
|
|
311
|
+
}
|
|
312
|
+
finally {
|
|
313
|
+
if (tempDir && skipFrames) {
|
|
314
|
+
await cleanupTempDir(tempDir).catch(() => undefined);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
//# sourceMappingURL=analyze-video.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze-video.js","sourceRoot":"","sources":["../../src/tools/analyze-video.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { imageContent, UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAdapter } from '../adapters/adapter.interface.js';
|
|
4
|
+
import { extractFrameAt, parseTimestamp } from '../processors/frame-extractor.js';
|
|
5
|
+
import { extractBrowserFrames } from '../processors/browser-frame-extractor.js';
|
|
6
|
+
import { optimizeFrame } from '../processors/image-optimizer.js';
|
|
7
|
+
import { createTempDir } from '../utils/temp-files.js';
|
|
8
|
+
import { getTempFilePath } from '../utils/temp-files.js';
|
|
9
|
+
const GetFrameAtSchema = z.object({
|
|
10
|
+
url: z.string().url().describe('Video URL (Loom share link or direct mp4/webm URL)'),
|
|
11
|
+
timestamp: z
|
|
12
|
+
.string()
|
|
13
|
+
.describe('Timestamp to extract frame at (e.g., "1:23", "0:05", "01:23:45")'),
|
|
14
|
+
returnBase64: z
|
|
15
|
+
.boolean()
|
|
16
|
+
.default(false)
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Return frame as base64 inline instead of file path'),
|
|
19
|
+
});
|
|
20
|
+
export function registerGetFrameAt(server) {
|
|
21
|
+
server.addTool({
|
|
22
|
+
name: 'get_frame_at',
|
|
23
|
+
description: `Extract a single video frame at a specific timestamp.
|
|
24
|
+
|
|
25
|
+
Useful for inspecting what's on screen at a particular moment. The AI reads the transcript,
|
|
26
|
+
identifies a critical moment, and requests the exact frame at that timestamp.
|
|
27
|
+
|
|
28
|
+
Supports: Loom (loom.com/share/...) and direct video URLs (.mp4, .webm, .mov).
|
|
29
|
+
Requires video download capability — direct URLs work best.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
- url: Video URL
|
|
33
|
+
- timestamp: Time position (e.g., "1:23", "0:05", "01:23:45")
|
|
34
|
+
|
|
35
|
+
Returns: A single image of the video frame at the specified timestamp.`,
|
|
36
|
+
parameters: GetFrameAtSchema,
|
|
37
|
+
annotations: {
|
|
38
|
+
title: 'Get Frame at Timestamp',
|
|
39
|
+
readOnlyHint: true,
|
|
40
|
+
destructiveHint: false,
|
|
41
|
+
idempotentHint: true,
|
|
42
|
+
openWorldHint: true,
|
|
43
|
+
},
|
|
44
|
+
execute: async (args, { reportProgress }) => {
|
|
45
|
+
const { url, timestamp } = args;
|
|
46
|
+
const adapter = getAdapter(url);
|
|
47
|
+
await reportProgress({ progress: 0, total: 100 });
|
|
48
|
+
const tempDir = await createTempDir();
|
|
49
|
+
// Strategy 1: Download video + ffmpeg extraction
|
|
50
|
+
if (adapter.capabilities.videoDownload) {
|
|
51
|
+
const videoPath = await adapter.downloadVideo(url, tempDir);
|
|
52
|
+
if (videoPath) {
|
|
53
|
+
await reportProgress({ progress: 50, total: 100 });
|
|
54
|
+
const frame = await extractFrameAt(videoPath, tempDir, timestamp);
|
|
55
|
+
const optimizedPath = getTempFilePath(tempDir, `opt_frame_at.jpg`);
|
|
56
|
+
await optimizeFrame(frame.filePath, optimizedPath);
|
|
57
|
+
await reportProgress({ progress: 100, total: 100 });
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{ type: 'text', text: `Frame extracted at ${timestamp}` },
|
|
61
|
+
await imageContent({ path: optimizedPath }),
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Strategy 2: Browser-based extraction (fallback)
|
|
67
|
+
await reportProgress({ progress: 30, total: 100 });
|
|
68
|
+
const seconds = parseTimestamp(timestamp);
|
|
69
|
+
const browserFrames = await extractBrowserFrames(url, tempDir, {
|
|
70
|
+
timestamps: [seconds],
|
|
71
|
+
});
|
|
72
|
+
if (browserFrames.length > 0) {
|
|
73
|
+
await reportProgress({ progress: 100, total: 100 });
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: 'text',
|
|
78
|
+
text: `Frame extracted at ${timestamp} (via browser)`,
|
|
79
|
+
},
|
|
80
|
+
await imageContent({ path: browserFrames[0].filePath }),
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
throw new UserError('Failed to extract frame. Install yt-dlp or Chrome/Chromium for frame extraction.');
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=get-frame-at.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-frame-at.js","sourceRoot":"","sources":["../../src/tools/get-frame-at.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { imageContent, UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAdapter } from '../adapters/adapter.interface.js';
|
|
4
|
+
import { extractFrameBurst, parseTimestamp } from '../processors/frame-extractor.js';
|
|
5
|
+
import { extractBrowserFrames } from '../processors/browser-frame-extractor.js';
|
|
6
|
+
import { optimizeFrames } from '../processors/image-optimizer.js';
|
|
7
|
+
import { createTempDir } from '../utils/temp-files.js';
|
|
8
|
+
const GetFrameBurstSchema = z.object({
|
|
9
|
+
url: z.string().url().describe('Video URL (Loom share link or direct mp4/webm URL)'),
|
|
10
|
+
from: z.string().describe('Start timestamp (e.g., "0:15")'),
|
|
11
|
+
to: z.string().describe('End timestamp (e.g., "0:17")'),
|
|
12
|
+
count: z
|
|
13
|
+
.number()
|
|
14
|
+
.min(2)
|
|
15
|
+
.max(30)
|
|
16
|
+
.default(5)
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Number of frames to extract (default: 5)'),
|
|
19
|
+
returnBase64: z
|
|
20
|
+
.boolean()
|
|
21
|
+
.default(false)
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Return frames as base64 inline instead of file paths'),
|
|
24
|
+
});
|
|
25
|
+
export function registerGetFrameBurst(server) {
|
|
26
|
+
server.addTool({
|
|
27
|
+
name: 'get_frame_burst',
|
|
28
|
+
description: `Extract multiple frames evenly distributed across a time range.
|
|
29
|
+
|
|
30
|
+
Designed for motion and vibration analysis where scene-change detection fails because
|
|
31
|
+
the "scene" doesn't change — only the position/state of objects does.
|
|
32
|
+
|
|
33
|
+
Example: get_frame_burst(url, "0:15", "0:17", 10) → 10 frames in 2 seconds
|
|
34
|
+
- AI sees the object in different positions across frames → understands the vibration
|
|
35
|
+
- Works for: shaking, flickering, animations, fast scrolling, loading spinners
|
|
36
|
+
|
|
37
|
+
Supports: Loom (loom.com/share/...) and direct video URLs (.mp4, .webm, .mov).
|
|
38
|
+
Requires video download capability — direct URLs work best.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
- url: Video URL
|
|
42
|
+
- from: Start timestamp (e.g., "0:15")
|
|
43
|
+
- to: End timestamp (e.g., "0:17")
|
|
44
|
+
- count: Number of frames (default: 5, max: 30)
|
|
45
|
+
|
|
46
|
+
Returns: N images evenly distributed between the from and to timestamps.`,
|
|
47
|
+
parameters: GetFrameBurstSchema,
|
|
48
|
+
annotations: {
|
|
49
|
+
title: 'Get Frame Burst',
|
|
50
|
+
readOnlyHint: true,
|
|
51
|
+
destructiveHint: false,
|
|
52
|
+
idempotentHint: true,
|
|
53
|
+
openWorldHint: true,
|
|
54
|
+
},
|
|
55
|
+
execute: async (args, { reportProgress }) => {
|
|
56
|
+
const { url, from, to, count } = args;
|
|
57
|
+
const frameCount = count ?? 5;
|
|
58
|
+
const adapter = getAdapter(url);
|
|
59
|
+
await reportProgress({ progress: 0, total: 100 });
|
|
60
|
+
const tempDir = await createTempDir();
|
|
61
|
+
// Strategy 1: Download video + ffmpeg burst extraction
|
|
62
|
+
if (adapter.capabilities.videoDownload) {
|
|
63
|
+
const videoPath = await adapter.downloadVideo(url, tempDir);
|
|
64
|
+
if (videoPath) {
|
|
65
|
+
await reportProgress({ progress: 40, total: 100 });
|
|
66
|
+
const frames = await extractFrameBurst(videoPath, tempDir, from, to, frameCount);
|
|
67
|
+
await reportProgress({ progress: 70, total: 100 });
|
|
68
|
+
const optimizedPaths = await optimizeFrames(frames.map((f) => f.filePath), tempDir);
|
|
69
|
+
await reportProgress({ progress: 100, total: 100 });
|
|
70
|
+
const content = [
|
|
71
|
+
{
|
|
72
|
+
type: 'text',
|
|
73
|
+
text: `Extracted ${optimizedPaths.length} frames from ${from} to ${to}`,
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
for (const path of optimizedPaths) {
|
|
77
|
+
content.push(await imageContent({ path }));
|
|
78
|
+
}
|
|
79
|
+
return { content };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Strategy 2: Browser-based extraction (fallback)
|
|
83
|
+
await reportProgress({ progress: 30, total: 100 });
|
|
84
|
+
const fromSeconds = parseTimestamp(from);
|
|
85
|
+
const toSeconds = parseTimestamp(to);
|
|
86
|
+
const interval = (toSeconds - fromSeconds) / Math.max(frameCount - 1, 1);
|
|
87
|
+
const timestamps = Array.from({ length: frameCount }, (_, i) => Math.round(fromSeconds + i * interval));
|
|
88
|
+
const browserFrames = await extractBrowserFrames(url, tempDir, { timestamps });
|
|
89
|
+
if (browserFrames.length > 0) {
|
|
90
|
+
await reportProgress({ progress: 100, total: 100 });
|
|
91
|
+
const content = [
|
|
92
|
+
{
|
|
93
|
+
type: 'text',
|
|
94
|
+
text: `Extracted ${browserFrames.length} frames from ${from} to ${to} (via browser)`,
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
for (const frame of browserFrames) {
|
|
98
|
+
content.push(await imageContent({ path: frame.filePath }));
|
|
99
|
+
}
|
|
100
|
+
return { content };
|
|
101
|
+
}
|
|
102
|
+
throw new UserError('Failed to extract frames. Install yt-dlp or Chrome/Chromium for frame extraction.');
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=get-frame-burst.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-frame-burst.js","sourceRoot":"","sources":["../../src/tools/get-frame-burst.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACrF,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,oDAAoD,CAAC;IACpF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gCAAgC,CAAC;IAC3D,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;IACvD,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;SACP,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,EAAE;SACV,QAAQ,CAAC,0CAA0C,CAAC;IACvD,YAAY,EAAE,CAAC;SACZ,OAAO,EAAE;SACT,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,EAAE;SACV,QAAQ,CAAC,sDAAsD,CAAC;CACpE,CAAC,CAAC;AAEH,MAAM,UAAU,qBAAqB,CAAC,MAAe;IACnD,MAAM,CAAC,OAAO,CAAC;QACb,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE;;;;;;;;;;;;;;;;;;yEAkBwD;QACrE,UAAU,EAAE,mBAAmB;QAC/B,WAAW,EAAE;YACX,KAAK,EAAE,iBAAiB;YACxB,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,IAAI;SACpB;QACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE;YAC1C,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;YACtC,MAAM,UAAU,GAAG,KAAK,IAAI,CAAC,CAAC;YAE9B,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;YAEhC,MAAM,cAAc,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAElD,MAAM,OAAO,GAAG,MAAM,aAAa,EAAE,CAAC;YAEtC,uDAAuD;YACvD,IAAI,OAAO,CAAC,YAAY,CAAC,aAAa,EAAE,CAAC;gBACvC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;gBAE5D,IAAI,SAAS,EAAE,CAAC;oBACd,MAAM,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;oBAEnD,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC;oBAEjF,MAAM,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;oBAEnD,MAAM,cAAc,GAAG,MAAM,cAAc,CACzC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAC7B,OAAO,CACR,CAAC;oBAEF,MAAM,cAAc,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;oBAEpD,MAAM,OAAO,GAGP;wBACJ;4BACE,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,aAAa,cAAc,CAAC,MAAM,gBAAgB,IAAI,OAAO,EAAE,EAAE;yBACxE;qBACF,CAAC;oBAEF,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;wBAClC,OAAO,CAAC,IAAI,CAAC,MAAM,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;oBAC7C,CAAC;oBAED,OAAO,EAAE,OAAO,EAAE,CAAC;gBACrB,CAAC;YACH,CAAC;YAED,kDAAkD;YAClD,MAAM,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YACnD,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;YACzC,MAAM,SAAS,GAAG,cAAc,CAAC,EAAE,CAAC,CAAC;YACrC,MAAM,QAAQ,GAAG,CAAC,SAAS,GAAG,WAAW,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YACzE,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC7D,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,GAAG,QAAQ,CAAC,CACvC,CAAC;YAEF,MAAM,aAAa,GAAG,MAAM,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;YAE/E,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC7B,MAAM,cAAc,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;gBAEpD,MAAM,OAAO,GAGP;oBACJ;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,aAAa,aAAa,CAAC,MAAM,gBAAgB,IAAI,OAAO,EAAE,gBAAgB;qBACrF;iBACF,CAAC;gBAEF,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;oBAClC,OAAO,CAAC,IAAI,CAAC,MAAM,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;gBAC7D,CAAC;gBAED,OAAO,EAAE,OAAO,EAAE,CAAC;YACrB,CAAC;YAED,MAAM,IAAI,SAAS,CACjB,mFAAmF,CACpF,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|