@lumy-pack/scene-sieve 0.0.4 → 0.0.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/dist/cli.mjs +693 -255
- package/dist/commands/Sieve.d.ts +15 -0
- package/dist/components/PhaseStep.d.ts +14 -0
- package/dist/components/ProgressBar.d.ts +7 -0
- package/dist/constants.d.ts +1 -0
- package/dist/core/analyzer.d.ts +9 -2
- package/dist/core/extractor.d.ts +2 -2
- package/dist/core/pipeline-worker.d.ts +1 -0
- package/dist/core/run-in-worker.d.ts +9 -0
- package/dist/core/workspace.d.ts +5 -0
- package/dist/index.cjs +115 -44
- package/dist/index.mjs +116 -45
- package/dist/pipeline-worker.mjs +1136 -0
- package/dist/types/index.d.ts +28 -0
- package/package.json +5 -4
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SieveViewProps {
|
|
3
|
+
input: string;
|
|
4
|
+
count?: number;
|
|
5
|
+
threshold?: number;
|
|
6
|
+
output?: string;
|
|
7
|
+
fps: number;
|
|
8
|
+
maxFrames: number;
|
|
9
|
+
scale: number;
|
|
10
|
+
quality: number;
|
|
11
|
+
iouThreshold?: number;
|
|
12
|
+
animationThreshold?: number;
|
|
13
|
+
debug: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare const SieveView: React.FC<SieveViewProps>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type PhaseStatus = 'pending' | 'running' | 'done' | 'failed';
|
|
3
|
+
export interface PhaseState {
|
|
4
|
+
label: string;
|
|
5
|
+
status: PhaseStatus;
|
|
6
|
+
hasProgress: boolean;
|
|
7
|
+
percent: number;
|
|
8
|
+
durationMs?: number;
|
|
9
|
+
}
|
|
10
|
+
interface PhaseStepProps {
|
|
11
|
+
phase: PhaseState;
|
|
12
|
+
}
|
|
13
|
+
export declare const PhaseStep: React.FC<PhaseStepProps>;
|
|
14
|
+
export {};
|
package/dist/constants.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const DEFAULT_THRESHOLD = 0.5;
|
|
|
4
4
|
export declare const DEFAULT_FPS = 5;
|
|
5
5
|
export declare const DEFAULT_SCALE = 720;
|
|
6
6
|
export declare const DEFAULT_QUALITY = 80;
|
|
7
|
+
export declare const DEFAULT_MAX_FRAMES = 300;
|
|
7
8
|
export declare const NORMALIZATION_PERCENTILE = 0.9;
|
|
8
9
|
export declare const WORKSPACE_PREFIX = "scene-sieve-";
|
|
9
10
|
export declare const TEMP_BASE_DIR: string;
|
package/dist/core/analyzer.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BoundingBox, ProcessContext
|
|
1
|
+
import type { AnalysisResult, AnimationMetadata, BoundingBox, ProcessContext } from '../types/index.js';
|
|
2
2
|
import type { Point2D } from './dbscan.js';
|
|
3
3
|
type CvLib = typeof import('@techstark/opencv-js');
|
|
4
4
|
export declare function preprocessFrame(framePath: string, scale: number): Promise<{
|
|
@@ -8,8 +8,15 @@ export declare function preprocessFrame(framePath: string, scale: number): Promi
|
|
|
8
8
|
}>;
|
|
9
9
|
export declare function computeIoU(a: BoundingBox, b: BoundingBox): number;
|
|
10
10
|
export declare class IoUTracker {
|
|
11
|
+
private fps;
|
|
12
|
+
private iouThreshold;
|
|
13
|
+
private animationThreshold;
|
|
11
14
|
private regions;
|
|
15
|
+
private extractedAnimations;
|
|
16
|
+
constructor(fps?: number, iouThreshold?: number, animationThreshold?: number);
|
|
12
17
|
update(boxes: BoundingBox[], pairIndex: number): Set<number>;
|
|
18
|
+
private collectAnimation;
|
|
19
|
+
flushAndGetAnimations(): AnimationMetadata[];
|
|
13
20
|
getAnimationWeight(boxIndex: number, boxes: BoundingBox[]): number;
|
|
14
21
|
}
|
|
15
22
|
export interface AKAZEResult {
|
|
@@ -51,5 +58,5 @@ export declare function computeInformationGain(clusters: BoundingBox[], clusterP
|
|
|
51
58
|
* 3. Spatio-temporal IoU Tracking
|
|
52
59
|
* 4. G(t) Information Gain Scoring
|
|
53
60
|
*/
|
|
54
|
-
export declare function analyzeFrames(ctx: ProcessContext): Promise<
|
|
61
|
+
export declare function analyzeFrames(ctx: ProcessContext): Promise<AnalysisResult>;
|
|
55
62
|
export {};
|
package/dist/core/extractor.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FrameNode, ProcessContext } from '../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Extract frames from video/GIF using FFmpeg.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Always uses FPS-based extraction. For long videos, FPS is automatically
|
|
5
|
+
* reduced to stay within maxFrames budget.
|
|
6
6
|
*/
|
|
7
7
|
export declare function extractFrames(ctx: ProcessContext): Promise<FrameNode[]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProgressPhase, SieveInput, SieveOptionsBase, SieveResult } from '../types/index.js';
|
|
2
|
+
export type SieveWorkerOptions = Omit<SieveOptionsBase, 'onProgress'> & SieveInput;
|
|
3
|
+
/**
|
|
4
|
+
* Run the pipeline, choosing the best execution strategy:
|
|
5
|
+
*
|
|
6
|
+
* - Production (bundled .mjs): Worker thread — spinner never freezes
|
|
7
|
+
* - Dev mode (tsx .ts): Main thread — simpler, spinner may stutter during CPU work
|
|
8
|
+
*/
|
|
9
|
+
export declare function runPipelineInWorker(options: SieveWorkerOptions, onProgress: (phase: ProgressPhase, percent: number) => void): Promise<SieveResult>;
|
package/dist/core/workspace.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { FrameNode, ProcessContext } from '../types/index.js';
|
|
|
2
2
|
export declare function createWorkspace(sessionId: string): Promise<string>;
|
|
3
3
|
export declare function finalizeOutput(ctx: ProcessContext, selectedFrames: FrameNode[]): Promise<string[]>;
|
|
4
4
|
export declare function cleanupWorkspace(workspacePath: string): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Remove stale workspace directories left by previous interrupted runs.
|
|
7
|
+
* Only deletes directories older than 1 hour to avoid removing active workspaces.
|
|
8
|
+
*/
|
|
9
|
+
export declare function cleanupStaleWorkspaces(): Promise<void>;
|
|
5
10
|
/**
|
|
6
11
|
* Write a video buffer to a temp file in the workspace and return the path.
|
|
7
12
|
* Used by 'buffer' input mode.
|
package/dist/index.cjs
CHANGED
|
@@ -84,6 +84,7 @@ var DEFAULT_THRESHOLD = 0.5;
|
|
|
84
84
|
var DEFAULT_FPS = 5;
|
|
85
85
|
var DEFAULT_SCALE = 720;
|
|
86
86
|
var DEFAULT_QUALITY = 80;
|
|
87
|
+
var DEFAULT_MAX_FRAMES = 300;
|
|
87
88
|
var NORMALIZATION_PERCENTILE = 0.9;
|
|
88
89
|
var WORKSPACE_PREFIX = `${APP_NAME}-`;
|
|
89
90
|
var TEMP_BASE_DIR = (0, import_node_os.tmpdir)();
|
|
@@ -96,8 +97,8 @@ var SUPPORTED_VIDEO_EXTENSIONS = [
|
|
|
96
97
|
];
|
|
97
98
|
var SUPPORTED_GIF_EXTENSIONS = [".gif"];
|
|
98
99
|
var FRAME_OUTPUT_EXTENSION = ".jpg";
|
|
100
|
+
var FRAME_FILENAME_PATTERN = "frame_%06d.jpg";
|
|
99
101
|
var OPENCV_BATCH_SIZE = 10;
|
|
100
|
-
var MIN_IFRAME_COUNT = 3;
|
|
101
102
|
var DBSCAN_ALPHA = 0.03;
|
|
102
103
|
var DBSCAN_MIN_PTS = 4;
|
|
103
104
|
var IOU_THRESHOLD = 0.9;
|
|
@@ -236,7 +237,13 @@ function computeIoU(a, b) {
|
|
|
236
237
|
return union === 0 ? 0 : intersection / union;
|
|
237
238
|
}
|
|
238
239
|
var IoUTracker = class {
|
|
240
|
+
constructor(fps = DEFAULT_FPS, iouThreshold = IOU_THRESHOLD, animationThreshold = ANIMATION_FRAME_THRESHOLD) {
|
|
241
|
+
this.fps = fps;
|
|
242
|
+
this.iouThreshold = iouThreshold;
|
|
243
|
+
this.animationThreshold = animationThreshold;
|
|
244
|
+
}
|
|
239
245
|
regions = [];
|
|
246
|
+
extractedAnimations = [];
|
|
240
247
|
update(boxes, pairIndex) {
|
|
241
248
|
const animationIndices = /* @__PURE__ */ new Set();
|
|
242
249
|
const matched = /* @__PURE__ */ new Set();
|
|
@@ -252,7 +259,7 @@ var IoUTracker = class {
|
|
|
252
259
|
bestRegionIdx = ri;
|
|
253
260
|
}
|
|
254
261
|
}
|
|
255
|
-
if (bestIoU >
|
|
262
|
+
if (bestIoU > this.iouThreshold && bestRegionIdx !== -1) {
|
|
256
263
|
const region = this.regions[bestRegionIdx];
|
|
257
264
|
const gap = pairIndex - region.lastSeen;
|
|
258
265
|
region.box = box;
|
|
@@ -260,13 +267,14 @@ var IoUTracker = class {
|
|
|
260
267
|
region.lastSeen = pairIndex;
|
|
261
268
|
region.weight *= Math.pow(DECAY_LAMBDA, gap);
|
|
262
269
|
matched.add(bestRegionIdx);
|
|
263
|
-
if (region.consecutiveCount >=
|
|
270
|
+
if (region.consecutiveCount >= this.animationThreshold) {
|
|
264
271
|
animationIndices.add(bi);
|
|
265
272
|
}
|
|
266
273
|
} else {
|
|
267
274
|
this.regions.push({
|
|
268
275
|
box,
|
|
269
276
|
consecutiveCount: 1,
|
|
277
|
+
firstSeen: pairIndex,
|
|
270
278
|
lastSeen: pairIndex,
|
|
271
279
|
weight: 1
|
|
272
280
|
});
|
|
@@ -278,17 +286,45 @@ var IoUTracker = class {
|
|
|
278
286
|
this.regions[ri].weight *= Math.pow(DECAY_LAMBDA, gap);
|
|
279
287
|
}
|
|
280
288
|
}
|
|
281
|
-
|
|
289
|
+
for (let i = 0; i < this.regions.length; i++) {
|
|
290
|
+
const region = this.regions[i];
|
|
291
|
+
if (region.weight <= 0.01 && !matched.has(i)) {
|
|
292
|
+
this.collectAnimation(region);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
this.regions = this.regions.filter(
|
|
296
|
+
(r, i) => r.weight > 0.01 || matched.has(i)
|
|
297
|
+
);
|
|
282
298
|
return animationIndices;
|
|
283
299
|
}
|
|
300
|
+
collectAnimation(region) {
|
|
301
|
+
if (region.consecutiveCount >= this.animationThreshold) {
|
|
302
|
+
const durationMs = region.consecutiveCount / this.fps * 1e3;
|
|
303
|
+
this.extractedAnimations.push({
|
|
304
|
+
type: "loading_spinner",
|
|
305
|
+
// 기본값으로 loading_spinner 사용
|
|
306
|
+
boundingBox: region.box,
|
|
307
|
+
startFrameId: region.firstSeen,
|
|
308
|
+
endFrameId: region.lastSeen,
|
|
309
|
+
durationMs
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
flushAndGetAnimations() {
|
|
314
|
+
for (const region of this.regions) {
|
|
315
|
+
this.collectAnimation(region);
|
|
316
|
+
}
|
|
317
|
+
this.regions = [];
|
|
318
|
+
return this.extractedAnimations;
|
|
319
|
+
}
|
|
284
320
|
getAnimationWeight(boxIndex, boxes) {
|
|
285
321
|
if (boxIndex >= boxes.length) return 0;
|
|
286
322
|
const box = boxes[boxIndex];
|
|
287
323
|
let maxWeight = 0;
|
|
288
324
|
for (const region of this.regions) {
|
|
289
|
-
if (region.consecutiveCount >=
|
|
325
|
+
if (region.consecutiveCount >= this.animationThreshold) {
|
|
290
326
|
const iou = computeIoU(box, region.box);
|
|
291
|
-
if (iou >
|
|
327
|
+
if (iou > this.iouThreshold) {
|
|
292
328
|
maxWeight = Math.max(maxWeight, region.weight);
|
|
293
329
|
}
|
|
294
330
|
}
|
|
@@ -510,13 +546,17 @@ async function analyzeBatch(cvLib, frames, scale, tracker, pairOffset) {
|
|
|
510
546
|
}
|
|
511
547
|
async function analyzeFrames(ctx) {
|
|
512
548
|
const { frames } = ctx;
|
|
513
|
-
if (frames.length < 2) return [];
|
|
549
|
+
if (frames.length < 2) return { edges: [], animations: [] };
|
|
514
550
|
logger.debug(
|
|
515
551
|
`Analyzing ${frames.length} frames in batches of ${OPENCV_BATCH_SIZE}`
|
|
516
552
|
);
|
|
517
553
|
const cvLib = await ensureOpenCV();
|
|
518
554
|
const edges = [];
|
|
519
|
-
const tracker = new IoUTracker(
|
|
555
|
+
const tracker = new IoUTracker(
|
|
556
|
+
ctx.options.fps,
|
|
557
|
+
ctx.options.iouThreshold,
|
|
558
|
+
ctx.options.animationThreshold
|
|
559
|
+
);
|
|
520
560
|
const scale = ctx.options.scale;
|
|
521
561
|
for (let i = 0; i < frames.length - 1; i += OPENCV_BATCH_SIZE) {
|
|
522
562
|
const batchEnd = Math.min(i + OPENCV_BATCH_SIZE + 1, frames.length);
|
|
@@ -529,8 +569,11 @@ async function analyzeFrames(ctx) {
|
|
|
529
569
|
);
|
|
530
570
|
ctx.emitProgress(progress);
|
|
531
571
|
}
|
|
532
|
-
|
|
533
|
-
|
|
572
|
+
const animations = tracker.flushAndGetAnimations();
|
|
573
|
+
logger.debug(
|
|
574
|
+
`Computed ${edges.length} score edges and ${animations.length} animations`
|
|
575
|
+
);
|
|
576
|
+
return { edges, animations };
|
|
534
577
|
}
|
|
535
578
|
|
|
536
579
|
// src/core/extractor.ts
|
|
@@ -577,7 +620,7 @@ function isSupportedFile(filePath, extensions) {
|
|
|
577
620
|
// src/core/extractor.ts
|
|
578
621
|
async function extractFrames(ctx) {
|
|
579
622
|
const framesDir = (0, import_node_path3.join)(ctx.workspacePath, "frames");
|
|
580
|
-
const { inputPath, fps, scale } = ctx.options;
|
|
623
|
+
const { inputPath, fps, maxFrames, scale } = ctx.options;
|
|
581
624
|
if (!inputPath) {
|
|
582
625
|
throw new Error("inputPath is required for frame extraction");
|
|
583
626
|
}
|
|
@@ -594,41 +637,23 @@ async function extractFrames(ctx) {
|
|
|
594
637
|
}
|
|
595
638
|
logger.debug(`Extracting frames from: ${inputPath}`);
|
|
596
639
|
await ensureDir(framesDir);
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
if (
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
`Insufficient I-frames (${frames.length}), falling back to FPS mode`
|
|
607
|
-
);
|
|
608
|
-
frames = await extractByFps(inputPath, framesDir, fps, scale);
|
|
609
|
-
}
|
|
640
|
+
let effectiveFps = fps;
|
|
641
|
+
const duration = await getVideoDuration(inputPath).catch(() => 0);
|
|
642
|
+
if (duration > 0) {
|
|
643
|
+
const fpsCap = maxFrames / duration;
|
|
644
|
+
effectiveFps = Math.min(fps, fpsCap);
|
|
645
|
+
effectiveFps = Math.max(0.5, effectiveFps);
|
|
646
|
+
logger.debug(
|
|
647
|
+
`Duration: ${duration.toFixed(1)}s, FPS: ${fps} \u2192 effective: ${effectiveFps.toFixed(2)} (maxFrames: ${maxFrames})`
|
|
648
|
+
);
|
|
610
649
|
}
|
|
650
|
+
const frames = await extractByFps(inputPath, framesDir, effectiveFps, scale);
|
|
611
651
|
ctx.emitProgress(100);
|
|
612
652
|
logger.debug(`Extracted ${frames.length} frames`);
|
|
613
653
|
return frames;
|
|
614
654
|
}
|
|
615
|
-
async function extractIFrames(inputPath, outputDir, scale) {
|
|
616
|
-
const outputPattern = (0, import_node_path3.join)(outputDir, "frame_%06d.jpg");
|
|
617
|
-
await (0, import_execa.execa)(import_ffmpeg_static.default, [
|
|
618
|
-
"-i",
|
|
619
|
-
inputPath,
|
|
620
|
-
"-vf",
|
|
621
|
-
`select='eq(pict_type,I)',scale=-1:${scale}`,
|
|
622
|
-
"-vsync",
|
|
623
|
-
"vfr",
|
|
624
|
-
"-q:v",
|
|
625
|
-
"2",
|
|
626
|
-
outputPattern
|
|
627
|
-
]);
|
|
628
|
-
return buildFrameList(outputDir, inputPath);
|
|
629
|
-
}
|
|
630
655
|
async function extractByFps(inputPath, outputDir, fps, scale) {
|
|
631
|
-
const outputPattern = (0, import_node_path3.join)(outputDir,
|
|
656
|
+
const outputPattern = (0, import_node_path3.join)(outputDir, FRAME_FILENAME_PATTERN);
|
|
632
657
|
await (0, import_execa.execa)(import_ffmpeg_static.default, [
|
|
633
658
|
"-i",
|
|
634
659
|
inputPath,
|
|
@@ -691,13 +716,44 @@ async function finalizeOutput(ctx, selectedFrames) {
|
|
|
691
716
|
const outputPath = ctx.options.outputPath;
|
|
692
717
|
const quality = ctx.options.quality;
|
|
693
718
|
const outputFiles = [];
|
|
719
|
+
const framesMetadata = [];
|
|
720
|
+
const totalFramesCount = ctx.frames.length;
|
|
721
|
+
const padding = Math.max(4, String(totalFramesCount).length);
|
|
694
722
|
for (let i = 0; i < selectedFrames.length; i++) {
|
|
695
723
|
const frame = selectedFrames[i];
|
|
696
|
-
const
|
|
697
|
-
const destPath = (0, import_node_path4.join)(stagingDir,
|
|
724
|
+
const fileName = `frame_${String(frame.id + 1).padStart(padding, "0")}.jpg`;
|
|
725
|
+
const destPath = (0, import_node_path4.join)(stagingDir, fileName);
|
|
698
726
|
await (0, import_sharp2.default)(frame.extractPath).jpeg({ quality, mozjpeg: true }).toFile(destPath);
|
|
699
|
-
outputFiles.push((0, import_node_path4.join)(outputPath,
|
|
727
|
+
outputFiles.push((0, import_node_path4.join)(outputPath, fileName));
|
|
728
|
+
framesMetadata.push({
|
|
729
|
+
step: i + 1,
|
|
730
|
+
fileName,
|
|
731
|
+
frameId: frame.id + 1,
|
|
732
|
+
timestampMs: Math.round(frame.timestamp * 1e3)
|
|
733
|
+
});
|
|
700
734
|
}
|
|
735
|
+
const metadata = {
|
|
736
|
+
video: {
|
|
737
|
+
originalDurationMs: Math.round(
|
|
738
|
+
(ctx.frames.length > 0 ? ctx.frames[ctx.frames.length - 1].timestamp : 0) * 1e3
|
|
739
|
+
),
|
|
740
|
+
fps: ctx.options.fps,
|
|
741
|
+
resolution: {
|
|
742
|
+
width: ctx.options.scale,
|
|
743
|
+
height: Math.round(ctx.options.scale * 9 / 16)
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
frames: framesMetadata,
|
|
747
|
+
animations: (ctx.animations || []).map((anim) => ({
|
|
748
|
+
...anim,
|
|
749
|
+
startFrameId: anim.startFrameId + 1,
|
|
750
|
+
endFrameId: anim.endFrameId + 1,
|
|
751
|
+
durationMs: Math.round(anim.durationMs)
|
|
752
|
+
}))
|
|
753
|
+
};
|
|
754
|
+
const metadataPath = (0, import_node_path4.join)(stagingDir, ".metadata.json");
|
|
755
|
+
await (0, import_promises3.writeFile)(metadataPath, JSON.stringify(metadata, null, 2));
|
|
756
|
+
outputFiles.push((0, import_node_path4.join)(outputPath, ".metadata.json"));
|
|
701
757
|
await ensureDir((0, import_node_path4.join)(outputPath, ".."));
|
|
702
758
|
await (0, import_promises3.rm)(outputPath, { recursive: true, force: true });
|
|
703
759
|
await (0, import_promises3.rename)(stagingDir, outputPath);
|
|
@@ -710,6 +766,7 @@ async function cleanupWorkspace(workspacePath) {
|
|
|
710
766
|
} catch {
|
|
711
767
|
}
|
|
712
768
|
}
|
|
769
|
+
var STALE_THRESHOLD_MS = 60 * 60 * 1e3;
|
|
713
770
|
async function writeInputBuffer(buffer, workspacePath) {
|
|
714
771
|
const inputDir = (0, import_node_path4.join)(workspacePath, "input");
|
|
715
772
|
await ensureDir(inputDir);
|
|
@@ -757,8 +814,11 @@ function resolveOptions(options) {
|
|
|
757
814
|
pruneMode,
|
|
758
815
|
outputPath,
|
|
759
816
|
fps: options.fps ?? DEFAULT_FPS,
|
|
817
|
+
maxFrames: options.maxFrames ?? DEFAULT_MAX_FRAMES,
|
|
760
818
|
scale: options.scale ?? DEFAULT_SCALE,
|
|
761
819
|
quality: options.quality ?? DEFAULT_QUALITY,
|
|
820
|
+
iouThreshold: options.iouThreshold ?? IOU_THRESHOLD,
|
|
821
|
+
animationThreshold: options.animationThreshold ?? ANIMATION_FRAME_THRESHOLD,
|
|
762
822
|
debug: options.debug ?? false
|
|
763
823
|
};
|
|
764
824
|
}
|
|
@@ -1029,7 +1089,9 @@ async function runPipeline(options) {
|
|
|
1029
1089
|
}
|
|
1030
1090
|
ctx.emitProgress(100);
|
|
1031
1091
|
ctx.status = "ANALYZING";
|
|
1032
|
-
|
|
1092
|
+
const { edges, animations } = await analyzeFrames(ctx);
|
|
1093
|
+
ctx.graph = edges;
|
|
1094
|
+
ctx.animations = animations;
|
|
1033
1095
|
ctx.status = "PRUNING";
|
|
1034
1096
|
const survivingIds = pruneByThresholdWithCap(
|
|
1035
1097
|
ctx.graph,
|
|
@@ -1062,6 +1124,15 @@ async function runPipeline(options) {
|
|
|
1062
1124
|
prunedFramesCount: prunedFrames.length,
|
|
1063
1125
|
outputFiles,
|
|
1064
1126
|
outputBuffers,
|
|
1127
|
+
animations: ctx.animations,
|
|
1128
|
+
video: {
|
|
1129
|
+
originalDurationMs: ctx.frames.length / ctx.options.fps * 1e3,
|
|
1130
|
+
fps: ctx.options.fps,
|
|
1131
|
+
resolution: {
|
|
1132
|
+
width: ctx.options.scale,
|
|
1133
|
+
height: Math.round(ctx.options.scale * 9 / 16)
|
|
1134
|
+
}
|
|
1135
|
+
},
|
|
1065
1136
|
executionTimeMs: Date.now() - startTime
|
|
1066
1137
|
};
|
|
1067
1138
|
} catch (error) {
|