agentreel 0.2.5 → 0.2.6
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/bin/agentreel.mjs +86 -22
- package/package.json +1 -1
package/bin/agentreel.mjs
CHANGED
|
@@ -154,6 +154,89 @@ function extractBrowserHighlights(videoPath, task) {
|
|
|
154
154
|
return outFile;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
// ── Browser Highlight Builder ───────────────────────────────
|
|
158
|
+
|
|
159
|
+
function buildBrowserHighlights(clicks, videoPath, task) {
|
|
160
|
+
const CLIP_DUR = 7;
|
|
161
|
+
const labels = ["Overview", "Interact", "Navigate", "Result"];
|
|
162
|
+
const overlays = ["**First look**", "**Key action**", "**Exploring**", "**The result**"];
|
|
163
|
+
|
|
164
|
+
// If we have clicks, cluster them into highlights
|
|
165
|
+
if (clicks.length >= 2) {
|
|
166
|
+
// Group clicks that are within 3s of each other
|
|
167
|
+
const clusters = [];
|
|
168
|
+
let cluster = [clicks[0]];
|
|
169
|
+
|
|
170
|
+
for (let i = 1; i < clicks.length; i++) {
|
|
171
|
+
if (clicks[i].timeSec - cluster[cluster.length - 1].timeSec < 3) {
|
|
172
|
+
cluster.push(clicks[i]);
|
|
173
|
+
} else {
|
|
174
|
+
clusters.push(cluster);
|
|
175
|
+
cluster = [clicks[i]];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
clusters.push(cluster);
|
|
179
|
+
|
|
180
|
+
// Take up to 4 clusters, pick the ones with most clicks
|
|
181
|
+
const ranked = clusters
|
|
182
|
+
.map((c, i) => ({ cluster: c, idx: i }))
|
|
183
|
+
.sort((a, b) => b.cluster.length - a.cluster.length)
|
|
184
|
+
.slice(0, 4)
|
|
185
|
+
.sort((a, b) => a.cluster[0].timeSec - b.cluster[0].timeSec);
|
|
186
|
+
|
|
187
|
+
const highlights = ranked.map((r, i) => {
|
|
188
|
+
const first = r.cluster[0];
|
|
189
|
+
const last = r.cluster[r.cluster.length - 1];
|
|
190
|
+
const center = (first.timeSec + last.timeSec) / 2;
|
|
191
|
+
const startSec = Math.max(0, center - CLIP_DUR / 2);
|
|
192
|
+
const endSec = startSec + CLIP_DUR;
|
|
193
|
+
|
|
194
|
+
const hlClicks = r.cluster.map(c => ({
|
|
195
|
+
x: Math.max(0, Math.min(1280, c.x)),
|
|
196
|
+
y: Math.max(0, Math.min(800, c.y)),
|
|
197
|
+
timeSec: c.timeSec - startSec,
|
|
198
|
+
}));
|
|
199
|
+
|
|
200
|
+
const focusX = hlClicks.reduce((s, c) => s + c.x, 0) / hlClicks.length / 1280;
|
|
201
|
+
const focusY = hlClicks.reduce((s, c) => s + c.y, 0) / hlClicks.length / 800;
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
label: labels[i % labels.length],
|
|
205
|
+
overlay: overlays[i % overlays.length],
|
|
206
|
+
videoSrc: "browser-demo.mp4",
|
|
207
|
+
videoStartSec: Math.round(startSec * 10) / 10,
|
|
208
|
+
videoEndSec: Math.round(endSec * 10) / 10,
|
|
209
|
+
focusX,
|
|
210
|
+
focusY,
|
|
211
|
+
clicks: hlClicks,
|
|
212
|
+
};
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
console.error(` ${highlights.length} highlights from ${clicks.length} clicks`);
|
|
216
|
+
return highlights;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Fallback: try Claude extraction, or use evenly-spaced defaults
|
|
220
|
+
try {
|
|
221
|
+
const highlightsPath = extractBrowserHighlights(videoPath, task);
|
|
222
|
+
const highlights = JSON.parse(readFileSync(highlightsPath, "utf-8"));
|
|
223
|
+
if (highlights.length > 0) {
|
|
224
|
+
console.error(` ${highlights.length} highlights from Claude`);
|
|
225
|
+
return highlights;
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
// Claude failed, use defaults
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Last resort: evenly-spaced clips
|
|
232
|
+
console.error(" Using default highlights (no clicks, no Claude)");
|
|
233
|
+
return [
|
|
234
|
+
{ label: "Overview", overlay: "**Quick look**", videoSrc: "browser-demo.mp4", videoStartSec: 1, videoEndSec: 8 },
|
|
235
|
+
{ label: "Features", overlay: "**Key features**", videoSrc: "browser-demo.mp4", videoStartSec: 8, videoEndSec: 15 },
|
|
236
|
+
{ label: "Result", overlay: "**See it work**", videoSrc: "browser-demo.mp4", videoStartSec: 15, videoEndSec: 22 },
|
|
237
|
+
];
|
|
238
|
+
}
|
|
239
|
+
|
|
157
240
|
// ── Render ──────────────────────────────────────────────────
|
|
158
241
|
|
|
159
242
|
async function renderVideo(props, output, musicPath) {
|
|
@@ -312,12 +395,9 @@ async function main() {
|
|
|
312
395
|
if (!existsSync(publicDir)) mkdirSync(publicDir, { recursive: true });
|
|
313
396
|
copyFileSync(videoPath, join(publicDir, "browser-demo.mp4"));
|
|
314
397
|
|
|
315
|
-
console.error("Step 2/3:
|
|
316
|
-
const highlightsPath = extractBrowserHighlights(videoPath, task);
|
|
317
|
-
const highlights = JSON.parse(readFileSync(highlightsPath, "utf-8"));
|
|
318
|
-
console.error(` ${highlights.length} highlights extracted`);
|
|
398
|
+
console.error("Step 2/3: Building highlights...");
|
|
319
399
|
|
|
320
|
-
//
|
|
400
|
+
// Read click data — this is the primary signal for highlights
|
|
321
401
|
const clicksPath = videoPath.replace(".mp4", "-clicks.json");
|
|
322
402
|
let allClicks = [];
|
|
323
403
|
if (existsSync(clicksPath)) {
|
|
@@ -325,23 +405,7 @@ async function main() {
|
|
|
325
405
|
console.error(` ${allClicks.length} clicks captured`);
|
|
326
406
|
}
|
|
327
407
|
|
|
328
|
-
|
|
329
|
-
const startSec = h.videoStartSec || 0;
|
|
330
|
-
const endSec = h.videoEndSec || (startSec + 7);
|
|
331
|
-
|
|
332
|
-
h.clicks = allClicks
|
|
333
|
-
.filter(c => c.timeSec >= startSec && c.timeSec <= endSec)
|
|
334
|
-
.map(c => ({
|
|
335
|
-
x: Math.max(0, Math.min(1280, c.x)),
|
|
336
|
-
y: Math.max(0, Math.min(800, c.y)),
|
|
337
|
-
timeSec: c.timeSec - startSec,
|
|
338
|
-
}));
|
|
339
|
-
|
|
340
|
-
if (h.clicks.length > 0) {
|
|
341
|
-
h.focusX = h.clicks.reduce((s, c) => s + c.x, 0) / h.clicks.length / 1280;
|
|
342
|
-
h.focusY = h.clicks.reduce((s, c) => s + c.y, 0) / h.clicks.length / 800;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
408
|
+
const highlights = buildBrowserHighlights(allClicks, videoPath, task);
|
|
345
409
|
|
|
346
410
|
console.error("Step 3/3: Rendering video...");
|
|
347
411
|
await renderVideo({
|