@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.
@@ -3,6 +3,7 @@ import { parentPort, workerData } from "worker_threads";
3
3
 
4
4
  // src/core/orchestrator.ts
5
5
  import { randomUUID as randomUUID2 } from "crypto";
6
+ import { filter as filter6 } from "@winglet/common-utils";
6
7
 
7
8
  // src/utils/logger.ts
8
9
  import pc from "picocolors";
@@ -36,6 +37,7 @@ ${pc.green("done")} ${message}`);
36
37
 
37
38
  // src/core/analyzer.ts
38
39
  import { createRequire } from "module";
40
+ import { filter, map } from "@winglet/common-utils";
39
41
  import sharp from "sharp";
40
42
 
41
43
  // src/constants.ts
@@ -252,7 +254,8 @@ var IoUTracker = class {
252
254
  this.collectAnimation(region);
253
255
  }
254
256
  }
255
- this.regions = this.regions.filter(
257
+ this.regions = filter(
258
+ this.regions,
256
259
  (r, i) => r.weight > 0.01 || matched.has(i)
257
260
  );
258
261
  return animationIndices;
@@ -433,7 +436,7 @@ function computeInformationGain(clusters, clusterPoints, imageArea, animationInd
433
436
  async function analyzeBatch(cvLib, frames, scale, tracker, pairOffset) {
434
437
  const edges = [];
435
438
  const preprocessed = await Promise.all(
436
- frames.map((f) => preprocessFrame(f.extractPath, scale))
439
+ map(frames, (f) => preprocessFrame(f.extractPath, scale))
437
440
  );
438
441
  const imageWidth = preprocessed[0]?.width ?? scale;
439
442
  const imageHeight = preprocessed[0]?.height ?? Math.round(scale * 9 / 16);
@@ -475,7 +478,8 @@ async function analyzeBatch(cvLib, frames, scale, tracker, pairOffset) {
475
478
  }
476
479
  }
477
480
  const animationIndices = tracker.update(clusters, pairIndex);
478
- const animationWeights = clusters.map(
481
+ const animationWeights = map(
482
+ clusters,
479
483
  (_, ci) => animationIndices.has(ci) ? tracker.getAnimationWeight(ci, clusters) : 0
480
484
  );
481
485
  const score = computeInformationGain(
@@ -540,6 +544,7 @@ async function analyzeFrames(ctx) {
540
544
  import { readdir } from "fs/promises";
541
545
  import { join as join2 } from "path";
542
546
  import { path as ffprobePath } from "@ffprobe-installer/ffprobe";
547
+ import { filter as filter2, map as map2 } from "@winglet/common-utils";
543
548
  import { execa } from "execa";
544
549
  import ffmpegPath from "ffmpeg-static";
545
550
 
@@ -651,11 +656,11 @@ async function getVideoMetadata(inputPath) {
651
656
  }
652
657
  async function buildFrameList(framesDir, duration) {
653
658
  const files = await readdir(framesDir);
654
- const jpgFiles = files.filter((f) => f.endsWith(".jpg")).sort();
659
+ const jpgFiles = filter2(files, (f) => f.endsWith(".jpg")).sort();
655
660
  if (jpgFiles.length === 0) {
656
661
  return [];
657
662
  }
658
- return jpgFiles.map((file, index) => ({
663
+ return map2(jpgFiles, (file, index) => ({
659
664
  id: index,
660
665
  timestamp: duration > 0 && jpgFiles.length > 1 ? duration * index / (jpgFiles.length - 1) : index,
661
666
  extractPath: join2(framesDir, file)
@@ -685,6 +690,7 @@ import { join as join4 } from "path";
685
690
  // src/core/workspace.ts
686
691
  import { readdir as readdir2, rename, rm, stat as stat2, writeFile } from "fs/promises";
687
692
  import { join as join3 } from "path";
693
+ import { map as map3 } from "@winglet/common-utils";
688
694
  import sharp2 from "sharp";
689
695
  async function createWorkspace(sessionId) {
690
696
  const workspacePath = getTempWorkspaceDir(sessionId);
@@ -725,7 +731,7 @@ async function finalizeOutput(ctx, selectedFrames) {
725
731
  }
726
732
  },
727
733
  frames: framesMetadata,
728
- animations: (ctx.animations || []).map((anim) => ({
734
+ animations: map3(ctx.animations || [], (anim) => ({
729
735
  ...anim,
730
736
  startFrameId: anim.startFrameId + 1,
731
737
  endFrameId: anim.endFrameId + 1,
@@ -778,7 +784,8 @@ async function writeInputFrames(frames, workspacePath) {
778
784
  }
779
785
  async function readFramesAsBuffers(frameNodes, quality) {
780
786
  return Promise.all(
781
- frameNodes.map(
787
+ map3(
788
+ frameNodes,
782
789
  (f) => sharp2(f.extractPath).jpeg({ quality, mozjpeg: true }).toBuffer()
783
790
  )
784
791
  );
@@ -835,39 +842,46 @@ async function resolveInput(options2, workspacePath) {
835
842
  throw new Error(`Unsupported input mode: ${options2.mode}`);
836
843
  }
837
844
 
845
+ // src/core/pruner.ts
846
+ import { filter as filter4, map as map5 } from "@winglet/common-utils";
847
+
838
848
  // src/utils/math.ts
849
+ import { filter as filter3, map as map4 } from "@winglet/common-utils";
839
850
  function normalizeScores(items) {
840
851
  if (items.length === 0) return [];
841
- const safeScores = items.map(
852
+ const safeScores = map4(
853
+ items,
842
854
  (e) => Number.isFinite(e.score) && e.score > 0 ? e.score : 0
843
855
  );
844
- const positiveScores = safeScores.filter((s) => s > 0);
856
+ const positiveScores = filter3(safeScores, (s) => s > 0);
845
857
  if (positiveScores.length === 0) return safeScores;
846
858
  const sorted = [...positiveScores].sort((a, b) => a - b);
847
859
  if (positiveScores.length <= NORMALIZATION_MIN_SAMPLE_SIZE) {
848
860
  const min = sorted[0];
849
861
  const max = sorted[sorted.length - 1];
850
- if (max === min) return safeScores.map((s) => s > 0 ? 1 : 0);
851
- return safeScores.map(
862
+ if (max === min) return map4(safeScores, (s) => s > 0 ? 1 : 0);
863
+ return map4(
864
+ safeScores,
852
865
  (s) => s <= 0 ? 0 : Math.max(0, Math.min((s - min) / (max - min), 1))
853
866
  );
854
867
  }
855
868
  const median = sorted[Math.floor(sorted.length / 2)];
856
- const absoluteDiffs = positiveScores.map((v) => Math.abs(v - median));
869
+ const absoluteDiffs = map4(positiveScores, (v) => Math.abs(v - median));
857
870
  const mad = [...absoluteDiffs].sort((a, b) => a - b)[Math.floor(absoluteDiffs.length / 2)];
858
871
  const scale = mad === 0 ? median : mad * NORMALIZATION_MAD_COEFFICIENT;
859
- const logisticZ = safeScores.map((s) => {
872
+ const logisticZ = map4(safeScores, (s) => {
860
873
  if (s <= 0) return 0;
861
874
  if (scale === 0) return 1;
862
875
  const z = (s - median) / scale;
863
876
  return 1 / (1 + Math.exp(-NORMALIZATION_LOGISTIC_K * z));
864
877
  });
865
- const cdf = safeScores.map((s) => {
878
+ const cdf = map4(safeScores, (s) => {
866
879
  if (s <= 0) return 0;
867
880
  const rank = sorted.findIndex((v) => v >= s);
868
881
  return rank / sorted.length;
869
882
  });
870
- return logisticZ.map(
883
+ return map4(
884
+ logisticZ,
871
885
  (z, i) => z * (1 - NORMALIZATION_ALPHA) + cdf[i] * NORMALIZATION_ALPHA
872
886
  );
873
887
  }
@@ -919,7 +933,7 @@ var MinHeap = class {
919
933
  // src/core/pruner.ts
920
934
  function pruneTo(graph, frames, targetCount) {
921
935
  if (frames.length <= targetCount) {
922
- return new Set(frames.map((f) => f.id));
936
+ return new Set(map5(frames, (f) => f.id));
923
937
  }
924
938
  const prev = /* @__PURE__ */ new Map();
925
939
  const next = /* @__PURE__ */ new Map();
@@ -937,7 +951,7 @@ function pruneTo(graph, frames, targetCount) {
937
951
  tgtId: edge.targetId
938
952
  });
939
953
  }
940
- const surviving = new Set(frames.map((f) => f.id));
954
+ const surviving = new Set(map5(frames, (f) => f.id));
941
955
  const firstId = frames[0].id;
942
956
  const lastId = frames[frames.length - 1].id;
943
957
  while (surviving.size > targetCount && heap.size > 0) {
@@ -1030,7 +1044,7 @@ function pruneByThresholdWithCap(graph, frames, threshold, maxCount) {
1030
1044
  if (thresholdSurvivors.size <= maxCount) {
1031
1045
  return thresholdSurvivors;
1032
1046
  }
1033
- const survivingFrames = frames.filter((f) => thresholdSurvivors.has(f.id));
1047
+ const survivingFrames = filter4(frames, (f) => thresholdSurvivors.has(f.id));
1034
1048
  const idToOrigIdx = /* @__PURE__ */ new Map();
1035
1049
  for (let i = 0; i < frames.length; i++) {
1036
1050
  idToOrigIdx.set(frames[i].id, i);
@@ -1066,6 +1080,7 @@ function pruneByThresholdWithCap(graph, frames, threshold, maxCount) {
1066
1080
  // src/core/segmenter.ts
1067
1081
  import { randomUUID } from "crypto";
1068
1082
  import { join as join5 } from "path";
1083
+ import { filter as filter5, map as map6 } from "@winglet/common-utils";
1069
1084
 
1070
1085
  // src/utils/concurrency.ts
1071
1086
  function concurrencyLimit(limit) {
@@ -1204,18 +1219,30 @@ function remapEdges(segmentResults, globalIdMap) {
1204
1219
  const edgeMap = /* @__PURE__ */ new Map();
1205
1220
  for (const result of segmentResults) {
1206
1221
  for (const edge of result.edges) {
1207
- const newSourceId = globalIdMap.get(`${result.segment.index}:${edge.sourceId}`);
1208
- const newTargetId = globalIdMap.get(`${result.segment.index}:${edge.targetId}`);
1222
+ const newSourceId = globalIdMap.get(
1223
+ `${result.segment.index}:${edge.sourceId}`
1224
+ );
1225
+ const newTargetId = globalIdMap.get(
1226
+ `${result.segment.index}:${edge.targetId}`
1227
+ );
1209
1228
  if (newSourceId === void 0 || newTargetId === void 0) continue;
1210
1229
  const edgeKey = `${newSourceId}-${newTargetId}`;
1211
1230
  const existingIdx = edgeMap.get(edgeKey);
1212
1231
  if (existingIdx !== void 0) {
1213
1232
  if (edges[existingIdx].score < edge.score) {
1214
- edges[existingIdx] = { sourceId: newSourceId, targetId: newTargetId, score: edge.score };
1233
+ edges[existingIdx] = {
1234
+ sourceId: newSourceId,
1235
+ targetId: newTargetId,
1236
+ score: edge.score
1237
+ };
1215
1238
  }
1216
1239
  } else {
1217
1240
  edgeMap.set(edgeKey, edges.length);
1218
- edges.push({ sourceId: newSourceId, targetId: newTargetId, score: edge.score });
1241
+ edges.push({
1242
+ sourceId: newSourceId,
1243
+ targetId: newTargetId,
1244
+ score: edge.score
1245
+ });
1219
1246
  }
1220
1247
  }
1221
1248
  }
@@ -1225,10 +1252,18 @@ function remapAnimations(segmentResults, globalIdMap) {
1225
1252
  const animations = [];
1226
1253
  for (const result of segmentResults) {
1227
1254
  for (const anim of result.animations) {
1228
- const newStartId = globalIdMap.get(`${result.segment.index}:${anim.startFrameId}`);
1229
- const newEndId = globalIdMap.get(`${result.segment.index}:${anim.endFrameId}`);
1255
+ const newStartId = globalIdMap.get(
1256
+ `${result.segment.index}:${anim.startFrameId}`
1257
+ );
1258
+ const newEndId = globalIdMap.get(
1259
+ `${result.segment.index}:${anim.endFrameId}`
1260
+ );
1230
1261
  if (newStartId === void 0 || newEndId === void 0) continue;
1231
- animations.push({ ...anim, startFrameId: newStartId, endFrameId: newEndId });
1262
+ animations.push({
1263
+ ...anim,
1264
+ startFrameId: newStartId,
1265
+ endFrameId: newEndId
1266
+ });
1232
1267
  }
1233
1268
  }
1234
1269
  return animations;
@@ -1309,7 +1344,7 @@ async function runSegmentedPipeline(options2, resolvedOptions) {
1309
1344
  logger.debug(`Segment plan: ${segments.length} segments`);
1310
1345
  const limit = concurrencyLimit(resolvedOptions.concurrency);
1311
1346
  const segmentProgresses = new Array(segments.length).fill(0);
1312
- const weights = segments.map((s) => s.duration / totalDuration);
1347
+ const weights = map6(segments, (s) => s.duration / totalDuration);
1313
1348
  const emitOverallProgress = (phase) => {
1314
1349
  if (!options2.onProgress) return;
1315
1350
  const overall = weights.reduce(
@@ -1320,7 +1355,8 @@ async function runSegmentedPipeline(options2, resolvedOptions) {
1320
1355
  };
1321
1356
  options2.onProgress?.("EXTRACTING", 0);
1322
1357
  const results = await Promise.all(
1323
- segments.map(
1358
+ map6(
1359
+ segments,
1324
1360
  (segment) => limit(async () => {
1325
1361
  const segWorkspace = await createSegmentWorkspace(
1326
1362
  mainWorkspace,
@@ -1352,7 +1388,7 @@ async function runSegmentedPipeline(options2, resolvedOptions) {
1352
1388
  resolvedOptions.threshold,
1353
1389
  resolvedOptions.count
1354
1390
  );
1355
- const prunedFrames = frames.filter((f) => survivingIds.has(f.id));
1391
+ const prunedFrames = filter5(frames, (f) => survivingIds.has(f.id));
1356
1392
  options2.onProgress?.("PRUNING", 100);
1357
1393
  options2.onProgress?.("FINALIZING", 0);
1358
1394
  const ctx = {
@@ -1462,7 +1498,7 @@ async function runPipeline(options2) {
1462
1498
  resolvedOptions.threshold,
1463
1499
  resolvedOptions.count
1464
1500
  );
1465
- const prunedFrames = ctx.frames.filter((f) => survivingIds.has(f.id));
1501
+ const prunedFrames = filter6(ctx.frames, (f) => survivingIds.has(f.id));
1466
1502
  ctx.emitProgress(100);
1467
1503
  ctx.status = "FINALIZING";
1468
1504
  let outputFiles = [];
@@ -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,5 @@
1
+ /**
2
+ * Creates a concurrency limiter that runs at most `limit` tasks in parallel.
3
+ * Lightweight replacement for p-limit to avoid external dependency.
4
+ */
5
+ export declare function concurrencyLimit(limit: number): <T>(fn: () => Promise<T>) => Promise<T>;
@@ -0,0 +1,8 @@
1
+ export declare function setDebugMode(enabled: boolean): void;
2
+ export declare const logger: {
3
+ info(message: string): void;
4
+ success(message: string): void;
5
+ warn(message: string): void;
6
+ error(message: string): void;
7
+ debug(message: string): void;
8
+ };
@@ -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.10",
3
+ "version": "0.0.12",
4
4
  "description": "CLI tool for extracting key frames from video and GIF files",
5
5
  "keywords": [
6
6
  "cli",
@@ -55,8 +55,9 @@
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",
59
+ "@winglet/common-utils": "^0.11.2",
60
+ "@winglet/react-utils": "^0.11.2",
60
61
  "commander": "^12.1.0",
61
62
  "execa": "^9.5.0",
62
63
  "ffmpeg-static": "^5.2.0",
@@ -67,6 +68,7 @@
67
68
  "sharp": "^0.33.0"
68
69
  },
69
70
  "devDependencies": {
71
+ "@lumy-pack/shared": "0.0.1",
70
72
  "@types/node": "^20.11.0",
71
73
  "@types/react": "^18.0.0",
72
74
  "@vitest/coverage-v8": "^3.2.4"