@lumy-pack/scene-sieve 0.0.10 → 0.0.12
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.d.ts +1 -0
- package/dist/cli.mjs +73 -31
- package/dist/commands/Sieve.d.ts +19 -0
- package/dist/components/PhaseStep.d.ts +14 -0
- package/dist/components/ProgressBar.d.ts +7 -0
- package/dist/constants.d.ts +32 -0
- package/dist/core/analyzer.d.ts +62 -0
- package/dist/core/dbscan.d.ts +10 -0
- package/dist/core/extractor.d.ts +30 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/input-resolver.d.ts +13 -0
- package/dist/core/orchestrator.d.ts +2 -0
- package/dist/core/pipeline-worker.d.ts +1 -0
- package/dist/core/pruner.d.ts +61 -0
- package/dist/core/run-in-worker.d.ts +9 -0
- package/dist/core/segmenter.d.ts +38 -0
- package/dist/core/workspace.d.ts +25 -0
- package/dist/errors.d.ts +10 -0
- package/dist/index.cjs +65 -29
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +65 -29
- package/dist/pipeline-worker.mjs +65 -29
- package/dist/types/index.d.ts +129 -0
- package/dist/utils/command-registry.d.ts +24 -0
- package/dist/utils/concurrency.d.ts +5 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/math.d.ts +27 -0
- package/dist/utils/min-heap.d.ts +16 -0
- package/dist/utils/parse-options.d.ts +21 -0
- package/dist/utils/paths.d.ts +18 -0
- package/package.json +4 -2
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const SieveErrorCode: {
|
|
2
|
+
readonly INVALID_INPUT: "INVALID_INPUT";
|
|
3
|
+
readonly FILE_NOT_FOUND: "FILE_NOT_FOUND";
|
|
4
|
+
readonly INVALID_FORMAT: "INVALID_FORMAT";
|
|
5
|
+
readonly PIPELINE_ERROR: "PIPELINE_ERROR";
|
|
6
|
+
readonly WORKER_ERROR: "WORKER_ERROR";
|
|
7
|
+
readonly UNKNOWN: "UNKNOWN";
|
|
8
|
+
};
|
|
9
|
+
export type SieveErrorCode = (typeof SieveErrorCode)[keyof typeof SieveErrorCode];
|
|
10
|
+
export declare function classifyError(error: Error): SieveErrorCode;
|
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
40
40
|
|
|
41
41
|
// src/core/orchestrator.ts
|
|
42
42
|
var import_node_crypto2 = require("crypto");
|
|
43
|
+
var import_common_utils7 = require("@winglet/common-utils");
|
|
43
44
|
|
|
44
45
|
// src/utils/logger.ts
|
|
45
46
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
@@ -73,6 +74,7 @@ ${import_picocolors.default.green("done")} ${message}`);
|
|
|
73
74
|
|
|
74
75
|
// src/core/analyzer.ts
|
|
75
76
|
var import_node_module = require("module");
|
|
77
|
+
var import_common_utils = require("@winglet/common-utils");
|
|
76
78
|
var import_sharp = __toESM(require("sharp"), 1);
|
|
77
79
|
|
|
78
80
|
// src/constants.ts
|
|
@@ -289,7 +291,8 @@ var IoUTracker = class {
|
|
|
289
291
|
this.collectAnimation(region);
|
|
290
292
|
}
|
|
291
293
|
}
|
|
292
|
-
this.regions =
|
|
294
|
+
this.regions = (0, import_common_utils.filter)(
|
|
295
|
+
this.regions,
|
|
293
296
|
(r, i) => r.weight > 0.01 || matched.has(i)
|
|
294
297
|
);
|
|
295
298
|
return animationIndices;
|
|
@@ -470,7 +473,7 @@ function computeInformationGain(clusters, clusterPoints, imageArea, animationInd
|
|
|
470
473
|
async function analyzeBatch(cvLib, frames, scale, tracker, pairOffset) {
|
|
471
474
|
const edges = [];
|
|
472
475
|
const preprocessed = await Promise.all(
|
|
473
|
-
|
|
476
|
+
(0, import_common_utils.map)(frames, (f) => preprocessFrame(f.extractPath, scale))
|
|
474
477
|
);
|
|
475
478
|
const imageWidth = preprocessed[0]?.width ?? scale;
|
|
476
479
|
const imageHeight = preprocessed[0]?.height ?? Math.round(scale * 9 / 16);
|
|
@@ -512,7 +515,8 @@ async function analyzeBatch(cvLib, frames, scale, tracker, pairOffset) {
|
|
|
512
515
|
}
|
|
513
516
|
}
|
|
514
517
|
const animationIndices = tracker.update(clusters, pairIndex);
|
|
515
|
-
const animationWeights =
|
|
518
|
+
const animationWeights = (0, import_common_utils.map)(
|
|
519
|
+
clusters,
|
|
516
520
|
(_, ci) => animationIndices.has(ci) ? tracker.getAnimationWeight(ci, clusters) : 0
|
|
517
521
|
);
|
|
518
522
|
const score = computeInformationGain(
|
|
@@ -577,6 +581,7 @@ async function analyzeFrames(ctx) {
|
|
|
577
581
|
var import_promises2 = require("fs/promises");
|
|
578
582
|
var import_node_path3 = require("path");
|
|
579
583
|
var import_ffprobe = require("@ffprobe-installer/ffprobe");
|
|
584
|
+
var import_common_utils2 = require("@winglet/common-utils");
|
|
580
585
|
var import_execa = require("execa");
|
|
581
586
|
var import_ffmpeg_static = __toESM(require("ffmpeg-static"), 1);
|
|
582
587
|
|
|
@@ -688,11 +693,11 @@ async function getVideoMetadata(inputPath) {
|
|
|
688
693
|
}
|
|
689
694
|
async function buildFrameList(framesDir, duration) {
|
|
690
695
|
const files = await (0, import_promises2.readdir)(framesDir);
|
|
691
|
-
const jpgFiles =
|
|
696
|
+
const jpgFiles = (0, import_common_utils2.filter)(files, (f) => f.endsWith(".jpg")).sort();
|
|
692
697
|
if (jpgFiles.length === 0) {
|
|
693
698
|
return [];
|
|
694
699
|
}
|
|
695
|
-
return
|
|
700
|
+
return (0, import_common_utils2.map)(jpgFiles, (file, index) => ({
|
|
696
701
|
id: index,
|
|
697
702
|
timestamp: duration > 0 && jpgFiles.length > 1 ? duration * index / (jpgFiles.length - 1) : index,
|
|
698
703
|
extractPath: (0, import_node_path3.join)(framesDir, file)
|
|
@@ -722,6 +727,7 @@ var import_node_path5 = require("path");
|
|
|
722
727
|
// src/core/workspace.ts
|
|
723
728
|
var import_promises3 = require("fs/promises");
|
|
724
729
|
var import_node_path4 = require("path");
|
|
730
|
+
var import_common_utils3 = require("@winglet/common-utils");
|
|
725
731
|
var import_sharp2 = __toESM(require("sharp"), 1);
|
|
726
732
|
async function createWorkspace(sessionId) {
|
|
727
733
|
const workspacePath = getTempWorkspaceDir(sessionId);
|
|
@@ -762,7 +768,7 @@ async function finalizeOutput(ctx, selectedFrames) {
|
|
|
762
768
|
}
|
|
763
769
|
},
|
|
764
770
|
frames: framesMetadata,
|
|
765
|
-
animations: (ctx.animations || []
|
|
771
|
+
animations: (0, import_common_utils3.map)(ctx.animations || [], (anim) => ({
|
|
766
772
|
...anim,
|
|
767
773
|
startFrameId: anim.startFrameId + 1,
|
|
768
774
|
endFrameId: anim.endFrameId + 1,
|
|
@@ -815,7 +821,8 @@ async function writeInputFrames(frames, workspacePath) {
|
|
|
815
821
|
}
|
|
816
822
|
async function readFramesAsBuffers(frameNodes, quality) {
|
|
817
823
|
return Promise.all(
|
|
818
|
-
|
|
824
|
+
(0, import_common_utils3.map)(
|
|
825
|
+
frameNodes,
|
|
819
826
|
(f) => (0, import_sharp2.default)(f.extractPath).jpeg({ quality, mozjpeg: true }).toBuffer()
|
|
820
827
|
)
|
|
821
828
|
);
|
|
@@ -872,39 +879,46 @@ async function resolveInput(options, workspacePath) {
|
|
|
872
879
|
throw new Error(`Unsupported input mode: ${options.mode}`);
|
|
873
880
|
}
|
|
874
881
|
|
|
882
|
+
// src/core/pruner.ts
|
|
883
|
+
var import_common_utils5 = require("@winglet/common-utils");
|
|
884
|
+
|
|
875
885
|
// src/utils/math.ts
|
|
886
|
+
var import_common_utils4 = require("@winglet/common-utils");
|
|
876
887
|
function normalizeScores(items) {
|
|
877
888
|
if (items.length === 0) return [];
|
|
878
|
-
const safeScores =
|
|
889
|
+
const safeScores = (0, import_common_utils4.map)(
|
|
890
|
+
items,
|
|
879
891
|
(e) => Number.isFinite(e.score) && e.score > 0 ? e.score : 0
|
|
880
892
|
);
|
|
881
|
-
const positiveScores =
|
|
893
|
+
const positiveScores = (0, import_common_utils4.filter)(safeScores, (s) => s > 0);
|
|
882
894
|
if (positiveScores.length === 0) return safeScores;
|
|
883
895
|
const sorted = [...positiveScores].sort((a, b) => a - b);
|
|
884
896
|
if (positiveScores.length <= NORMALIZATION_MIN_SAMPLE_SIZE) {
|
|
885
897
|
const min = sorted[0];
|
|
886
898
|
const max = sorted[sorted.length - 1];
|
|
887
|
-
if (max === min) return
|
|
888
|
-
return
|
|
899
|
+
if (max === min) return (0, import_common_utils4.map)(safeScores, (s) => s > 0 ? 1 : 0);
|
|
900
|
+
return (0, import_common_utils4.map)(
|
|
901
|
+
safeScores,
|
|
889
902
|
(s) => s <= 0 ? 0 : Math.max(0, Math.min((s - min) / (max - min), 1))
|
|
890
903
|
);
|
|
891
904
|
}
|
|
892
905
|
const median = sorted[Math.floor(sorted.length / 2)];
|
|
893
|
-
const absoluteDiffs =
|
|
906
|
+
const absoluteDiffs = (0, import_common_utils4.map)(positiveScores, (v) => Math.abs(v - median));
|
|
894
907
|
const mad = [...absoluteDiffs].sort((a, b) => a - b)[Math.floor(absoluteDiffs.length / 2)];
|
|
895
908
|
const scale = mad === 0 ? median : mad * NORMALIZATION_MAD_COEFFICIENT;
|
|
896
|
-
const logisticZ =
|
|
909
|
+
const logisticZ = (0, import_common_utils4.map)(safeScores, (s) => {
|
|
897
910
|
if (s <= 0) return 0;
|
|
898
911
|
if (scale === 0) return 1;
|
|
899
912
|
const z = (s - median) / scale;
|
|
900
913
|
return 1 / (1 + Math.exp(-NORMALIZATION_LOGISTIC_K * z));
|
|
901
914
|
});
|
|
902
|
-
const cdf =
|
|
915
|
+
const cdf = (0, import_common_utils4.map)(safeScores, (s) => {
|
|
903
916
|
if (s <= 0) return 0;
|
|
904
917
|
const rank = sorted.findIndex((v) => v >= s);
|
|
905
918
|
return rank / sorted.length;
|
|
906
919
|
});
|
|
907
|
-
return
|
|
920
|
+
return (0, import_common_utils4.map)(
|
|
921
|
+
logisticZ,
|
|
908
922
|
(z, i) => z * (1 - NORMALIZATION_ALPHA) + cdf[i] * NORMALIZATION_ALPHA
|
|
909
923
|
);
|
|
910
924
|
}
|
|
@@ -956,7 +970,7 @@ var MinHeap = class {
|
|
|
956
970
|
// src/core/pruner.ts
|
|
957
971
|
function pruneTo(graph, frames, targetCount) {
|
|
958
972
|
if (frames.length <= targetCount) {
|
|
959
|
-
return new Set(
|
|
973
|
+
return new Set((0, import_common_utils5.map)(frames, (f) => f.id));
|
|
960
974
|
}
|
|
961
975
|
const prev = /* @__PURE__ */ new Map();
|
|
962
976
|
const next = /* @__PURE__ */ new Map();
|
|
@@ -974,7 +988,7 @@ function pruneTo(graph, frames, targetCount) {
|
|
|
974
988
|
tgtId: edge.targetId
|
|
975
989
|
});
|
|
976
990
|
}
|
|
977
|
-
const surviving = new Set(
|
|
991
|
+
const surviving = new Set((0, import_common_utils5.map)(frames, (f) => f.id));
|
|
978
992
|
const firstId = frames[0].id;
|
|
979
993
|
const lastId = frames[frames.length - 1].id;
|
|
980
994
|
while (surviving.size > targetCount && heap.size > 0) {
|
|
@@ -1067,7 +1081,7 @@ function pruneByThresholdWithCap(graph, frames, threshold, maxCount) {
|
|
|
1067
1081
|
if (thresholdSurvivors.size <= maxCount) {
|
|
1068
1082
|
return thresholdSurvivors;
|
|
1069
1083
|
}
|
|
1070
|
-
const survivingFrames =
|
|
1084
|
+
const survivingFrames = (0, import_common_utils5.filter)(frames, (f) => thresholdSurvivors.has(f.id));
|
|
1071
1085
|
const idToOrigIdx = /* @__PURE__ */ new Map();
|
|
1072
1086
|
for (let i = 0; i < frames.length; i++) {
|
|
1073
1087
|
idToOrigIdx.set(frames[i].id, i);
|
|
@@ -1103,6 +1117,7 @@ function pruneByThresholdWithCap(graph, frames, threshold, maxCount) {
|
|
|
1103
1117
|
// src/core/segmenter.ts
|
|
1104
1118
|
var import_node_crypto = require("crypto");
|
|
1105
1119
|
var import_node_path6 = require("path");
|
|
1120
|
+
var import_common_utils6 = require("@winglet/common-utils");
|
|
1106
1121
|
|
|
1107
1122
|
// src/utils/concurrency.ts
|
|
1108
1123
|
function concurrencyLimit(limit) {
|
|
@@ -1241,18 +1256,30 @@ function remapEdges(segmentResults, globalIdMap) {
|
|
|
1241
1256
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
1242
1257
|
for (const result of segmentResults) {
|
|
1243
1258
|
for (const edge of result.edges) {
|
|
1244
|
-
const newSourceId = globalIdMap.get(
|
|
1245
|
-
|
|
1259
|
+
const newSourceId = globalIdMap.get(
|
|
1260
|
+
`${result.segment.index}:${edge.sourceId}`
|
|
1261
|
+
);
|
|
1262
|
+
const newTargetId = globalIdMap.get(
|
|
1263
|
+
`${result.segment.index}:${edge.targetId}`
|
|
1264
|
+
);
|
|
1246
1265
|
if (newSourceId === void 0 || newTargetId === void 0) continue;
|
|
1247
1266
|
const edgeKey = `${newSourceId}-${newTargetId}`;
|
|
1248
1267
|
const existingIdx = edgeMap.get(edgeKey);
|
|
1249
1268
|
if (existingIdx !== void 0) {
|
|
1250
1269
|
if (edges[existingIdx].score < edge.score) {
|
|
1251
|
-
edges[existingIdx] = {
|
|
1270
|
+
edges[existingIdx] = {
|
|
1271
|
+
sourceId: newSourceId,
|
|
1272
|
+
targetId: newTargetId,
|
|
1273
|
+
score: edge.score
|
|
1274
|
+
};
|
|
1252
1275
|
}
|
|
1253
1276
|
} else {
|
|
1254
1277
|
edgeMap.set(edgeKey, edges.length);
|
|
1255
|
-
edges.push({
|
|
1278
|
+
edges.push({
|
|
1279
|
+
sourceId: newSourceId,
|
|
1280
|
+
targetId: newTargetId,
|
|
1281
|
+
score: edge.score
|
|
1282
|
+
});
|
|
1256
1283
|
}
|
|
1257
1284
|
}
|
|
1258
1285
|
}
|
|
@@ -1262,10 +1289,18 @@ function remapAnimations(segmentResults, globalIdMap) {
|
|
|
1262
1289
|
const animations = [];
|
|
1263
1290
|
for (const result of segmentResults) {
|
|
1264
1291
|
for (const anim of result.animations) {
|
|
1265
|
-
const newStartId = globalIdMap.get(
|
|
1266
|
-
|
|
1292
|
+
const newStartId = globalIdMap.get(
|
|
1293
|
+
`${result.segment.index}:${anim.startFrameId}`
|
|
1294
|
+
);
|
|
1295
|
+
const newEndId = globalIdMap.get(
|
|
1296
|
+
`${result.segment.index}:${anim.endFrameId}`
|
|
1297
|
+
);
|
|
1267
1298
|
if (newStartId === void 0 || newEndId === void 0) continue;
|
|
1268
|
-
animations.push({
|
|
1299
|
+
animations.push({
|
|
1300
|
+
...anim,
|
|
1301
|
+
startFrameId: newStartId,
|
|
1302
|
+
endFrameId: newEndId
|
|
1303
|
+
});
|
|
1269
1304
|
}
|
|
1270
1305
|
}
|
|
1271
1306
|
return animations;
|
|
@@ -1346,7 +1381,7 @@ async function runSegmentedPipeline(options, resolvedOptions) {
|
|
|
1346
1381
|
logger.debug(`Segment plan: ${segments.length} segments`);
|
|
1347
1382
|
const limit = concurrencyLimit(resolvedOptions.concurrency);
|
|
1348
1383
|
const segmentProgresses = new Array(segments.length).fill(0);
|
|
1349
|
-
const weights =
|
|
1384
|
+
const weights = (0, import_common_utils6.map)(segments, (s) => s.duration / totalDuration);
|
|
1350
1385
|
const emitOverallProgress = (phase) => {
|
|
1351
1386
|
if (!options.onProgress) return;
|
|
1352
1387
|
const overall = weights.reduce(
|
|
@@ -1357,7 +1392,8 @@ async function runSegmentedPipeline(options, resolvedOptions) {
|
|
|
1357
1392
|
};
|
|
1358
1393
|
options.onProgress?.("EXTRACTING", 0);
|
|
1359
1394
|
const results = await Promise.all(
|
|
1360
|
-
|
|
1395
|
+
(0, import_common_utils6.map)(
|
|
1396
|
+
segments,
|
|
1361
1397
|
(segment) => limit(async () => {
|
|
1362
1398
|
const segWorkspace = await createSegmentWorkspace(
|
|
1363
1399
|
mainWorkspace,
|
|
@@ -1389,7 +1425,7 @@ async function runSegmentedPipeline(options, resolvedOptions) {
|
|
|
1389
1425
|
resolvedOptions.threshold,
|
|
1390
1426
|
resolvedOptions.count
|
|
1391
1427
|
);
|
|
1392
|
-
const prunedFrames =
|
|
1428
|
+
const prunedFrames = (0, import_common_utils6.filter)(frames, (f) => survivingIds.has(f.id));
|
|
1393
1429
|
options.onProgress?.("PRUNING", 100);
|
|
1394
1430
|
options.onProgress?.("FINALIZING", 0);
|
|
1395
1431
|
const ctx = {
|
|
@@ -1499,7 +1535,7 @@ async function runPipeline(options) {
|
|
|
1499
1535
|
resolvedOptions.threshold,
|
|
1500
1536
|
resolvedOptions.count
|
|
1501
1537
|
);
|
|
1502
|
-
const prunedFrames = ctx.frames
|
|
1538
|
+
const prunedFrames = (0, import_common_utils7.filter)(ctx.frames, (f) => survivingIds.has(f.id));
|
|
1503
1539
|
ctx.emitProgress(100);
|
|
1504
1540
|
ctx.status = "FINALIZING";
|
|
1505
1541
|
let outputFiles = [];
|
package/dist/index.d.ts
ADDED
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/core/orchestrator.ts
|
|
2
2
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3
|
+
import { filter as filter6 } from "@winglet/common-utils";
|
|
3
4
|
|
|
4
5
|
// src/utils/logger.ts
|
|
5
6
|
import pc from "picocolors";
|
|
@@ -33,6 +34,7 @@ ${pc.green("done")} ${message}`);
|
|
|
33
34
|
|
|
34
35
|
// src/core/analyzer.ts
|
|
35
36
|
import { createRequire } from "module";
|
|
37
|
+
import { filter, map } from "@winglet/common-utils";
|
|
36
38
|
import sharp from "sharp";
|
|
37
39
|
|
|
38
40
|
// src/constants.ts
|
|
@@ -249,7 +251,8 @@ var IoUTracker = class {
|
|
|
249
251
|
this.collectAnimation(region);
|
|
250
252
|
}
|
|
251
253
|
}
|
|
252
|
-
this.regions =
|
|
254
|
+
this.regions = filter(
|
|
255
|
+
this.regions,
|
|
253
256
|
(r, i) => r.weight > 0.01 || matched.has(i)
|
|
254
257
|
);
|
|
255
258
|
return animationIndices;
|
|
@@ -430,7 +433,7 @@ function computeInformationGain(clusters, clusterPoints, imageArea, animationInd
|
|
|
430
433
|
async function analyzeBatch(cvLib, frames, scale, tracker, pairOffset) {
|
|
431
434
|
const edges = [];
|
|
432
435
|
const preprocessed = await Promise.all(
|
|
433
|
-
|
|
436
|
+
map(frames, (f) => preprocessFrame(f.extractPath, scale))
|
|
434
437
|
);
|
|
435
438
|
const imageWidth = preprocessed[0]?.width ?? scale;
|
|
436
439
|
const imageHeight = preprocessed[0]?.height ?? Math.round(scale * 9 / 16);
|
|
@@ -472,7 +475,8 @@ async function analyzeBatch(cvLib, frames, scale, tracker, pairOffset) {
|
|
|
472
475
|
}
|
|
473
476
|
}
|
|
474
477
|
const animationIndices = tracker.update(clusters, pairIndex);
|
|
475
|
-
const animationWeights =
|
|
478
|
+
const animationWeights = map(
|
|
479
|
+
clusters,
|
|
476
480
|
(_, ci) => animationIndices.has(ci) ? tracker.getAnimationWeight(ci, clusters) : 0
|
|
477
481
|
);
|
|
478
482
|
const score = computeInformationGain(
|
|
@@ -537,6 +541,7 @@ async function analyzeFrames(ctx) {
|
|
|
537
541
|
import { readdir } from "fs/promises";
|
|
538
542
|
import { join as join2 } from "path";
|
|
539
543
|
import { path as ffprobePath } from "@ffprobe-installer/ffprobe";
|
|
544
|
+
import { filter as filter2, map as map2 } from "@winglet/common-utils";
|
|
540
545
|
import { execa } from "execa";
|
|
541
546
|
import ffmpegPath from "ffmpeg-static";
|
|
542
547
|
|
|
@@ -648,11 +653,11 @@ async function getVideoMetadata(inputPath) {
|
|
|
648
653
|
}
|
|
649
654
|
async function buildFrameList(framesDir, duration) {
|
|
650
655
|
const files = await readdir(framesDir);
|
|
651
|
-
const jpgFiles = files
|
|
656
|
+
const jpgFiles = filter2(files, (f) => f.endsWith(".jpg")).sort();
|
|
652
657
|
if (jpgFiles.length === 0) {
|
|
653
658
|
return [];
|
|
654
659
|
}
|
|
655
|
-
return jpgFiles
|
|
660
|
+
return map2(jpgFiles, (file, index) => ({
|
|
656
661
|
id: index,
|
|
657
662
|
timestamp: duration > 0 && jpgFiles.length > 1 ? duration * index / (jpgFiles.length - 1) : index,
|
|
658
663
|
extractPath: join2(framesDir, file)
|
|
@@ -682,6 +687,7 @@ import { join as join4 } from "path";
|
|
|
682
687
|
// src/core/workspace.ts
|
|
683
688
|
import { readdir as readdir2, rename, rm, stat as stat2, writeFile } from "fs/promises";
|
|
684
689
|
import { join as join3 } from "path";
|
|
690
|
+
import { map as map3 } from "@winglet/common-utils";
|
|
685
691
|
import sharp2 from "sharp";
|
|
686
692
|
async function createWorkspace(sessionId) {
|
|
687
693
|
const workspacePath = getTempWorkspaceDir(sessionId);
|
|
@@ -722,7 +728,7 @@ async function finalizeOutput(ctx, selectedFrames) {
|
|
|
722
728
|
}
|
|
723
729
|
},
|
|
724
730
|
frames: framesMetadata,
|
|
725
|
-
animations: (ctx.animations || []
|
|
731
|
+
animations: map3(ctx.animations || [], (anim) => ({
|
|
726
732
|
...anim,
|
|
727
733
|
startFrameId: anim.startFrameId + 1,
|
|
728
734
|
endFrameId: anim.endFrameId + 1,
|
|
@@ -775,7 +781,8 @@ async function writeInputFrames(frames, workspacePath) {
|
|
|
775
781
|
}
|
|
776
782
|
async function readFramesAsBuffers(frameNodes, quality) {
|
|
777
783
|
return Promise.all(
|
|
778
|
-
|
|
784
|
+
map3(
|
|
785
|
+
frameNodes,
|
|
779
786
|
(f) => sharp2(f.extractPath).jpeg({ quality, mozjpeg: true }).toBuffer()
|
|
780
787
|
)
|
|
781
788
|
);
|
|
@@ -832,39 +839,46 @@ async function resolveInput(options, workspacePath) {
|
|
|
832
839
|
throw new Error(`Unsupported input mode: ${options.mode}`);
|
|
833
840
|
}
|
|
834
841
|
|
|
842
|
+
// src/core/pruner.ts
|
|
843
|
+
import { filter as filter4, map as map5 } from "@winglet/common-utils";
|
|
844
|
+
|
|
835
845
|
// src/utils/math.ts
|
|
846
|
+
import { filter as filter3, map as map4 } from "@winglet/common-utils";
|
|
836
847
|
function normalizeScores(items) {
|
|
837
848
|
if (items.length === 0) return [];
|
|
838
|
-
const safeScores =
|
|
849
|
+
const safeScores = map4(
|
|
850
|
+
items,
|
|
839
851
|
(e) => Number.isFinite(e.score) && e.score > 0 ? e.score : 0
|
|
840
852
|
);
|
|
841
|
-
const positiveScores = safeScores
|
|
853
|
+
const positiveScores = filter3(safeScores, (s) => s > 0);
|
|
842
854
|
if (positiveScores.length === 0) return safeScores;
|
|
843
855
|
const sorted = [...positiveScores].sort((a, b) => a - b);
|
|
844
856
|
if (positiveScores.length <= NORMALIZATION_MIN_SAMPLE_SIZE) {
|
|
845
857
|
const min = sorted[0];
|
|
846
858
|
const max = sorted[sorted.length - 1];
|
|
847
|
-
if (max === min) return safeScores
|
|
848
|
-
return
|
|
859
|
+
if (max === min) return map4(safeScores, (s) => s > 0 ? 1 : 0);
|
|
860
|
+
return map4(
|
|
861
|
+
safeScores,
|
|
849
862
|
(s) => s <= 0 ? 0 : Math.max(0, Math.min((s - min) / (max - min), 1))
|
|
850
863
|
);
|
|
851
864
|
}
|
|
852
865
|
const median = sorted[Math.floor(sorted.length / 2)];
|
|
853
|
-
const absoluteDiffs = positiveScores
|
|
866
|
+
const absoluteDiffs = map4(positiveScores, (v) => Math.abs(v - median));
|
|
854
867
|
const mad = [...absoluteDiffs].sort((a, b) => a - b)[Math.floor(absoluteDiffs.length / 2)];
|
|
855
868
|
const scale = mad === 0 ? median : mad * NORMALIZATION_MAD_COEFFICIENT;
|
|
856
|
-
const logisticZ = safeScores
|
|
869
|
+
const logisticZ = map4(safeScores, (s) => {
|
|
857
870
|
if (s <= 0) return 0;
|
|
858
871
|
if (scale === 0) return 1;
|
|
859
872
|
const z = (s - median) / scale;
|
|
860
873
|
return 1 / (1 + Math.exp(-NORMALIZATION_LOGISTIC_K * z));
|
|
861
874
|
});
|
|
862
|
-
const cdf = safeScores
|
|
875
|
+
const cdf = map4(safeScores, (s) => {
|
|
863
876
|
if (s <= 0) return 0;
|
|
864
877
|
const rank = sorted.findIndex((v) => v >= s);
|
|
865
878
|
return rank / sorted.length;
|
|
866
879
|
});
|
|
867
|
-
return
|
|
880
|
+
return map4(
|
|
881
|
+
logisticZ,
|
|
868
882
|
(z, i) => z * (1 - NORMALIZATION_ALPHA) + cdf[i] * NORMALIZATION_ALPHA
|
|
869
883
|
);
|
|
870
884
|
}
|
|
@@ -916,7 +930,7 @@ var MinHeap = class {
|
|
|
916
930
|
// src/core/pruner.ts
|
|
917
931
|
function pruneTo(graph, frames, targetCount) {
|
|
918
932
|
if (frames.length <= targetCount) {
|
|
919
|
-
return new Set(frames
|
|
933
|
+
return new Set(map5(frames, (f) => f.id));
|
|
920
934
|
}
|
|
921
935
|
const prev = /* @__PURE__ */ new Map();
|
|
922
936
|
const next = /* @__PURE__ */ new Map();
|
|
@@ -934,7 +948,7 @@ function pruneTo(graph, frames, targetCount) {
|
|
|
934
948
|
tgtId: edge.targetId
|
|
935
949
|
});
|
|
936
950
|
}
|
|
937
|
-
const surviving = new Set(frames
|
|
951
|
+
const surviving = new Set(map5(frames, (f) => f.id));
|
|
938
952
|
const firstId = frames[0].id;
|
|
939
953
|
const lastId = frames[frames.length - 1].id;
|
|
940
954
|
while (surviving.size > targetCount && heap.size > 0) {
|
|
@@ -1027,7 +1041,7 @@ function pruneByThresholdWithCap(graph, frames, threshold, maxCount) {
|
|
|
1027
1041
|
if (thresholdSurvivors.size <= maxCount) {
|
|
1028
1042
|
return thresholdSurvivors;
|
|
1029
1043
|
}
|
|
1030
|
-
const survivingFrames = frames
|
|
1044
|
+
const survivingFrames = filter4(frames, (f) => thresholdSurvivors.has(f.id));
|
|
1031
1045
|
const idToOrigIdx = /* @__PURE__ */ new Map();
|
|
1032
1046
|
for (let i = 0; i < frames.length; i++) {
|
|
1033
1047
|
idToOrigIdx.set(frames[i].id, i);
|
|
@@ -1063,6 +1077,7 @@ function pruneByThresholdWithCap(graph, frames, threshold, maxCount) {
|
|
|
1063
1077
|
// src/core/segmenter.ts
|
|
1064
1078
|
import { randomUUID } from "crypto";
|
|
1065
1079
|
import { join as join5 } from "path";
|
|
1080
|
+
import { filter as filter5, map as map6 } from "@winglet/common-utils";
|
|
1066
1081
|
|
|
1067
1082
|
// src/utils/concurrency.ts
|
|
1068
1083
|
function concurrencyLimit(limit) {
|
|
@@ -1201,18 +1216,30 @@ function remapEdges(segmentResults, globalIdMap) {
|
|
|
1201
1216
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
1202
1217
|
for (const result of segmentResults) {
|
|
1203
1218
|
for (const edge of result.edges) {
|
|
1204
|
-
const newSourceId = globalIdMap.get(
|
|
1205
|
-
|
|
1219
|
+
const newSourceId = globalIdMap.get(
|
|
1220
|
+
`${result.segment.index}:${edge.sourceId}`
|
|
1221
|
+
);
|
|
1222
|
+
const newTargetId = globalIdMap.get(
|
|
1223
|
+
`${result.segment.index}:${edge.targetId}`
|
|
1224
|
+
);
|
|
1206
1225
|
if (newSourceId === void 0 || newTargetId === void 0) continue;
|
|
1207
1226
|
const edgeKey = `${newSourceId}-${newTargetId}`;
|
|
1208
1227
|
const existingIdx = edgeMap.get(edgeKey);
|
|
1209
1228
|
if (existingIdx !== void 0) {
|
|
1210
1229
|
if (edges[existingIdx].score < edge.score) {
|
|
1211
|
-
edges[existingIdx] = {
|
|
1230
|
+
edges[existingIdx] = {
|
|
1231
|
+
sourceId: newSourceId,
|
|
1232
|
+
targetId: newTargetId,
|
|
1233
|
+
score: edge.score
|
|
1234
|
+
};
|
|
1212
1235
|
}
|
|
1213
1236
|
} else {
|
|
1214
1237
|
edgeMap.set(edgeKey, edges.length);
|
|
1215
|
-
edges.push({
|
|
1238
|
+
edges.push({
|
|
1239
|
+
sourceId: newSourceId,
|
|
1240
|
+
targetId: newTargetId,
|
|
1241
|
+
score: edge.score
|
|
1242
|
+
});
|
|
1216
1243
|
}
|
|
1217
1244
|
}
|
|
1218
1245
|
}
|
|
@@ -1222,10 +1249,18 @@ function remapAnimations(segmentResults, globalIdMap) {
|
|
|
1222
1249
|
const animations = [];
|
|
1223
1250
|
for (const result of segmentResults) {
|
|
1224
1251
|
for (const anim of result.animations) {
|
|
1225
|
-
const newStartId = globalIdMap.get(
|
|
1226
|
-
|
|
1252
|
+
const newStartId = globalIdMap.get(
|
|
1253
|
+
`${result.segment.index}:${anim.startFrameId}`
|
|
1254
|
+
);
|
|
1255
|
+
const newEndId = globalIdMap.get(
|
|
1256
|
+
`${result.segment.index}:${anim.endFrameId}`
|
|
1257
|
+
);
|
|
1227
1258
|
if (newStartId === void 0 || newEndId === void 0) continue;
|
|
1228
|
-
animations.push({
|
|
1259
|
+
animations.push({
|
|
1260
|
+
...anim,
|
|
1261
|
+
startFrameId: newStartId,
|
|
1262
|
+
endFrameId: newEndId
|
|
1263
|
+
});
|
|
1229
1264
|
}
|
|
1230
1265
|
}
|
|
1231
1266
|
return animations;
|
|
@@ -1306,7 +1341,7 @@ async function runSegmentedPipeline(options, resolvedOptions) {
|
|
|
1306
1341
|
logger.debug(`Segment plan: ${segments.length} segments`);
|
|
1307
1342
|
const limit = concurrencyLimit(resolvedOptions.concurrency);
|
|
1308
1343
|
const segmentProgresses = new Array(segments.length).fill(0);
|
|
1309
|
-
const weights = segments
|
|
1344
|
+
const weights = map6(segments, (s) => s.duration / totalDuration);
|
|
1310
1345
|
const emitOverallProgress = (phase) => {
|
|
1311
1346
|
if (!options.onProgress) return;
|
|
1312
1347
|
const overall = weights.reduce(
|
|
@@ -1317,7 +1352,8 @@ async function runSegmentedPipeline(options, resolvedOptions) {
|
|
|
1317
1352
|
};
|
|
1318
1353
|
options.onProgress?.("EXTRACTING", 0);
|
|
1319
1354
|
const results = await Promise.all(
|
|
1320
|
-
|
|
1355
|
+
map6(
|
|
1356
|
+
segments,
|
|
1321
1357
|
(segment) => limit(async () => {
|
|
1322
1358
|
const segWorkspace = await createSegmentWorkspace(
|
|
1323
1359
|
mainWorkspace,
|
|
@@ -1349,7 +1385,7 @@ async function runSegmentedPipeline(options, resolvedOptions) {
|
|
|
1349
1385
|
resolvedOptions.threshold,
|
|
1350
1386
|
resolvedOptions.count
|
|
1351
1387
|
);
|
|
1352
|
-
const prunedFrames = frames
|
|
1388
|
+
const prunedFrames = filter5(frames, (f) => survivingIds.has(f.id));
|
|
1353
1389
|
options.onProgress?.("PRUNING", 100);
|
|
1354
1390
|
options.onProgress?.("FINALIZING", 0);
|
|
1355
1391
|
const ctx = {
|
|
@@ -1459,7 +1495,7 @@ async function runPipeline(options) {
|
|
|
1459
1495
|
resolvedOptions.threshold,
|
|
1460
1496
|
resolvedOptions.count
|
|
1461
1497
|
);
|
|
1462
|
-
const prunedFrames = ctx.frames
|
|
1498
|
+
const prunedFrames = filter6(ctx.frames, (f) => survivingIds.has(f.id));
|
|
1463
1499
|
ctx.emitProgress(100);
|
|
1464
1500
|
ctx.status = "FINALIZING";
|
|
1465
1501
|
let outputFiles = [];
|