@lumy-pack/scene-sieve 0.0.1 → 0.0.3
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 +230 -72
- package/dist/constants.d.ts +7 -1
- package/dist/core/analyzer.d.ts +26 -0
- package/dist/core/index.d.ts +2 -2
- package/dist/core/input-resolver.d.ts +1 -1
- package/dist/core/pruner.d.ts +27 -8
- package/dist/index.cjs +220 -69
- package/dist/index.mjs +220 -69
- package/dist/types/index.d.ts +2 -2
- package/dist/utils/paths.d.ts +10 -0
- package/package.json +2 -3
package/dist/index.cjs
CHANGED
|
@@ -55,7 +55,8 @@ var logger = {
|
|
|
55
55
|
console.log(`${import_picocolors.default.blue("info")} ${message}`);
|
|
56
56
|
},
|
|
57
57
|
success(message) {
|
|
58
|
-
console.log(
|
|
58
|
+
console.log(`
|
|
59
|
+
${import_picocolors.default.green("done")} ${message}`);
|
|
59
60
|
},
|
|
60
61
|
warn(message) {
|
|
61
62
|
console.warn(`${import_picocolors.default.yellow("warn")} ${message}`);
|
|
@@ -78,10 +79,12 @@ var import_sharp = __toESM(require("sharp"), 1);
|
|
|
78
79
|
var import_node_os = require("os");
|
|
79
80
|
var import_node_path = require("path");
|
|
80
81
|
var APP_NAME = "scene-sieve";
|
|
81
|
-
var DEFAULT_COUNT =
|
|
82
|
+
var DEFAULT_COUNT = 20;
|
|
83
|
+
var DEFAULT_THRESHOLD = 0.5;
|
|
82
84
|
var DEFAULT_FPS = 5;
|
|
83
85
|
var DEFAULT_SCALE = 720;
|
|
84
86
|
var DEFAULT_QUALITY = 80;
|
|
87
|
+
var NORMALIZATION_PERCENTILE = 0.9;
|
|
85
88
|
var WORKSPACE_PREFIX = `${APP_NAME}-`;
|
|
86
89
|
var TEMP_BASE_DIR = (0, import_node_os.tmpdir)();
|
|
87
90
|
var SUPPORTED_VIDEO_EXTENSIONS = [
|
|
@@ -101,6 +104,10 @@ var IOU_THRESHOLD = 0.9;
|
|
|
101
104
|
var DECAY_LAMBDA = 0.95;
|
|
102
105
|
var ANIMATION_FRAME_THRESHOLD = 5;
|
|
103
106
|
var MATCH_DISTANCE_THRESHOLD = 0.75;
|
|
107
|
+
var PIXELDIFF_GAUSSIAN_KERNEL = 3;
|
|
108
|
+
var PIXELDIFF_BINARY_THRESHOLD = 30;
|
|
109
|
+
var PIXELDIFF_CONTOUR_MIN_AREA = 100;
|
|
110
|
+
var PIXELDIFF_SAMPLE_SPACING = 8;
|
|
104
111
|
function getTempWorkspaceDir(sessionId) {
|
|
105
112
|
return (0, import_node_path.join)(TEMP_BASE_DIR, `${WORKSPACE_PREFIX}${sessionId}`);
|
|
106
113
|
}
|
|
@@ -360,6 +367,60 @@ async function computeAKAZEDiff(cvLib, frame1, frame2) {
|
|
|
360
367
|
if (matches) matches.delete();
|
|
361
368
|
}
|
|
362
369
|
}
|
|
370
|
+
function computePixelDiff(cvLib, frame1, frame2) {
|
|
371
|
+
const cv = cvLib;
|
|
372
|
+
const mat1 = new cv.Mat(frame1.height, frame1.width, cv.CV_8UC1);
|
|
373
|
+
const mat2 = new cv.Mat(frame2.height, frame2.width, cv.CV_8UC1);
|
|
374
|
+
const diff = new cv.Mat();
|
|
375
|
+
const blurred = new cv.Mat();
|
|
376
|
+
const binary = new cv.Mat();
|
|
377
|
+
const contours = new cv.MatVector();
|
|
378
|
+
const hierarchy = new cv.Mat();
|
|
379
|
+
try {
|
|
380
|
+
mat1.data.set(frame1.data);
|
|
381
|
+
mat2.data.set(frame2.data);
|
|
382
|
+
cv.absdiff(mat1, mat2, diff);
|
|
383
|
+
const ksize = new cv.Size(
|
|
384
|
+
PIXELDIFF_GAUSSIAN_KERNEL,
|
|
385
|
+
PIXELDIFF_GAUSSIAN_KERNEL
|
|
386
|
+
);
|
|
387
|
+
cv.GaussianBlur(diff, blurred, ksize, 0);
|
|
388
|
+
cv.threshold(
|
|
389
|
+
blurred,
|
|
390
|
+
binary,
|
|
391
|
+
PIXELDIFF_BINARY_THRESHOLD,
|
|
392
|
+
255,
|
|
393
|
+
cv.THRESH_BINARY
|
|
394
|
+
);
|
|
395
|
+
cv.findContours(
|
|
396
|
+
binary,
|
|
397
|
+
contours,
|
|
398
|
+
hierarchy,
|
|
399
|
+
cv.RETR_EXTERNAL,
|
|
400
|
+
cv.CHAIN_APPROX_SIMPLE
|
|
401
|
+
);
|
|
402
|
+
const points = [];
|
|
403
|
+
for (let c = 0; c < contours.size(); c++) {
|
|
404
|
+
const contour = contours.get(c);
|
|
405
|
+
const rect = cv.boundingRect(contour);
|
|
406
|
+
if (rect.width * rect.height < PIXELDIFF_CONTOUR_MIN_AREA) continue;
|
|
407
|
+
for (let y = rect.y; y < rect.y + rect.height; y += PIXELDIFF_SAMPLE_SPACING) {
|
|
408
|
+
for (let x = rect.x; x < rect.x + rect.width; x += PIXELDIFF_SAMPLE_SPACING) {
|
|
409
|
+
points.push({ x, y });
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return points;
|
|
414
|
+
} finally {
|
|
415
|
+
mat1.delete();
|
|
416
|
+
mat2.delete();
|
|
417
|
+
diff.delete();
|
|
418
|
+
blurred.delete();
|
|
419
|
+
binary.delete();
|
|
420
|
+
contours.delete();
|
|
421
|
+
hierarchy.delete();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
363
424
|
function computeInformationGain(clusters, clusterPoints, imageArea, animationIndices, animationWeights) {
|
|
364
425
|
if (clusters.length === 0) return 0;
|
|
365
426
|
let gain = 0;
|
|
@@ -394,8 +455,28 @@ async function analyzeBatch(cvLib, frames, scale, tracker, pairOffset) {
|
|
|
394
455
|
preprocessed[i],
|
|
395
456
|
preprocessed[i + 1]
|
|
396
457
|
);
|
|
397
|
-
|
|
398
|
-
|
|
458
|
+
let dbscanResult = dbscan(sNew, imageWidth, imageHeight);
|
|
459
|
+
let clusters = dbscanResult.boundingBoxes;
|
|
460
|
+
if (clusters.length === 0) {
|
|
461
|
+
const pixelDiffPoints = computePixelDiff(
|
|
462
|
+
cvLib,
|
|
463
|
+
preprocessed[i],
|
|
464
|
+
preprocessed[i + 1]
|
|
465
|
+
);
|
|
466
|
+
if (pixelDiffPoints.length > 0) {
|
|
467
|
+
logger.debug(
|
|
468
|
+
`Edge ${frames[i].id}->${frames[i + 1].id}: pixel-diff fallback (${pixelDiffPoints.length} points)`
|
|
469
|
+
);
|
|
470
|
+
dbscanResult = dbscan(
|
|
471
|
+
pixelDiffPoints,
|
|
472
|
+
imageWidth,
|
|
473
|
+
imageHeight,
|
|
474
|
+
void 0,
|
|
475
|
+
2
|
|
476
|
+
);
|
|
477
|
+
clusters = dbscanResult.boundingBoxes;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
399
480
|
const clusterPointCounts = new Array(clusters.length).fill(0);
|
|
400
481
|
for (const label of dbscanResult.labels) {
|
|
401
482
|
if (label >= 0) {
|
|
@@ -413,7 +494,9 @@ async function analyzeBatch(cvLib, frames, scale, tracker, pairOffset) {
|
|
|
413
494
|
animationIndices,
|
|
414
495
|
animationWeights
|
|
415
496
|
);
|
|
416
|
-
logger.debug(
|
|
497
|
+
logger.debug(
|
|
498
|
+
`Edge ${frames[i].id}->${frames[i + 1].id} G(t)=${score.toFixed(6)}`
|
|
499
|
+
);
|
|
417
500
|
edges.push({
|
|
418
501
|
sourceId: frames[i].id,
|
|
419
502
|
targetId: frames[i + 1].id,
|
|
@@ -458,12 +541,13 @@ async function analyzeFrames(ctx) {
|
|
|
458
541
|
// src/core/extractor.ts
|
|
459
542
|
var import_promises2 = require("fs/promises");
|
|
460
543
|
var import_node_path3 = require("path");
|
|
461
|
-
var import_fluent_ffmpeg = __toESM(require("fluent-ffmpeg"), 1);
|
|
462
|
-
var import_ffmpeg_static = __toESM(require("ffmpeg-static"), 1);
|
|
463
544
|
var import_ffprobe = require("@ffprobe-installer/ffprobe");
|
|
545
|
+
var import_execa = require("execa");
|
|
546
|
+
var import_ffmpeg_static = __toESM(require("ffmpeg-static"), 1);
|
|
464
547
|
|
|
465
548
|
// src/utils/paths.ts
|
|
466
549
|
var import_promises = require("fs/promises");
|
|
550
|
+
var import_node_os2 = require("os");
|
|
467
551
|
var import_node_path2 = require("path");
|
|
468
552
|
async function ensureDir(dirPath) {
|
|
469
553
|
await (0, import_promises.mkdir)(dirPath, { recursive: true });
|
|
@@ -476,6 +560,16 @@ async function fileExists(filePath) {
|
|
|
476
560
|
return false;
|
|
477
561
|
}
|
|
478
562
|
}
|
|
563
|
+
function expandTilde(p) {
|
|
564
|
+
if (p === "~") return (0, import_node_os2.homedir)();
|
|
565
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) {
|
|
566
|
+
return (0, import_node_path2.resolve)((0, import_node_os2.homedir)(), p.slice(2));
|
|
567
|
+
}
|
|
568
|
+
return p;
|
|
569
|
+
}
|
|
570
|
+
function resolveAbsolute(p) {
|
|
571
|
+
return (0, import_node_path2.resolve)(expandTilde(p));
|
|
572
|
+
}
|
|
479
573
|
function deriveOutputPath(inputPath) {
|
|
480
574
|
const dir = (0, import_node_path2.resolve)(inputPath, "..");
|
|
481
575
|
const name = (0, import_node_path2.basename)(inputPath, (0, import_node_path2.extname)(inputPath));
|
|
@@ -486,8 +580,6 @@ function isSupportedFile(filePath, extensions) {
|
|
|
486
580
|
}
|
|
487
581
|
|
|
488
582
|
// src/core/extractor.ts
|
|
489
|
-
if (import_ffmpeg_static.default) import_fluent_ffmpeg.default.setFfmpegPath(import_ffmpeg_static.default);
|
|
490
|
-
import_fluent_ffmpeg.default.setFfprobePath(import_ffprobe.path);
|
|
491
583
|
async function extractFrames(ctx) {
|
|
492
584
|
const framesDir = (0, import_node_path3.join)(ctx.workspacePath, "frames");
|
|
493
585
|
const { inputPath, fps, scale } = ctx.options;
|
|
@@ -498,7 +590,10 @@ async function extractFrames(ctx) {
|
|
|
498
590
|
if (!exists) {
|
|
499
591
|
throw new Error(`Input file not found: ${inputPath}`);
|
|
500
592
|
}
|
|
501
|
-
const allExtensions = [
|
|
593
|
+
const allExtensions = [
|
|
594
|
+
...SUPPORTED_VIDEO_EXTENSIONS,
|
|
595
|
+
...SUPPORTED_GIF_EXTENSIONS
|
|
596
|
+
];
|
|
502
597
|
if (!isSupportedFile(inputPath, allExtensions)) {
|
|
503
598
|
throw new Error(`Unsupported file format: ${inputPath}`);
|
|
504
599
|
}
|
|
@@ -524,35 +619,43 @@ async function extractFrames(ctx) {
|
|
|
524
619
|
}
|
|
525
620
|
async function extractIFrames(inputPath, outputDir, scale) {
|
|
526
621
|
const outputPattern = (0, import_node_path3.join)(outputDir, "frame_%06d.jpg");
|
|
527
|
-
await
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
622
|
+
await (0, import_execa.execa)(import_ffmpeg_static.default, [
|
|
623
|
+
"-i",
|
|
624
|
+
inputPath,
|
|
625
|
+
"-vf",
|
|
626
|
+
`select='eq(pict_type,I)',scale=-1:${scale}`,
|
|
627
|
+
"-vsync",
|
|
628
|
+
"vfr",
|
|
629
|
+
"-q:v",
|
|
630
|
+
"2",
|
|
631
|
+
outputPattern
|
|
632
|
+
]);
|
|
534
633
|
return buildFrameList(outputDir, inputPath);
|
|
535
634
|
}
|
|
536
635
|
async function extractByFps(inputPath, outputDir, fps, scale) {
|
|
537
636
|
const outputPattern = (0, import_node_path3.join)(outputDir, "frame_%06d.jpg");
|
|
538
|
-
await
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
637
|
+
await (0, import_execa.execa)(import_ffmpeg_static.default, [
|
|
638
|
+
"-i",
|
|
639
|
+
inputPath,
|
|
640
|
+
"-vf",
|
|
641
|
+
`fps=${fps},scale=-1:${scale}`,
|
|
642
|
+
"-q:v",
|
|
643
|
+
"2",
|
|
644
|
+
outputPattern
|
|
645
|
+
]);
|
|
544
646
|
return buildFrameList(outputDir, inputPath);
|
|
545
647
|
}
|
|
546
648
|
async function getVideoDuration(inputPath) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
649
|
+
const { stdout } = await (0, import_execa.execa)(import_ffprobe.path, [
|
|
650
|
+
"-v",
|
|
651
|
+
"quiet",
|
|
652
|
+
"-print_format",
|
|
653
|
+
"json",
|
|
654
|
+
"-show_format",
|
|
655
|
+
inputPath
|
|
656
|
+
]);
|
|
657
|
+
const metadata = JSON.parse(stdout);
|
|
658
|
+
return parseFloat(metadata.format?.duration ?? "0");
|
|
556
659
|
}
|
|
557
660
|
async function buildFrameList(framesDir, inputPath) {
|
|
558
661
|
const files = await (0, import_promises2.readdir)(framesDir);
|
|
@@ -564,7 +667,9 @@ async function buildFrameList(framesDir, inputPath) {
|
|
|
564
667
|
try {
|
|
565
668
|
duration = await getVideoDuration(inputPath);
|
|
566
669
|
} catch {
|
|
567
|
-
logger.debug(
|
|
670
|
+
logger.debug(
|
|
671
|
+
"Could not determine video duration; using frame index for timestamps"
|
|
672
|
+
);
|
|
568
673
|
}
|
|
569
674
|
return jpgFiles.map((file, index) => ({
|
|
570
675
|
id: index,
|
|
@@ -639,17 +744,15 @@ async function readFramesAsBuffers(frameNodes, quality) {
|
|
|
639
744
|
// src/core/input-resolver.ts
|
|
640
745
|
function resolveOptions(options) {
|
|
641
746
|
const mode = options.mode;
|
|
642
|
-
const inputPath = mode === "file" ? options.inputPath : void 0;
|
|
747
|
+
const inputPath = mode === "file" ? resolveAbsolute(options.inputPath) : void 0;
|
|
643
748
|
const outputPath = options.outputPath ?? (inputPath ? deriveOutputPath(inputPath) : (0, import_node_path5.join)(process.cwd(), "scene-sieve-output"));
|
|
644
|
-
const threshold = options.threshold;
|
|
645
|
-
if (threshold
|
|
749
|
+
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
750
|
+
if (threshold <= 0 || threshold > 1) {
|
|
646
751
|
throw new Error(
|
|
647
752
|
`threshold must be in range (0, 1], received: ${threshold}`
|
|
648
753
|
);
|
|
649
754
|
}
|
|
650
|
-
const
|
|
651
|
-
const hasExplicitCount = options.count !== void 0;
|
|
652
|
-
const pruneMode = hasThreshold && hasExplicitCount ? "threshold-with-cap" : hasThreshold ? "threshold" : "count";
|
|
755
|
+
const pruneMode = "threshold-with-cap";
|
|
653
756
|
return {
|
|
654
757
|
mode,
|
|
655
758
|
inputPath,
|
|
@@ -665,10 +768,16 @@ function resolveOptions(options) {
|
|
|
665
768
|
}
|
|
666
769
|
async function resolveInput(options, workspacePath) {
|
|
667
770
|
if (options.mode === "file") {
|
|
668
|
-
return {
|
|
771
|
+
return {
|
|
772
|
+
frames: [],
|
|
773
|
+
resolvedInputPath: resolveAbsolute(options.inputPath)
|
|
774
|
+
};
|
|
669
775
|
}
|
|
670
776
|
if (options.mode === "buffer") {
|
|
671
|
-
const resolvedInputPath = await writeInputBuffer(
|
|
777
|
+
const resolvedInputPath = await writeInputBuffer(
|
|
778
|
+
options.inputBuffer,
|
|
779
|
+
workspacePath
|
|
780
|
+
);
|
|
672
781
|
return { frames: [], resolvedInputPath };
|
|
673
782
|
}
|
|
674
783
|
if (options.mode === "frames") {
|
|
@@ -737,7 +846,11 @@ function pruneTo(graph, frames, targetCount) {
|
|
|
737
846
|
const heap = new MinHeap();
|
|
738
847
|
for (const edge of graph) {
|
|
739
848
|
edgeScore.set(`${edge.sourceId}:${edge.targetId}`, edge.score);
|
|
740
|
-
heap.push({
|
|
849
|
+
heap.push({
|
|
850
|
+
score: edge.score,
|
|
851
|
+
srcId: edge.sourceId,
|
|
852
|
+
tgtId: edge.targetId
|
|
853
|
+
});
|
|
741
854
|
}
|
|
742
855
|
const surviving = new Set(frames.map((f) => f.id));
|
|
743
856
|
const firstId = frames[0].id;
|
|
@@ -773,9 +886,55 @@ function normalizeScores(graph) {
|
|
|
773
886
|
const safeScores = graph.map(
|
|
774
887
|
(e) => Number.isFinite(e.score) && e.score > 0 ? e.score : 0
|
|
775
888
|
);
|
|
776
|
-
const
|
|
777
|
-
if (
|
|
778
|
-
|
|
889
|
+
const sorted = [...safeScores].filter((s) => s > 0).sort((a, b) => a - b);
|
|
890
|
+
if (sorted.length === 0) return safeScores;
|
|
891
|
+
const pIdx = Math.min(
|
|
892
|
+
Math.floor(sorted.length * NORMALIZATION_PERCENTILE),
|
|
893
|
+
sorted.length - 1
|
|
894
|
+
);
|
|
895
|
+
const refScore = sorted[pIdx];
|
|
896
|
+
return safeScores.map((s) => Math.min(s / refScore, 1));
|
|
897
|
+
}
|
|
898
|
+
function suppressConsecutiveRuns(graph, passingIndices, normalizedScores) {
|
|
899
|
+
const result = /* @__PURE__ */ new Set();
|
|
900
|
+
let runStart = 0;
|
|
901
|
+
while (runStart < passingIndices.length) {
|
|
902
|
+
let runEnd = runStart;
|
|
903
|
+
while (runEnd + 1 < passingIndices.length && passingIndices[runEnd + 1] === passingIndices[runEnd] + 1) {
|
|
904
|
+
runEnd++;
|
|
905
|
+
}
|
|
906
|
+
const runLen = runEnd - runStart + 1;
|
|
907
|
+
if (runLen === 1) {
|
|
908
|
+
result.add(graph[passingIndices[runStart]].targetId);
|
|
909
|
+
} else {
|
|
910
|
+
const peaks = [];
|
|
911
|
+
for (let j = runStart; j <= runEnd; j++) {
|
|
912
|
+
const idx = passingIndices[j];
|
|
913
|
+
const score = normalizedScores[idx];
|
|
914
|
+
const prevScore = j > runStart ? normalizedScores[passingIndices[j - 1]] : -Infinity;
|
|
915
|
+
const nextScore = j < runEnd ? normalizedScores[passingIndices[j + 1]] : -Infinity;
|
|
916
|
+
if (score > prevScore && score > nextScore) {
|
|
917
|
+
peaks.push(idx);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (peaks.length > 0) {
|
|
921
|
+
for (const peakIdx of peaks) {
|
|
922
|
+
result.add(graph[peakIdx].targetId);
|
|
923
|
+
}
|
|
924
|
+
} else {
|
|
925
|
+
let peakIdx = passingIndices[runStart];
|
|
926
|
+
for (let j = runStart + 1; j <= runEnd; j++) {
|
|
927
|
+
const idx = passingIndices[j];
|
|
928
|
+
if (normalizedScores[idx] > normalizedScores[peakIdx]) {
|
|
929
|
+
peakIdx = idx;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
result.add(graph[peakIdx].targetId);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
runStart = runEnd + 1;
|
|
936
|
+
}
|
|
937
|
+
return result;
|
|
779
938
|
}
|
|
780
939
|
function pruneByThreshold(graph, frames, threshold) {
|
|
781
940
|
if (frames.length === 0) return /* @__PURE__ */ new Set();
|
|
@@ -783,11 +942,16 @@ function pruneByThreshold(graph, frames, threshold) {
|
|
|
783
942
|
surviving.add(frames[0].id);
|
|
784
943
|
surviving.add(frames[frames.length - 1].id);
|
|
785
944
|
const normalized = normalizeScores(graph);
|
|
945
|
+
const passingIndices = [];
|
|
786
946
|
for (let i = 0; i < graph.length; i++) {
|
|
787
947
|
if (normalized[i] >= threshold) {
|
|
788
|
-
|
|
948
|
+
passingIndices.push(i);
|
|
789
949
|
}
|
|
790
950
|
}
|
|
951
|
+
const nmsTargets = suppressConsecutiveRuns(graph, passingIndices, normalized);
|
|
952
|
+
for (const id of nmsTargets) {
|
|
953
|
+
surviving.add(id);
|
|
954
|
+
}
|
|
791
955
|
return surviving;
|
|
792
956
|
}
|
|
793
957
|
function pruneByThresholdWithCap(graph, frames, threshold, maxCount) {
|
|
@@ -871,35 +1035,22 @@ async function runPipeline(options) {
|
|
|
871
1035
|
ctx.status = "ANALYZING";
|
|
872
1036
|
ctx.graph = await analyzeFrames(ctx);
|
|
873
1037
|
ctx.status = "PRUNING";
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
resolvedOptions.threshold,
|
|
881
|
-
resolvedOptions.count
|
|
882
|
-
);
|
|
883
|
-
break;
|
|
884
|
-
case "threshold":
|
|
885
|
-
survivingIds = pruneByThreshold(
|
|
886
|
-
ctx.graph,
|
|
887
|
-
ctx.frames,
|
|
888
|
-
resolvedOptions.threshold
|
|
889
|
-
);
|
|
890
|
-
break;
|
|
891
|
-
case "count":
|
|
892
|
-
default:
|
|
893
|
-
survivingIds = pruneTo(ctx.graph, ctx.frames, resolvedOptions.count);
|
|
894
|
-
break;
|
|
895
|
-
}
|
|
1038
|
+
const survivingIds = pruneByThresholdWithCap(
|
|
1039
|
+
ctx.graph,
|
|
1040
|
+
ctx.frames,
|
|
1041
|
+
resolvedOptions.threshold,
|
|
1042
|
+
resolvedOptions.count
|
|
1043
|
+
);
|
|
896
1044
|
const prunedFrames = ctx.frames.filter((f) => survivingIds.has(f.id));
|
|
897
1045
|
ctx.emitProgress(100);
|
|
898
1046
|
ctx.status = "FINALIZING";
|
|
899
1047
|
let outputFiles = [];
|
|
900
1048
|
let outputBuffers;
|
|
901
1049
|
if (resolvedOptions.mode === "buffer" || resolvedOptions.mode === "frames") {
|
|
902
|
-
outputBuffers = await readFramesAsBuffers(
|
|
1050
|
+
outputBuffers = await readFramesAsBuffers(
|
|
1051
|
+
prunedFrames,
|
|
1052
|
+
resolvedOptions.quality
|
|
1053
|
+
);
|
|
903
1054
|
ctx.emitProgress(100);
|
|
904
1055
|
} else {
|
|
905
1056
|
outputFiles = await finalizeOutput(ctx, prunedFrames);
|