@lumy-pack/scene-sieve 0.0.10 → 0.0.11
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 +27 -7
- 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 +27 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +27 -7
- package/dist/pipeline-worker.mjs +27 -7
- 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 +2 -2
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.mjs
CHANGED
|
@@ -1297,18 +1297,30 @@ function remapEdges(segmentResults, globalIdMap) {
|
|
|
1297
1297
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
1298
1298
|
for (const result of segmentResults) {
|
|
1299
1299
|
for (const edge of result.edges) {
|
|
1300
|
-
const newSourceId = globalIdMap.get(
|
|
1301
|
-
|
|
1300
|
+
const newSourceId = globalIdMap.get(
|
|
1301
|
+
`${result.segment.index}:${edge.sourceId}`
|
|
1302
|
+
);
|
|
1303
|
+
const newTargetId = globalIdMap.get(
|
|
1304
|
+
`${result.segment.index}:${edge.targetId}`
|
|
1305
|
+
);
|
|
1302
1306
|
if (newSourceId === void 0 || newTargetId === void 0) continue;
|
|
1303
1307
|
const edgeKey = `${newSourceId}-${newTargetId}`;
|
|
1304
1308
|
const existingIdx = edgeMap.get(edgeKey);
|
|
1305
1309
|
if (existingIdx !== void 0) {
|
|
1306
1310
|
if (edges[existingIdx].score < edge.score) {
|
|
1307
|
-
edges[existingIdx] = {
|
|
1311
|
+
edges[existingIdx] = {
|
|
1312
|
+
sourceId: newSourceId,
|
|
1313
|
+
targetId: newTargetId,
|
|
1314
|
+
score: edge.score
|
|
1315
|
+
};
|
|
1308
1316
|
}
|
|
1309
1317
|
} else {
|
|
1310
1318
|
edgeMap.set(edgeKey, edges.length);
|
|
1311
|
-
edges.push({
|
|
1319
|
+
edges.push({
|
|
1320
|
+
sourceId: newSourceId,
|
|
1321
|
+
targetId: newTargetId,
|
|
1322
|
+
score: edge.score
|
|
1323
|
+
});
|
|
1312
1324
|
}
|
|
1313
1325
|
}
|
|
1314
1326
|
}
|
|
@@ -1318,10 +1330,18 @@ function remapAnimations(segmentResults, globalIdMap) {
|
|
|
1318
1330
|
const animations = [];
|
|
1319
1331
|
for (const result of segmentResults) {
|
|
1320
1332
|
for (const anim of result.animations) {
|
|
1321
|
-
const newStartId = globalIdMap.get(
|
|
1322
|
-
|
|
1333
|
+
const newStartId = globalIdMap.get(
|
|
1334
|
+
`${result.segment.index}:${anim.startFrameId}`
|
|
1335
|
+
);
|
|
1336
|
+
const newEndId = globalIdMap.get(
|
|
1337
|
+
`${result.segment.index}:${anim.endFrameId}`
|
|
1338
|
+
);
|
|
1323
1339
|
if (newStartId === void 0 || newEndId === void 0) continue;
|
|
1324
|
-
animations.push({
|
|
1340
|
+
animations.push({
|
|
1341
|
+
...anim,
|
|
1342
|
+
startFrameId: newStartId,
|
|
1343
|
+
endFrameId: newEndId
|
|
1344
|
+
});
|
|
1325
1345
|
}
|
|
1326
1346
|
}
|
|
1327
1347
|
return animations;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export interface SieveViewProps {
|
|
4
|
+
input: string;
|
|
5
|
+
count?: number;
|
|
6
|
+
threshold?: number;
|
|
7
|
+
output?: string;
|
|
8
|
+
fps: number;
|
|
9
|
+
maxFrames: number;
|
|
10
|
+
scale: number;
|
|
11
|
+
quality: number;
|
|
12
|
+
iouThreshold?: number;
|
|
13
|
+
animationThreshold?: number;
|
|
14
|
+
maxSegmentDuration?: number;
|
|
15
|
+
concurrency?: number;
|
|
16
|
+
debug: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function registerSieveCommand(program: Command, version: string): void;
|
|
19
|
+
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 {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const APP_NAME = "scene-sieve";
|
|
2
|
+
export declare const DEFAULT_COUNT = 20;
|
|
3
|
+
export declare const DEFAULT_THRESHOLD = 0.5;
|
|
4
|
+
export declare const DEFAULT_FPS = 5;
|
|
5
|
+
export declare const DEFAULT_SCALE = 720;
|
|
6
|
+
export declare const DEFAULT_QUALITY = 80;
|
|
7
|
+
export declare const DEFAULT_MAX_FRAMES = 300;
|
|
8
|
+
export declare const NORMALIZATION_MIN_PERCENTILE = 0.1;
|
|
9
|
+
export declare const NORMALIZATION_MAX_PERCENTILE = 0.9;
|
|
10
|
+
export declare const NORMALIZATION_LOGISTIC_K = 3;
|
|
11
|
+
export declare const NORMALIZATION_ALPHA = 0.4;
|
|
12
|
+
export declare const NORMALIZATION_MAD_COEFFICIENT = 1.4826;
|
|
13
|
+
export declare const NORMALIZATION_MIN_SAMPLE_SIZE = 10;
|
|
14
|
+
export declare const WORKSPACE_PREFIX = "scene-sieve-";
|
|
15
|
+
export declare const TEMP_BASE_DIR: string;
|
|
16
|
+
export declare const FRAME_OUTPUT_EXTENSION = ".jpg";
|
|
17
|
+
export declare const FRAME_FILENAME_PATTERN = "frame_%06d.jpg";
|
|
18
|
+
export declare const OPENCV_BATCH_SIZE = 10;
|
|
19
|
+
export declare const MIN_IFRAME_COUNT = 3;
|
|
20
|
+
export declare const DBSCAN_ALPHA = 0.03;
|
|
21
|
+
export declare const DBSCAN_MIN_PTS = 4;
|
|
22
|
+
export declare const IOU_THRESHOLD = 0.9;
|
|
23
|
+
export declare const DECAY_LAMBDA = 0.95;
|
|
24
|
+
export declare const ANIMATION_FRAME_THRESHOLD = 5;
|
|
25
|
+
export declare const MATCH_DISTANCE_THRESHOLD = 0.25;
|
|
26
|
+
export declare const PIXELDIFF_GAUSSIAN_KERNEL = 3;
|
|
27
|
+
export declare const PIXELDIFF_BINARY_THRESHOLD = 30;
|
|
28
|
+
export declare const PIXELDIFF_CONTOUR_MIN_AREA = 100;
|
|
29
|
+
export declare const PIXELDIFF_SAMPLE_SPACING = 8;
|
|
30
|
+
export declare const DEFAULT_MAX_SEGMENT_DURATION = 300;
|
|
31
|
+
export declare const DEFAULT_SEGMENT_CONCURRENCY = 2;
|
|
32
|
+
export declare function getTempWorkspaceDir(sessionId: string): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { AnalysisResult, AnimationMetadata, BoundingBox, ProcessContext } from '../types/index.js';
|
|
2
|
+
import type { Point2D } from './dbscan.js';
|
|
3
|
+
type CvLib = typeof import('@techstark/opencv-js');
|
|
4
|
+
export declare function preprocessFrame(framePath: string, scale: number): Promise<{
|
|
5
|
+
data: Uint8Array;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function computeIoU(a: BoundingBox, b: BoundingBox): number;
|
|
10
|
+
export declare class IoUTracker {
|
|
11
|
+
private fps;
|
|
12
|
+
private iouThreshold;
|
|
13
|
+
private animationThreshold;
|
|
14
|
+
private regions;
|
|
15
|
+
private extractedAnimations;
|
|
16
|
+
constructor(fps?: number, iouThreshold?: number, animationThreshold?: number);
|
|
17
|
+
update(boxes: BoundingBox[], pairIndex: number): Set<number>;
|
|
18
|
+
private collectAnimation;
|
|
19
|
+
flushAndGetAnimations(): AnimationMetadata[];
|
|
20
|
+
getAnimationWeight(boxIndex: number, boxes: BoundingBox[]): number;
|
|
21
|
+
}
|
|
22
|
+
export interface AKAZEResult {
|
|
23
|
+
sNew: Point2D[];
|
|
24
|
+
sLoss: Point2D[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Pixel-level difference fallback for AKAZE blind spots.
|
|
28
|
+
*
|
|
29
|
+
* When AKAZE produces sparse results (typical for UI screen recordings
|
|
30
|
+
* where form fields, dropdowns, or overlays change), this function
|
|
31
|
+
* detects changed regions via cv.absdiff and generates synthetic
|
|
32
|
+
* Point2D[] that feed into the existing DBSCAN → IoU → G(t) pipeline.
|
|
33
|
+
*
|
|
34
|
+
* Algorithm:
|
|
35
|
+
* 1. absdiff(frame1, frame2) → grayscale difference
|
|
36
|
+
* 2. GaussianBlur → reduce JPEG compression noise
|
|
37
|
+
* 3. threshold → binary mask of significant changes
|
|
38
|
+
* 4. findContours → bounding rects of changed regions
|
|
39
|
+
* 5. Grid sampling within each bounding rect → Point2D[]
|
|
40
|
+
*/
|
|
41
|
+
export declare function computePixelDiff(cvLib: CvLib, frame1: {
|
|
42
|
+
data: Uint8Array;
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
}, frame2: {
|
|
46
|
+
data: Uint8Array;
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
}): Point2D[];
|
|
50
|
+
export declare function computeInformationGain(clusters: BoundingBox[], clusterPoints: number[], imageArea: number, animationIndices: Set<number>, animationWeights: number[]): number;
|
|
51
|
+
/**
|
|
52
|
+
* Analyze adjacent frame pairs to compute information gain scores (G(t)).
|
|
53
|
+
* Processes frames in batches for memory efficiency.
|
|
54
|
+
*
|
|
55
|
+
* Pipeline:
|
|
56
|
+
* 1. AKAZE Feature Set Difference
|
|
57
|
+
* 2. DBSCAN Spatial Clustering
|
|
58
|
+
* 3. Spatio-temporal IoU Tracking
|
|
59
|
+
* 4. G(t) Information Gain Scoring
|
|
60
|
+
*/
|
|
61
|
+
export declare function analyzeFrames(ctx: ProcessContext): Promise<AnalysisResult>;
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DBSCANResult } from '../types/index.js';
|
|
2
|
+
export interface Point2D {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* DBSCAN clustering with resolution-independent eps.
|
|
8
|
+
* eps = alpha * sqrt(width^2 + height^2)
|
|
9
|
+
*/
|
|
10
|
+
export declare function dbscan(points: Point2D[], imageWidth: number, imageHeight: number, alpha?: number, minPts?: number): DBSCANResult;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { FrameNode, ProcessContext } from '../types/index.js';
|
|
2
|
+
export interface FFprobeMetadata {
|
|
3
|
+
format?: {
|
|
4
|
+
format_name?: string;
|
|
5
|
+
duration?: string;
|
|
6
|
+
};
|
|
7
|
+
streams?: Array<{
|
|
8
|
+
codec_type?: string;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Extract frames from video/GIF using FFmpeg.
|
|
13
|
+
* Always uses FPS-based extraction. For long videos, FPS is automatically
|
|
14
|
+
* reduced to stay within maxFrames budget.
|
|
15
|
+
*/
|
|
16
|
+
export declare function extractFrames(ctx: ProcessContext): Promise<FrameNode[]>;
|
|
17
|
+
export declare function getVideoMetadata(inputPath: string): Promise<FFprobeMetadata>;
|
|
18
|
+
/**
|
|
19
|
+
* Extract frames from a specific time range of a video using FFmpeg.
|
|
20
|
+
* Uses input seeking (-ss before -i) for fast seek + -t for duration.
|
|
21
|
+
*
|
|
22
|
+
* @param inputPath - Path to the video file
|
|
23
|
+
* @param outputDir - Directory to write extracted frames
|
|
24
|
+
* @param fps - Frames per second for extraction
|
|
25
|
+
* @param scale - Height scale for vision analysis
|
|
26
|
+
* @param startTime - Start time in seconds
|
|
27
|
+
* @param duration - Duration in seconds to extract
|
|
28
|
+
* @returns Array of FrameNode with segment-local timestamps (starting from 0)
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractFramesForRange(inputPath: string, outputDir: string, fps: number, scale: number, startTime: number, duration: number): Promise<FrameNode[]>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { runPipeline } from './orchestrator.js';
|
|
2
|
+
export { analyzeFrames, computeIoU, computeInformationGain, } from './analyzer.js';
|
|
3
|
+
export { extractFrames } from './extractor.js';
|
|
4
|
+
export { pruneTo, pruneByThreshold, pruneByThresholdWithCap, suppressConsecutiveRuns, } from './pruner.js';
|
|
5
|
+
export { dbscan } from './dbscan.js';
|
|
6
|
+
export type { Point2D } from './dbscan.js';
|
|
7
|
+
export { resolveInput, resolveOptions } from './input-resolver.js';
|
|
8
|
+
export { shouldSegment, computeSegmentPlan, processSegment, mergeSegmentFrames, runSegmentedPipeline, } from './segmenter.js';
|
|
9
|
+
export { createWorkspace, createSegmentWorkspace, cleanupWorkspace, finalizeOutput, readFramesAsBuffers, writeInputBuffer, writeInputFrames, } from './workspace.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FrameNode, ResolvedOptions, SieveOptions } from '../types/index.js';
|
|
2
|
+
export declare function resolveOptions(options: SieveOptions): ResolvedOptions;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the input source to a list of FrameNode[].
|
|
5
|
+
*
|
|
6
|
+
* - 'file' mode: validate file exists and delegate to extractor (caller's responsibility)
|
|
7
|
+
* - 'buffer' mode: write buffer as temp video file, return path via FrameNode trick (empty list)
|
|
8
|
+
* - 'frames' mode: write frame buffers as JPGs, return FrameNode[]
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveInput(options: SieveOptions, workspacePath: string): Promise<{
|
|
11
|
+
frames: FrameNode[];
|
|
12
|
+
resolvedInputPath?: string;
|
|
13
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { FrameNode, ScoreEdge } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Edge-aware greedy merge with re-linking — O(N log N).
|
|
4
|
+
*
|
|
5
|
+
* 1. Build a doubly-linked list of frames
|
|
6
|
+
* 2. Insert all edges into a min-heap
|
|
7
|
+
* 3. Pop the lowest-score edge (most similar pair)
|
|
8
|
+
* 4. Remove the later frame (tgtId), re-link neighbors
|
|
9
|
+
* 5. Push synthetic edge with score = max(left, right)
|
|
10
|
+
* 6. Repeat until surviving count === targetCount
|
|
11
|
+
* 7. First and last frames are never removed (boundary preservation)
|
|
12
|
+
*
|
|
13
|
+
* Stale heap entries (involving removed frames) are lazily skipped on pop.
|
|
14
|
+
*/
|
|
15
|
+
export declare function pruneTo(graph: ScoreEdge[], frames: FrameNode[], targetCount: number): Set<number>;
|
|
16
|
+
/**
|
|
17
|
+
* Non-Maximum Suppression (NMS) for consecutive edge runs.
|
|
18
|
+
*
|
|
19
|
+
* Consecutive edges share overlapping frames (edge i: frame i->i+1,
|
|
20
|
+
* edge i+1: frame i+1->i+2), so consecutive passing edges indicate
|
|
21
|
+
* the same visual transition region. This function groups consecutive
|
|
22
|
+
* passing edge indices into "runs" and keeps all distinct peaks per run.
|
|
23
|
+
*
|
|
24
|
+
* Multi-peak detection: within each run, strict local maxima (score higher
|
|
25
|
+
* than both neighbors) are identified. Each local maximum represents a
|
|
26
|
+
* distinct visual transition. If no strict local maxima exist (plateau or
|
|
27
|
+
* monotonic sequence), the global peak of the run is selected as fallback.
|
|
28
|
+
*
|
|
29
|
+
* Single-element runs are unaffected (isolated transitions preserved).
|
|
30
|
+
*
|
|
31
|
+
* @param graph - full ScoreEdge array (for targetId lookup)
|
|
32
|
+
* @param passingIndices - edge indices that passed threshold filtering (sorted ascending)
|
|
33
|
+
* @param normalizedScores - normalized score array (same length as graph)
|
|
34
|
+
* @returns Set of targetIds to add to surviving set (one or more per run)
|
|
35
|
+
*/
|
|
36
|
+
export declare function suppressConsecutiveRuns(graph: ScoreEdge[], passingIndices: number[], normalizedScores: number[]): Set<number>;
|
|
37
|
+
/**
|
|
38
|
+
* Threshold-based pruning with NMS -- O(N).
|
|
39
|
+
*
|
|
40
|
+
* 1. Scores are normalized to [0, 1] via percentile normalization.
|
|
41
|
+
* 2. Edges with normalized score >= threshold are collected.
|
|
42
|
+
* 3. Non-Maximum Suppression groups consecutive passing edges and keeps
|
|
43
|
+
* only the peak per run, preventing near-duplicate frame selection
|
|
44
|
+
* from a single visual transition.
|
|
45
|
+
*
|
|
46
|
+
* First and last frames are always preserved (boundary protection).
|
|
47
|
+
*/
|
|
48
|
+
export declare function pruneByThreshold(graph: ScoreEdge[], frames: FrameNode[], threshold: number): Set<number>;
|
|
49
|
+
/**
|
|
50
|
+
* Combined threshold + count pruning -- 2-stage pipeline.
|
|
51
|
+
*
|
|
52
|
+
* Stage 1: pruneByThreshold -- keep all frames with normalized score >= threshold
|
|
53
|
+
* Stage 2: if result exceeds maxCount, rebuild subgraph with synthetic edges
|
|
54
|
+
* (min-score over each gap) and apply pruneTo on the surviving subset
|
|
55
|
+
*
|
|
56
|
+
* Edge reconstruction: for consecutive survivors A, B with removed frames
|
|
57
|
+
* [x1, x2, ...] between them, the synthetic edge score is:
|
|
58
|
+
* min(score(A->x1), score(x1->x2), ..., score(xN->B))
|
|
59
|
+
* This preserves the "weakest link" semantics.
|
|
60
|
+
*/
|
|
61
|
+
export declare function pruneByThresholdWithCap(graph: ScoreEdge[], frames: FrameNode[], threshold: number, maxCount: number): Set<number>;
|
|
@@ -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>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AnimationMetadata, FrameNode, ResolvedOptions, ScoreEdge, SegmentPlan, SegmentResult, SieveOptions, SieveResult } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Determine whether segmentation should be used.
|
|
4
|
+
* Returns false for frames mode and GIF files.
|
|
5
|
+
* Actual duration check happens inside runSegmentedPipeline after metadata fetch.
|
|
6
|
+
*/
|
|
7
|
+
export declare function shouldSegment(resolvedOptions: ResolvedOptions, originalOptions: SieveOptions): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Compute segment boundaries with overlap, frame allocation, and effectiveFps.
|
|
10
|
+
* Pure function — no I/O.
|
|
11
|
+
*
|
|
12
|
+
* - effectiveFps is uniform across all segments
|
|
13
|
+
* - Overlap: 1 frame at each internal boundary
|
|
14
|
+
* - allocatedFrames total <= maxFrames (last segment adjusted if needed)
|
|
15
|
+
*/
|
|
16
|
+
export declare function computeSegmentPlan(totalDuration: number, maxSegmentDuration: number, maxFrames: number, fps: number): SegmentPlan[];
|
|
17
|
+
/**
|
|
18
|
+
* Merge multiple segment results into a single unified frame/edge/animation set.
|
|
19
|
+
* - Timestamps adjusted using extractStartTime (Section 18 note 1)
|
|
20
|
+
* - Overlap frames deduplicated by threshold 1/(effectiveFps*2) (Section 18 note 5)
|
|
21
|
+
* - Global IDs reassigned after dedup
|
|
22
|
+
* - Duplicate edges keep higher score
|
|
23
|
+
*/
|
|
24
|
+
export declare function mergeSegmentFrames(segmentResults: SegmentResult[]): {
|
|
25
|
+
frames: FrameNode[];
|
|
26
|
+
edges: ScoreEdge[];
|
|
27
|
+
animations: AnimationMetadata[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Extract frames for a single segment and analyze them.
|
|
31
|
+
* Each segment uses an isolated workspace directory.
|
|
32
|
+
*/
|
|
33
|
+
export declare function processSegment(inputPath: string, segment: SegmentPlan, workspacePath: string, resolvedOptions: ResolvedOptions, onProgress: (percent: number) => void): Promise<SegmentResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Full segmented pipeline: metadata → plan → parallel extract+analyze → merge → prune → finalize.
|
|
36
|
+
* Called from runPipeline when shouldSegment() returns true.
|
|
37
|
+
*/
|
|
38
|
+
export declare function runSegmentedPipeline(options: SieveOptions, resolvedOptions: ResolvedOptions): Promise<SieveResult>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FrameNode, ProcessContext } from '../types/index.js';
|
|
2
|
+
export declare function createWorkspace(sessionId: string): Promise<string>;
|
|
3
|
+
export declare function finalizeOutput(ctx: ProcessContext, selectedFrames: FrameNode[]): Promise<string[]>;
|
|
4
|
+
export declare function createSegmentWorkspace(parentWorkspacePath: string, segmentIndex: number): Promise<string>;
|
|
5
|
+
export declare function cleanupWorkspace(workspacePath: string): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Remove stale workspace directories left by previous interrupted runs.
|
|
8
|
+
* Only deletes directories older than 1 hour to avoid removing active workspaces.
|
|
9
|
+
*/
|
|
10
|
+
export declare function cleanupStaleWorkspaces(): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Write a video buffer to a temp file in the workspace and return the path.
|
|
13
|
+
* Used by 'buffer' input mode.
|
|
14
|
+
*/
|
|
15
|
+
export declare function writeInputBuffer(buffer: Buffer, workspacePath: string): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Write an array of frame Buffers as JPG files and return FrameNode[].
|
|
18
|
+
* Used by 'frames' input mode.
|
|
19
|
+
*/
|
|
20
|
+
export declare function writeInputFrames(frames: Buffer[], workspacePath: string): Promise<FrameNode[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Read selected FrameNode files as Buffers with JPEG compression.
|
|
23
|
+
* Used to return output buffers in 'buffer' and 'frames' modes.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readFramesAsBuffers(frameNodes: FrameNode[], quality: number): Promise<Buffer[]>;
|
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
|
@@ -1241,18 +1241,30 @@ function remapEdges(segmentResults, globalIdMap) {
|
|
|
1241
1241
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
1242
1242
|
for (const result of segmentResults) {
|
|
1243
1243
|
for (const edge of result.edges) {
|
|
1244
|
-
const newSourceId = globalIdMap.get(
|
|
1245
|
-
|
|
1244
|
+
const newSourceId = globalIdMap.get(
|
|
1245
|
+
`${result.segment.index}:${edge.sourceId}`
|
|
1246
|
+
);
|
|
1247
|
+
const newTargetId = globalIdMap.get(
|
|
1248
|
+
`${result.segment.index}:${edge.targetId}`
|
|
1249
|
+
);
|
|
1246
1250
|
if (newSourceId === void 0 || newTargetId === void 0) continue;
|
|
1247
1251
|
const edgeKey = `${newSourceId}-${newTargetId}`;
|
|
1248
1252
|
const existingIdx = edgeMap.get(edgeKey);
|
|
1249
1253
|
if (existingIdx !== void 0) {
|
|
1250
1254
|
if (edges[existingIdx].score < edge.score) {
|
|
1251
|
-
edges[existingIdx] = {
|
|
1255
|
+
edges[existingIdx] = {
|
|
1256
|
+
sourceId: newSourceId,
|
|
1257
|
+
targetId: newTargetId,
|
|
1258
|
+
score: edge.score
|
|
1259
|
+
};
|
|
1252
1260
|
}
|
|
1253
1261
|
} else {
|
|
1254
1262
|
edgeMap.set(edgeKey, edges.length);
|
|
1255
|
-
edges.push({
|
|
1263
|
+
edges.push({
|
|
1264
|
+
sourceId: newSourceId,
|
|
1265
|
+
targetId: newTargetId,
|
|
1266
|
+
score: edge.score
|
|
1267
|
+
});
|
|
1256
1268
|
}
|
|
1257
1269
|
}
|
|
1258
1270
|
}
|
|
@@ -1262,10 +1274,18 @@ function remapAnimations(segmentResults, globalIdMap) {
|
|
|
1262
1274
|
const animations = [];
|
|
1263
1275
|
for (const result of segmentResults) {
|
|
1264
1276
|
for (const anim of result.animations) {
|
|
1265
|
-
const newStartId = globalIdMap.get(
|
|
1266
|
-
|
|
1277
|
+
const newStartId = globalIdMap.get(
|
|
1278
|
+
`${result.segment.index}:${anim.startFrameId}`
|
|
1279
|
+
);
|
|
1280
|
+
const newEndId = globalIdMap.get(
|
|
1281
|
+
`${result.segment.index}:${anim.endFrameId}`
|
|
1282
|
+
);
|
|
1267
1283
|
if (newStartId === void 0 || newEndId === void 0) continue;
|
|
1268
|
-
animations.push({
|
|
1284
|
+
animations.push({
|
|
1285
|
+
...anim,
|
|
1286
|
+
startFrameId: newStartId,
|
|
1287
|
+
endFrameId: newEndId
|
|
1288
|
+
});
|
|
1269
1289
|
}
|
|
1270
1290
|
}
|
|
1271
1291
|
return animations;
|
package/dist/index.d.ts
ADDED
package/dist/index.mjs
CHANGED
|
@@ -1201,18 +1201,30 @@ function remapEdges(segmentResults, globalIdMap) {
|
|
|
1201
1201
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
1202
1202
|
for (const result of segmentResults) {
|
|
1203
1203
|
for (const edge of result.edges) {
|
|
1204
|
-
const newSourceId = globalIdMap.get(
|
|
1205
|
-
|
|
1204
|
+
const newSourceId = globalIdMap.get(
|
|
1205
|
+
`${result.segment.index}:${edge.sourceId}`
|
|
1206
|
+
);
|
|
1207
|
+
const newTargetId = globalIdMap.get(
|
|
1208
|
+
`${result.segment.index}:${edge.targetId}`
|
|
1209
|
+
);
|
|
1206
1210
|
if (newSourceId === void 0 || newTargetId === void 0) continue;
|
|
1207
1211
|
const edgeKey = `${newSourceId}-${newTargetId}`;
|
|
1208
1212
|
const existingIdx = edgeMap.get(edgeKey);
|
|
1209
1213
|
if (existingIdx !== void 0) {
|
|
1210
1214
|
if (edges[existingIdx].score < edge.score) {
|
|
1211
|
-
edges[existingIdx] = {
|
|
1215
|
+
edges[existingIdx] = {
|
|
1216
|
+
sourceId: newSourceId,
|
|
1217
|
+
targetId: newTargetId,
|
|
1218
|
+
score: edge.score
|
|
1219
|
+
};
|
|
1212
1220
|
}
|
|
1213
1221
|
} else {
|
|
1214
1222
|
edgeMap.set(edgeKey, edges.length);
|
|
1215
|
-
edges.push({
|
|
1223
|
+
edges.push({
|
|
1224
|
+
sourceId: newSourceId,
|
|
1225
|
+
targetId: newTargetId,
|
|
1226
|
+
score: edge.score
|
|
1227
|
+
});
|
|
1216
1228
|
}
|
|
1217
1229
|
}
|
|
1218
1230
|
}
|
|
@@ -1222,10 +1234,18 @@ function remapAnimations(segmentResults, globalIdMap) {
|
|
|
1222
1234
|
const animations = [];
|
|
1223
1235
|
for (const result of segmentResults) {
|
|
1224
1236
|
for (const anim of result.animations) {
|
|
1225
|
-
const newStartId = globalIdMap.get(
|
|
1226
|
-
|
|
1237
|
+
const newStartId = globalIdMap.get(
|
|
1238
|
+
`${result.segment.index}:${anim.startFrameId}`
|
|
1239
|
+
);
|
|
1240
|
+
const newEndId = globalIdMap.get(
|
|
1241
|
+
`${result.segment.index}:${anim.endFrameId}`
|
|
1242
|
+
);
|
|
1227
1243
|
if (newStartId === void 0 || newEndId === void 0) continue;
|
|
1228
|
-
animations.push({
|
|
1244
|
+
animations.push({
|
|
1245
|
+
...anim,
|
|
1246
|
+
startFrameId: newStartId,
|
|
1247
|
+
endFrameId: newEndId
|
|
1248
|
+
});
|
|
1229
1249
|
}
|
|
1230
1250
|
}
|
|
1231
1251
|
return animations;
|
package/dist/pipeline-worker.mjs
CHANGED
|
@@ -1204,18 +1204,30 @@ function remapEdges(segmentResults, globalIdMap) {
|
|
|
1204
1204
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
1205
1205
|
for (const result of segmentResults) {
|
|
1206
1206
|
for (const edge of result.edges) {
|
|
1207
|
-
const newSourceId = globalIdMap.get(
|
|
1208
|
-
|
|
1207
|
+
const newSourceId = globalIdMap.get(
|
|
1208
|
+
`${result.segment.index}:${edge.sourceId}`
|
|
1209
|
+
);
|
|
1210
|
+
const newTargetId = globalIdMap.get(
|
|
1211
|
+
`${result.segment.index}:${edge.targetId}`
|
|
1212
|
+
);
|
|
1209
1213
|
if (newSourceId === void 0 || newTargetId === void 0) continue;
|
|
1210
1214
|
const edgeKey = `${newSourceId}-${newTargetId}`;
|
|
1211
1215
|
const existingIdx = edgeMap.get(edgeKey);
|
|
1212
1216
|
if (existingIdx !== void 0) {
|
|
1213
1217
|
if (edges[existingIdx].score < edge.score) {
|
|
1214
|
-
edges[existingIdx] = {
|
|
1218
|
+
edges[existingIdx] = {
|
|
1219
|
+
sourceId: newSourceId,
|
|
1220
|
+
targetId: newTargetId,
|
|
1221
|
+
score: edge.score
|
|
1222
|
+
};
|
|
1215
1223
|
}
|
|
1216
1224
|
} else {
|
|
1217
1225
|
edgeMap.set(edgeKey, edges.length);
|
|
1218
|
-
edges.push({
|
|
1226
|
+
edges.push({
|
|
1227
|
+
sourceId: newSourceId,
|
|
1228
|
+
targetId: newTargetId,
|
|
1229
|
+
score: edge.score
|
|
1230
|
+
});
|
|
1219
1231
|
}
|
|
1220
1232
|
}
|
|
1221
1233
|
}
|
|
@@ -1225,10 +1237,18 @@ function remapAnimations(segmentResults, globalIdMap) {
|
|
|
1225
1237
|
const animations = [];
|
|
1226
1238
|
for (const result of segmentResults) {
|
|
1227
1239
|
for (const anim of result.animations) {
|
|
1228
|
-
const newStartId = globalIdMap.get(
|
|
1229
|
-
|
|
1240
|
+
const newStartId = globalIdMap.get(
|
|
1241
|
+
`${result.segment.index}:${anim.startFrameId}`
|
|
1242
|
+
);
|
|
1243
|
+
const newEndId = globalIdMap.get(
|
|
1244
|
+
`${result.segment.index}:${anim.endFrameId}`
|
|
1245
|
+
);
|
|
1230
1246
|
if (newStartId === void 0 || newEndId === void 0) continue;
|
|
1231
|
-
animations.push({
|
|
1247
|
+
animations.push({
|
|
1248
|
+
...anim,
|
|
1249
|
+
startFrameId: newStartId,
|
|
1250
|
+
endFrameId: newEndId
|
|
1251
|
+
});
|
|
1232
1252
|
}
|
|
1233
1253
|
}
|
|
1234
1254
|
return animations;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export type ProgressPhase = 'EXTRACTING' | 'ANALYZING' | 'PRUNING' | 'FINALIZING';
|
|
2
|
+
export type SieveInput = {
|
|
3
|
+
mode: 'file';
|
|
4
|
+
inputPath: string;
|
|
5
|
+
} | {
|
|
6
|
+
mode: 'buffer';
|
|
7
|
+
inputBuffer: Buffer;
|
|
8
|
+
} | {
|
|
9
|
+
mode: 'frames';
|
|
10
|
+
inputFrames: Buffer[];
|
|
11
|
+
};
|
|
12
|
+
export interface SieveOptionsBase {
|
|
13
|
+
count?: number;
|
|
14
|
+
threshold?: number;
|
|
15
|
+
outputPath?: string;
|
|
16
|
+
fps?: number;
|
|
17
|
+
maxFrames?: number;
|
|
18
|
+
scale?: number;
|
|
19
|
+
quality?: number;
|
|
20
|
+
iouThreshold?: number;
|
|
21
|
+
animationThreshold?: number;
|
|
22
|
+
debug?: boolean;
|
|
23
|
+
onProgress?: (phase: ProgressPhase, percent: number) => void;
|
|
24
|
+
maxSegmentDuration?: number;
|
|
25
|
+
concurrency?: number;
|
|
26
|
+
}
|
|
27
|
+
export type SieveOptions = SieveOptionsBase & SieveInput;
|
|
28
|
+
export interface ResolvedOptions {
|
|
29
|
+
mode: 'file' | 'buffer' | 'frames';
|
|
30
|
+
inputPath?: string;
|
|
31
|
+
count: number;
|
|
32
|
+
threshold: number;
|
|
33
|
+
pruneMode: 'threshold-with-cap';
|
|
34
|
+
outputPath: string;
|
|
35
|
+
fps: number;
|
|
36
|
+
maxFrames: number;
|
|
37
|
+
scale: number;
|
|
38
|
+
quality: number;
|
|
39
|
+
iouThreshold: number;
|
|
40
|
+
animationThreshold: number;
|
|
41
|
+
debug: boolean;
|
|
42
|
+
maxSegmentDuration: number;
|
|
43
|
+
concurrency: number;
|
|
44
|
+
}
|
|
45
|
+
export interface SieveResult {
|
|
46
|
+
success: boolean;
|
|
47
|
+
originalFramesCount: number;
|
|
48
|
+
prunedFramesCount: number;
|
|
49
|
+
outputFiles: string[];
|
|
50
|
+
outputBuffers?: Buffer[];
|
|
51
|
+
animations?: AnimationMetadata[];
|
|
52
|
+
video?: VideoMetadata;
|
|
53
|
+
executionTimeMs: number;
|
|
54
|
+
}
|
|
55
|
+
export interface AnimationMetadata {
|
|
56
|
+
type: string;
|
|
57
|
+
boundingBox: BoundingBox;
|
|
58
|
+
startFrameId: number;
|
|
59
|
+
endFrameId: number;
|
|
60
|
+
durationMs: number;
|
|
61
|
+
}
|
|
62
|
+
export interface VideoMetadata {
|
|
63
|
+
originalDurationMs: number;
|
|
64
|
+
fps: number;
|
|
65
|
+
resolution: {
|
|
66
|
+
width: number;
|
|
67
|
+
height: number;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export interface FrameNode {
|
|
71
|
+
id: number;
|
|
72
|
+
timestamp: number;
|
|
73
|
+
extractPath: string;
|
|
74
|
+
}
|
|
75
|
+
export interface ScoreEdge {
|
|
76
|
+
sourceId: number;
|
|
77
|
+
targetId: number;
|
|
78
|
+
/**
|
|
79
|
+
* Information gain score (G(t)) between adjacent frames.
|
|
80
|
+
* Higher values = greater visual change (state transition) = should be preserved.
|
|
81
|
+
* Lower values = similar frames (little change) = candidates for pruning.
|
|
82
|
+
*
|
|
83
|
+
* Pruner removes frames with the LOWEST scores first (greedy ascending).
|
|
84
|
+
* Maps directly to G(t) from the vision analysis pipeline.
|
|
85
|
+
*/
|
|
86
|
+
score: number;
|
|
87
|
+
}
|
|
88
|
+
export interface BoundingBox {
|
|
89
|
+
x: number;
|
|
90
|
+
y: number;
|
|
91
|
+
width: number;
|
|
92
|
+
height: number;
|
|
93
|
+
}
|
|
94
|
+
export interface DBSCANResult {
|
|
95
|
+
labels: number[];
|
|
96
|
+
boundingBoxes: BoundingBox[];
|
|
97
|
+
}
|
|
98
|
+
export interface ProcessContext {
|
|
99
|
+
options: ResolvedOptions;
|
|
100
|
+
workspacePath: string;
|
|
101
|
+
frames: FrameNode[];
|
|
102
|
+
graph: ScoreEdge[];
|
|
103
|
+
animations?: AnimationMetadata[];
|
|
104
|
+
status: 'INIT' | ProgressPhase | 'SUCCESS' | 'FAILED';
|
|
105
|
+
emitProgress: (percent: number) => void;
|
|
106
|
+
error?: Error;
|
|
107
|
+
}
|
|
108
|
+
export interface AnalysisResult {
|
|
109
|
+
edges: ScoreEdge[];
|
|
110
|
+
animations: AnimationMetadata[];
|
|
111
|
+
}
|
|
112
|
+
export interface SegmentPlan {
|
|
113
|
+
index: number;
|
|
114
|
+
startTime: number;
|
|
115
|
+
endTime: number;
|
|
116
|
+
duration: number;
|
|
117
|
+
allocatedFrames: number;
|
|
118
|
+
effectiveFps: number;
|
|
119
|
+
overlapBefore: number;
|
|
120
|
+
overlapAfter: number;
|
|
121
|
+
extractStartTime: number;
|
|
122
|
+
extractDuration: number;
|
|
123
|
+
}
|
|
124
|
+
export interface SegmentResult {
|
|
125
|
+
segment: SegmentPlan;
|
|
126
|
+
frames: FrameNode[];
|
|
127
|
+
edges: ScoreEdge[];
|
|
128
|
+
animations: AnimationMetadata[];
|
|
129
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central command registry for scene-sieve CLI.
|
|
3
|
+
* Single source of truth for command metadata, used by --describe and --help.
|
|
4
|
+
*/
|
|
5
|
+
export interface CommandOption {
|
|
6
|
+
flag: string;
|
|
7
|
+
description: string;
|
|
8
|
+
type?: 'boolean' | 'string' | 'number';
|
|
9
|
+
default?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CommandArgument {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
required: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface CommandInfo {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
usage: string;
|
|
20
|
+
arguments?: CommandArgument[];
|
|
21
|
+
options?: CommandOption[];
|
|
22
|
+
examples?: string[];
|
|
23
|
+
}
|
|
24
|
+
export declare const SIEVE_COMMAND: CommandInfo;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for objects that have a numeric score.
|
|
3
|
+
*/
|
|
4
|
+
export interface ScoredItem {
|
|
5
|
+
score: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Normalize raw scores to [0, 1] range via Robust Hybrid Normalization.
|
|
9
|
+
*
|
|
10
|
+
* This model combines two mathematical approaches to provide a stable "relative" threshold:
|
|
11
|
+
*
|
|
12
|
+
* 1. Logistic-Robust-Z (Intensity):
|
|
13
|
+
* Calculates Z-scores using Median and Median Absolute Deviation (MAD).
|
|
14
|
+
* Maps these to a sigmoid (logistic) curve. This suppresses noise (scores near median)
|
|
15
|
+
* and highlights significant signals (outliers) without letting extreme outliers
|
|
16
|
+
* crush other meaningful transitions.
|
|
17
|
+
*
|
|
18
|
+
* 2. CDF / Percentile Rank (Relative Position):
|
|
19
|
+
* Maps each score to its percentile rank in the sequence. This ensures that 't'
|
|
20
|
+
* always has a consistent meaning as a "relative rank" regardless of absolute values.
|
|
21
|
+
*
|
|
22
|
+
* The final score is a weighted sum (NORMALIZATION_ALPHA) of both.
|
|
23
|
+
*
|
|
24
|
+
* @param items - Array of items with scores to normalize
|
|
25
|
+
* @returns normalized scores array (same length as input)
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeScores<T extends ScoredItem>(items: T[]): number[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic binary min-heap.
|
|
3
|
+
*
|
|
4
|
+
* Elements are ordered by a numeric `score` field.
|
|
5
|
+
* push/pop: O(log N), size: O(1).
|
|
6
|
+
*/
|
|
7
|
+
export declare class MinHeap<T extends {
|
|
8
|
+
score: number;
|
|
9
|
+
}> {
|
|
10
|
+
private readonly h;
|
|
11
|
+
get size(): number;
|
|
12
|
+
push(entry: T): void;
|
|
13
|
+
pop(): T | undefined;
|
|
14
|
+
private siftUp;
|
|
15
|
+
private siftDown;
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SieveOptionsBase } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Common pipeline options parsed from CLI opts (Commander string values → typed values).
|
|
4
|
+
* Does NOT include mode-specific fields (mode, inputPath, onProgress).
|
|
5
|
+
*/
|
|
6
|
+
export type ParsedPipelineOptions = Pick<Required<SieveOptionsBase>, 'fps' | 'maxFrames' | 'scale' | 'quality' | 'debug'> & Pick<SieveOptionsBase, 'count' | 'threshold' | 'outputPath' | 'iouThreshold' | 'animationThreshold' | 'maxSegmentDuration' | 'concurrency'>;
|
|
7
|
+
export interface RawCliOptions {
|
|
8
|
+
count?: string;
|
|
9
|
+
threshold?: string;
|
|
10
|
+
output?: string;
|
|
11
|
+
fps: string;
|
|
12
|
+
maxFrames: string;
|
|
13
|
+
scale: string;
|
|
14
|
+
quality: string;
|
|
15
|
+
iouThreshold?: string;
|
|
16
|
+
animThreshold?: string;
|
|
17
|
+
maxSegmentDuration?: string;
|
|
18
|
+
concurrency?: string;
|
|
19
|
+
debug?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function parsePipelineOptions(opts: RawCliOptions): ParsedPipelineOptions;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function ensureDir(dirPath: string): Promise<void>;
|
|
2
|
+
export declare function fileExists(filePath: string): Promise<boolean>;
|
|
3
|
+
/**
|
|
4
|
+
* Expand leading ~ to homedir. Node's path.resolve() does not expand ~,
|
|
5
|
+
* so paths like ~/Desktop/foo depend on process.cwd() and can produce
|
|
6
|
+
* different results when run from different directories.
|
|
7
|
+
*/
|
|
8
|
+
export declare function expandTilde(p: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve path to absolute. Expands ~ to homedir first so that the result
|
|
11
|
+
* does not depend on process.cwd().
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveAbsolute(p: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Derive default output directory name from input file path.
|
|
16
|
+
* e.g., /path/to/video.mp4 -> /path/to/video_scenes
|
|
17
|
+
*/
|
|
18
|
+
export declare function deriveOutputPath(inputPath: string): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumy-pack/scene-sieve",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "CLI tool for extracting key frames from video and GIF files",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -55,7 +55,6 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@ffprobe-installer/ffprobe": "^1.4.1",
|
|
58
|
-
"@lumy-pack/shared": "0.0.1",
|
|
59
58
|
"@techstark/opencv-js": "4.12.0-release.1",
|
|
60
59
|
"commander": "^12.1.0",
|
|
61
60
|
"execa": "^9.5.0",
|
|
@@ -67,6 +66,7 @@
|
|
|
67
66
|
"sharp": "^0.33.0"
|
|
68
67
|
},
|
|
69
68
|
"devDependencies": {
|
|
69
|
+
"@lumy-pack/shared": "0.0.1",
|
|
70
70
|
"@types/node": "^20.11.0",
|
|
71
71
|
"@types/react": "^18.0.0",
|
|
72
72
|
"@vitest/coverage-v8": "^3.2.4"
|